kgabryje commented on code in PR #26202:
URL: https://github.com/apache/superset/pull/26202#discussion_r1494625377


##########
superset-frontend/src/features/alerts/AlertReportModal.tsx:
##########
@@ -1154,403 +1196,495 @@ const AlertReportModal: 
FunctionComponent<AlertReportModalProps> = ({
     notificationSettings,
     conditionNotNull,
   ]);
+  useEffect(() => {
+    enforceValidation();
+  }, [validationStatus]);
 
   // Show/hide
   if (isHidden && show) {
     setIsHidden(false);
   }
 
+  const getTitleText = () => {
+    let titleText;
+
+    switch (true) {
+      case isEditMode && isReport:
+        titleText = t('Edit Report');
+        break;
+      case isEditMode:
+        titleText = t('Edit Alert');
+        break;
+      case isReport:
+        titleText = t('Add Report');
+        break;
+      default:
+        titleText = t('Add Alert');
+        break;
+    }
+
+    return titleText;
+  };
+
   return (
     <StyledModal
       className="no-content-padding"
       responsive
       disablePrimaryButton={disableSave}
+      primaryTooltipMessage={errorTooltipMessage}
       onHandledPrimaryAction={onSave}
       onHide={hide}
-      primaryButtonName={
-        isEditMode ? TRANSLATIONS.SAVE_TEXT : TRANSLATIONS.ADD_TEXT
-      }
+      primaryButtonName={isEditMode ? t('Save') : t('Add')}
       show={show}
-      width="100%"
-      maxWidth="1450px"
-      title={
-        <h4 data-test="alert-report-modal-title">
-          {isEditMode ? (
-            <Icons.EditAlt css={StyledIcon} />
-          ) : (
-            <Icons.PlusLarge css={StyledIcon} />
-          )}
-          {isEditMode && isReport
-            ? TRANSLATIONS.EDIT_REPORT_TEXT
-            : isEditMode
-              ? TRANSLATIONS.EDIT_ALERT_TEXT
-              : isReport
-                ? TRANSLATIONS.ADD_REPORT_TEXT
-                : TRANSLATIONS.ADD_ALERT_TEXT}
-        </h4>
-      }
+      width="500px"
+      centered
+      title={<h4 data-test="alert-report-modal-title">{getTitleText()}</h4>}
     >
-      <StyledSectionContainer>
-        <div className="header-section">
-          <StyledInputContainer>
-            <div className="control-label">
-              {isReport
-                ? TRANSLATIONS.REPORT_NAME_TEXT
-                : TRANSLATIONS.ALERT_NAME_TEXT}
-              <span className="required">*</span>
-            </div>
-            <div className="input-container">
-              <input
-                type="text"
-                name="name"
-                value={currentAlert ? currentAlert.name : ''}
-                placeholder={
-                  isReport
-                    ? TRANSLATIONS.REPORT_NAME_TEXT
-                    : TRANSLATIONS.ALERT_NAME_TEXT
-                }
-                onChange={onInputChange}
-                css={inputSpacer}
-              />
-            </div>
-          </StyledInputContainer>
-          <StyledInputContainer>
-            <div className="control-label">
-              {TRANSLATIONS.OWNERS_TEXT}
-              <span className="required">*</span>
-            </div>
-            <div data-test="owners-select" className="input-container">
-              <AsyncSelect
-                ariaLabel={TRANSLATIONS.OWNERS_TEXT}
-                allowClear
-                name="owners"
-                mode="multiple"
-                value={
-                  (currentAlert?.owners as {
-                    label: string;
-                    value: number;
-                  }[]) || []
-                }
-                options={loadOwnerOptions}
-                onChange={onOwnersChange}
-                css={inputSpacer}
-              />
-            </div>
-          </StyledInputContainer>
-          <StyledInputContainer>
-            <div 
className="control-label">{TRANSLATIONS.DESCRIPTION_TEXT}</div>
-            <div className="input-container">
-              <input
-                type="text"
-                name="description"
-                value={currentAlert ? currentAlert.description || '' : ''}
-                placeholder={TRANSLATIONS.DESCRIPTION_TEXT}
-                onChange={onInputChange}
-                css={inputSpacer}
-              />
-            </div>
-          </StyledInputContainer>
-          <StyledSwitchContainer>
-            <Switch
-              onChange={onActiveSwitch}
-              checked={currentAlert ? currentAlert.active : true}
+      <Collapse
+        expandIconPosition="right"
+        defaultActiveKey="general"
+        accordion
+        css={css`
+          border: 'none';
+        `}
+      >
+        <StyledPanel
+          header={
+            <ValidatedPanelHeader
+              title={TRANSLATIONS.GENERAL_TITLE}
+              subtitle={t(
+                'Set up basic details, such as name and description.',
+              )}
+              validateCheckStatus={
+                !validationStatus[Sections.General].hasErrors
+              }
+              testId="general-information-panel"
             />
-            <div className="switch-label">{TRANSLATIONS.ACTIVE_TEXT}</div>
-          </StyledSwitchContainer>
-        </div>
-        <div className="column-section">
-          {!isReport && (
-            <div className="column condition">
-              <StyledSectionTitle>
-                <h4>{TRANSLATIONS.ALERT_CONDITION_TEXT}</h4>
-              </StyledSectionTitle>
-              <StyledInputContainer>
-                <div className="control-label">
-                  {TRANSLATIONS.DATABASE_TEXT}
-                  <span className="required">*</span>
-                </div>
-                <div className="input-container">
-                  <AsyncSelect
-                    ariaLabel={TRANSLATIONS.DATABASE_TEXT}
-                    name="source"
-                    value={
-                      currentAlert?.database?.label &&
-                      currentAlert?.database?.value
-                        ? {
-                            value: currentAlert.database.value,
-                            label: currentAlert.database.label,
-                          }
-                        : undefined
-                    }
-                    options={loadSourceOptions}
-                    onChange={onSourceChange}
-                  />
-                </div>
-              </StyledInputContainer>
-              <StyledInputContainer>
-                <div className="control-label">
-                  {TRANSLATIONS.SQL_QUERY_TEXT}
-                  <StyledTooltip tooltip={TRANSLATIONS.SQL_QUERY_TOOLTIP} />
-                  <span className="required">*</span>
-                </div>
-                <TextAreaControl
-                  name="sql"
-                  language="sql"
-                  offerEditInModal={false}
-                  minLines={15}
-                  maxLines={15}
-                  onChange={onSQLChange}
-                  readOnly={false}
-                  initialValue={resource?.sql}
-                  key={currentAlert?.id}
+          }
+          key="general"
+        >
+          <div className="header-section">
+            <StyledInputContainer>
+              <div className="control-label">
+                {isReport ? t('Report name') : t('Alert name')}
+                <span className="required">*</span>
+              </div>
+              <div className="input-container">
+                <input
+                  type="text"
+                  name="name"
+                  value={currentAlert ? currentAlert.name : ''}
+                  placeholder={
+                    isReport ? t('Enter report name') : t('Enter alert name')
+                  }
+                  onChange={onInputChange}
                 />
-              </StyledInputContainer>
-              <div className="inline-container wrap">
-                <StyledInputContainer>
-                  <div className="control-label" css={inputSpacer}>
-                    {TRANSLATIONS.TRIGGER_ALERT_IF_TEXT}
-                    <span className="required">*</span>
-                  </div>
-                  <div className="input-container">
-                    <Select
-                      ariaLabel={TRANSLATIONS.CONDITION_TEXT}
-                      onChange={onConditionChange}
-                      placeholder="Condition"
-                      value={
-                        currentAlert?.validator_config_json?.op || undefined
-                      }
-                      options={CONDITIONS}
-                      css={inputSpacer}
-                    />
-                  </div>
-                </StyledInputContainer>
-                <StyledInputContainer>
-                  <div className="control-label">
-                    {TRANSLATIONS.VALUE_TEXT}
-                    <span className="required">*</span>
-                  </div>
-                  <div className="input-container">
-                    <input
-                      type="number"
-                      name="threshold"
-                      disabled={conditionNotNull}
-                      value={
-                        currentAlert?.validator_config_json?.threshold !==
-                        undefined
-                          ? currentAlert.validator_config_json.threshold
-                          : ''
-                      }
-                      placeholder={TRANSLATIONS.VALUE_TEXT}
-                      onChange={onThresholdChange}
-                    />
-                  </div>
-                </StyledInputContainer>
               </div>
-            </div>
-          )}
-          <div className="column schedule">
-            <StyledSectionTitle>
-              <h4>
-                {isReport
-                  ? TRANSLATIONS.REPORT_SCHEDULE_TEXT
-                  : TRANSLATIONS.ALERT_CONDITION_SCHEDULE_TEXT}
-              </h4>
-              <span className="required">*</span>
-            </StyledSectionTitle>
-            <AlertReportCronScheduler
-              value={currentAlert?.crontab || ALERT_REPORTS_DEFAULT_CRON_VALUE}
-              onChange={newVal => updateAlertState('crontab', newVal)}
-            />
-            <div className="control-label">{TRANSLATIONS.TIMEZONE_TEXT}</div>
-            <div
-              className="input-container"
-              css={(theme: SupersetTheme) => timezoneHeaderStyle(theme)}
-            >
-              <TimezoneSelector
-                onTimezoneChange={onTimezoneChange}
-                timezone={currentAlert?.timezone}
-                minWidth="100%"
-              />
-            </div>
-            <StyledSectionTitle>
-              <h4>{TRANSLATIONS.SCHEDULE_SETTINGS_TEXT}</h4>
-            </StyledSectionTitle>
+            </StyledInputContainer>
             <StyledInputContainer>
               <div className="control-label">
-                {TRANSLATIONS.LOG_RETENTION_TEXT}
+                {t('Owners')}
                 <span className="required">*</span>
               </div>
-              <div className="input-container">
-                <Select
-                  ariaLabel={TRANSLATIONS.LOG_RETENTION_TEXT}
-                  placeholder={TRANSLATIONS.LOG_RETENTION_TEXT}
-                  onChange={onLogRetentionChange}
+              <div data-test="owners-select" className="input-container">
+                <AsyncSelect
+                  ariaLabel={t('Owners')}
+                  allowClear
+                  name="owners"
+                  mode="multiple"
+                  placeholder={t('Select owners')}
                   value={
-                    typeof currentAlert?.log_retention === 'number'
-                      ? currentAlert?.log_retention
-                      : ALERT_REPORTS_DEFAULT_RETENTION
+                    (currentAlert?.owners as {
+                      label: string;
+                      value: number;
+                    }[]) || []
                   }
-                  options={RETENTION_OPTIONS}
-                  sortComparator={propertyComparator('value')}
+                  options={loadOwnerOptions}
+                  onChange={onOwnersChange}
                 />
               </div>
             </StyledInputContainer>
+            <StyledInputContainer>
+              <div className="control-label">{t('Description')}</div>
+              <div className="input-container">
+                <input
+                  type="text"
+                  name="description"
+                  value={currentAlert ? currentAlert.description || '' : ''}
+                  placeholder={t(
+                    'Include description to be sent with %s',
+                    reportOrAlert,
+                  )}
+                  onChange={onInputChange}
+                />
+              </div>
+            </StyledInputContainer>
+            <StyledSwitchContainer>
+              <Switch
+                checked={currentAlert ? currentAlert.active : false}
+                defaultChecked
+                onChange={onActiveSwitch}
+              />
+              <div className="switch-label">
+                {isReport ? t('Report is active') : t('Alert is active')}
+              </div>
+            </StyledSwitchContainer>
+          </div>
+        </StyledPanel>
+        {!isReport && (
+          <StyledPanel
+            header={
+              <ValidatedPanelHeader
+                title={TRANSLATIONS.ALERT_CONDITION_TITLE}
+                subtitle={t(
+                  'Define the database, SQL query, and triggering conditions 
for alert.',
+                )}
+                validateCheckStatus={
+                  !validationStatus[Sections.Alert].hasErrors
+                }
+                testId="alert-condition-panel"
+              />
+            }
+            key="condition"
+          >
             <StyledInputContainer>
               <div className="control-label">
-                {TRANSLATIONS.WORKING_TIMEOUT_TEXT}
+                {t('Database')}
                 <span className="required">*</span>
               </div>
               <div className="input-container">
-                <input
-                  type="number"
-                  min="1"
-                  name="working_timeout"
-                  value={currentAlert?.working_timeout || ''}
-                  placeholder={TRANSLATIONS.TIME_IN_SECONDS_TEXT}
-                  onChange={onTimeoutVerifyChange}
+                <AsyncSelect
+                  ariaLabel={t('Database')}
+                  name="source"
+                  placeholder={t('Select database')}
+                  value={
+                    currentAlert?.database?.label &&
+                    currentAlert?.database?.value
+                      ? {
+                          value: currentAlert.database.value,
+                          label: currentAlert.database.label,
+                        }
+                      : undefined
+                  }
+                  options={loadSourceOptions}
+                  onChange={onSourceChange}
                 />
-                <span 
className="input-label">{TRANSLATIONS.SECONDS_TEXT}</span>
               </div>
             </StyledInputContainer>
-            {!isReport && (
-              <StyledInputContainer>
+            <StyledInputContainer>
+              <div className="control-label">
+                {t('SQL Query')}
+                <StyledTooltip
+                  tooltip={t(
+                    'The result of this query must be a value capable of 
numeric interpretation e.g. 1, 1.0, or "1" (compatible with Python\'s float() 
function).',
+                  )}
+                />
+                <span className="required">*</span>
+              </div>
+              <TextAreaControl
+                name="sql"
+                language="sql"
+                offerEditInModal={false}
+                minLines={15}
+                maxLines={15}
+                onChange={onSQLChange}
+                readOnly={false}
+                initialValue={resource?.sql}
+                key={currentAlert?.id}
+              />
+            </StyledInputContainer>
+            <div className="inline-container wrap">
+              <StyledInputContainer css={noMarginBottom}>
+                <div className="control-label" css={inputSpacer}>
+                  {t('Trigger Alert If...')}
+                  <span className="required">*</span>
+                </div>
+                <div className="input-container">
+                  <Select
+                    ariaLabel={t('Condition')}
+                    onChange={onConditionChange}
+                    placeholder={t('Condition')}
+                    value={currentAlert?.validator_config_json?.op || 
undefined}
+                    options={CONDITIONS}
+                    css={inputSpacer}
+                  />
+                </div>
+              </StyledInputContainer>
+              <StyledInputContainer css={noMarginBottom}>
                 <div className="control-label">
-                  {TRANSLATIONS.GRACE_PERIOD_TEXT}
+                  {t('Value')} <span className="required">*</span>
                 </div>
                 <div className="input-container">
                   <input
                     type="number"
-                    min="1"
-                    name="grace_period"
-                    value={currentAlert?.grace_period || ''}
-                    placeholder={TRANSLATIONS.TIME_IN_SECONDS_TEXT}
-                    onChange={onTimeoutVerifyChange}
+                    name="threshold"
+                    disabled={conditionNotNull}
+                    value={
+                      currentAlert?.validator_config_json?.threshold !==
+                      undefined
+                        ? currentAlert.validator_config_json.threshold
+                        : ''
+                    }
+                    placeholder={t('Value')}
+                    onChange={onThresholdChange}
                   />
-                  <span className="input-label">
-                    {TRANSLATIONS.SECONDS_TEXT}
-                  </span>
                 </div>
               </StyledInputContainer>
-            )}
-          </div>
-          <div className="column message">
-            <StyledSectionTitle>
-              <h4>{TRANSLATIONS.MESSAGE_CONTENT_TEXT}</h4>
+            </div>
+          </StyledPanel>
+        )}
+        <StyledPanel
+          header={
+            <ValidatedPanelHeader
+              title={
+                isReport
+                  ? TRANSLATIONS.REPORT_CONTENTS_TITLE
+                  : TRANSLATIONS.ALERT_CONTENTS_TITLE
+              }
+              subtitle={t('Customize data source, filters, and layout.')}
+              validateCheckStatus={
+                !validationStatus[Sections.Content].hasErrors
+              }
+              testId="contents-panel"
+            />
+          }
+          key="contents"
+        >
+          <StyledInputContainer>
+            <div className="control-label">
+              {t('Content type')}
               <span className="required">*</span>
-            </StyledSectionTitle>
-            <Radio.Group onChange={onContentTypeChange} value={contentType}>
-              <StyledRadio value="dashboard">
-                {TRANSLATIONS.DASHBOARD_TEXT}
-              </StyledRadio>
-              <StyledRadio 
value="chart">{TRANSLATIONS.CHART_TEXT}</StyledRadio>
-            </Radio.Group>
+            </div>
+            <Select
+              ariaLabel={t('Select content type')}
+              onChange={onContentTypeChange}
+              value={contentType}
+              options={CONTENT_TYPE_OPTIONS}
+              placeholder={t('Select content type')}
+            />
+          </StyledInputContainer>
+          <StyledInputContainer>
             {contentType === 'chart' ? (
-              <AsyncSelect
-                ariaLabel={TRANSLATIONS.CHART_TEXT}
-                name="chart"
-                value={
-                  currentAlert?.chart?.label && currentAlert?.chart?.value
-                    ? {
-                        value: currentAlert.chart.value,
-                        label: currentAlert.chart.label,
-                      }
-                    : undefined
-                }
-                options={loadChartOptions}
-                onChange={onChartChange}
-              />
+              <>
+                <div className="control-label">
+                  {t('Select chart')}
+                  <span className="required">*</span>
+                </div>
+                <AsyncSelect
+                  ariaLabel={t('Chart')}
+                  name="chart"
+                  value={
+                    currentAlert?.chart?.label && currentAlert?.chart?.value
+                      ? {
+                          value: currentAlert.chart.value,
+                          label: currentAlert.chart.label,
+                        }
+                      : undefined
+                  }
+                  options={loadChartOptions}
+                  onChange={onChartChange}
+                  placeholder={t('Select chart to use')}
+                />
+              </>
             ) : (
-              <AsyncSelect
-                ariaLabel={TRANSLATIONS.DASHBOARD_TEXT}
-                name="dashboard"
-                value={
-                  currentAlert?.dashboard?.label &&
-                  currentAlert?.dashboard?.value
-                    ? {
-                        value: currentAlert.dashboard.value,
-                        label: currentAlert.dashboard.label,
-                      }
-                    : undefined
-                }
-                options={loadDashboardOptions}
-                onChange={onDashboardChange}
-              />
+              <>
+                <div className="control-label">
+                  {t('Select dashboard')}
+                  <span className="required">*</span>
+                </div>
+                <AsyncSelect
+                  ariaLabel={t('Dashboard')}
+                  name="dashboard"
+                  value={
+                    currentAlert?.dashboard?.label &&
+                    currentAlert?.dashboard?.value
+                      ? {
+                          value: currentAlert.dashboard.value,
+                          label: currentAlert.dashboard.label,
+                        }
+                      : undefined
+                  }
+                  options={loadDashboardOptions}
+                  onChange={onDashboardChange}
+                  placeholder={t('Select dashboard to use')}
+                />
+              </>
             )}
+          </StyledInputContainer>
+          <StyledInputContainer
+            css={['TEXT', 'CSV'].includes(reportFormat) && noMarginBottom}
+          >
             {formatOptionEnabled && (
               <>
-                <div className="inline-container">
-                  <StyledRadioGroup
-                    onChange={onFormatChange}
-                    value={reportFormat}
-                  >
-                    <StyledRadio value="PNG">
-                      {TRANSLATIONS.SEND_AS_PNG_TEXT}
-                    </StyledRadio>
-                    <StyledRadio value="CSV">
-                      {TRANSLATIONS.SEND_AS_CSV_TEXT}
-                    </StyledRadio>
-                    {TEXT_BASED_VISUALIZATION_TYPES.includes(chartVizType) && (
-                      <StyledRadio value="TEXT">
-                        {TRANSLATIONS.SEND_AS_TEXT}
-                      </StyledRadio>
-                    )}
-                  </StyledRadioGroup>
+                <div className="control-label">
+                  {t('Content format')}
+                  <span className="required">*</span>
                 </div>
+                <Select
+                  ariaLabel={t('Select format')}
+                  onChange={onFormatChange}
+                  value={reportFormat}
+                  options={
+                    /* If chart is of text based viz type: show text
+                  format option */
+                    TEXT_BASED_VISUALIZATION_TYPES.includes(chartVizType)
+                      ? Object.values(FORMAT_OPTIONS)
+                      : ['png', 'csv'].map(key => FORMAT_OPTIONS[key])
+                  }
+                  placeholder={t('Select format')}
+                />
               </>
             )}
-            {isScreenshot && (
-              <StyledInputContainer>
-                <div className="control-label" css={CustomWidthHeaderStyle}>
-                  {TRANSLATIONS.CUSTOM_SCREENSHOT_WIDTH_TEXT}
+          </StyledInputContainer>
+          {isScreenshot && (
+            <StyledInputContainer
+              css={!isReport && contentType === 'chart' && noMarginBottom}

Review Comment:
   Nit: instead of all that logic for adding `noMarginBottom` styles, could we 
maybe just use something like `:last-child` in `StyledInputContainer`? That 
would simplify the logic and we could get rid of `noMarginBottom` everywhere.
   
   If you agree, I'm ok with doing that as a cleanup follow up



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to