From 7a0c5421e3f20846f509f2b6c51f6beda9a73cd1 Mon Sep 17 00:00:00 2001 From: Puridach wutthihathaithamrong <76474644+puridach-w@users.noreply.github.com> Date: Thu, 16 May 2024 20:04:42 +0700 Subject: [PATCH] feat: Customizable email subject name (#26327) Co-authored-by: Puridach Wutthihathaithamrong <> --- .../src/features/alerts/AlertReportModal.tsx | 46 ++++++++ .../alerts/components/NotificationMethod.tsx | 104 +++++++++++++++--- .../src/features/alerts/types.ts | 1 + superset/commands/report/execute.py | 21 ++-- ...b_add_subject_column_to_report_schedule.py | 41 +++++++ superset/reports/api.py | 1 + superset/reports/models.py | 2 + superset/reports/schemas.py | 17 +++ 8 files changed, 209 insertions(+), 24 deletions(-) create mode 100644 superset/migrations/versions/2024-05-10_11-09_9621c6d56ffb_add_subject_column_to_report_schedule.py diff --git a/superset-frontend/src/features/alerts/AlertReportModal.tsx b/superset-frontend/src/features/alerts/AlertReportModal.tsx index fd0cf7eda9592..ac2f6a56500b3 100644 --- a/superset-frontend/src/features/alerts/AlertReportModal.tsx +++ b/superset-frontend/src/features/alerts/AlertReportModal.tsx @@ -367,6 +367,7 @@ export const TRANSLATIONS = { CRONTAB_ERROR_TEXT: t('crontab'), WORKING_TIMEOUT_ERROR_TEXT: t('working timeout'), RECIPIENTS_ERROR_TEXT: t('recipients'), + EMAIL_SUBJECT_ERROR_TEXT: t('email subject'), ERROR_TOOLTIP_MESSAGE: t( 'Not all required fields are complete. Please provide the following:', ), @@ -491,6 +492,9 @@ const AlertReportModal: FunctionComponent = ({ const [notificationSettings, setNotificationSettings] = useState< NotificationSetting[] >([]); + const [emailSubject, setEmailSubject] = useState(''); + const [emailError, setEmailError] = useState(false); + const onNotificationAdd = () => { setNotificationSettings([ ...notificationSettings, @@ -543,6 +547,7 @@ const AlertReportModal: FunctionComponent = ({ owners: [], recipients: [], sql: '', + email_subject: '', validator_config_json: {}, validator_type: '', force_screenshot: false, @@ -888,6 +893,10 @@ const AlertReportModal: FunctionComponent = ({ const parsedValue = type === 'number' ? parseInt(value, 10) || null : value; updateAlertState(name, parsedValue); + + if (name === 'name') { + updateEmailSubject(); + } }; const onCustomWidthChange = (value: number | null | undefined) => { @@ -1058,6 +1067,11 @@ const AlertReportModal: FunctionComponent = ({ const validateNotificationSection = () => { const hasErrors = !checkNotificationSettings(); const errors = hasErrors ? [TRANSLATIONS.RECIPIENTS_ERROR_TEXT] : []; + + if (emailError) { + errors.push(TRANSLATIONS.EMAIL_SUBJECT_ERROR_TEXT); + } + updateValidationStatus(Sections.Notification, errors); }; @@ -1199,6 +1213,7 @@ const AlertReportModal: FunctionComponent = ({ const currentAlertSafe = currentAlert || {}; useEffect(() => { validateAll(); + updateEmailSubject(); }, [ currentAlertSafe.name, currentAlertSafe.owners, @@ -1212,6 +1227,7 @@ const AlertReportModal: FunctionComponent = ({ contentType, notificationSettings, conditionNotNull, + emailError, ]); useEffect(() => { enforceValidation(); @@ -1243,6 +1259,32 @@ const AlertReportModal: FunctionComponent = ({ return titleText; }; + const updateEmailSubject = () => { + if (contentType === 'chart') { + if (currentAlert?.name || currentAlert?.chart?.label) { + setEmailSubject( + `${currentAlert?.name}: ${currentAlert?.chart?.label || ''}`, + ); + } else { + setEmailSubject(''); + } + } else if (contentType === 'dashboard') { + if (currentAlert?.name || currentAlert?.dashboard?.label) { + setEmailSubject( + `${currentAlert?.name}: ${currentAlert?.dashboard?.label || ''}`, + ); + } else { + setEmailSubject(''); + } + } else { + setEmailSubject(''); + } + }; + + const handleErrorUpdate = (hasError: boolean) => { + setEmailError(hasError); + }; + return ( = ({ key={`NotificationMethod-${i}`} onUpdate={updateNotificationSetting} onRemove={removeNotificationSetting} + onInputChange={onInputChange} + email_subject={currentAlert?.email_subject || ''} + defaultSubject={emailSubject || ''} + setErrorSubject={handleErrorUpdate} /> ))} diff --git a/superset-frontend/src/features/alerts/components/NotificationMethod.tsx b/superset-frontend/src/features/alerts/components/NotificationMethod.tsx index b50988700ef4e..614018c12a171 100644 --- a/superset-frontend/src/features/alerts/components/NotificationMethod.tsx +++ b/superset-frontend/src/features/alerts/components/NotificationMethod.tsx @@ -30,6 +30,12 @@ const StyledNotificationMethod = styled.div` textarea { height: auto; } + + &.error { + input { + border-color: ${({ theme }) => theme.colors.error.base}; + } + } } .inline-container { @@ -51,18 +57,36 @@ interface NotificationMethodProps { index: number; onUpdate?: (index: number, updatedSetting: NotificationSetting) => void; onRemove?: (index: number) => void; + onInputChange?: ( + event: React.ChangeEvent, + ) => void; + email_subject: string; + defaultSubject: string; + setErrorSubject: (hasError: boolean) => void; } +const TRANSLATIONS = { + EMAIL_SUBJECT_NAME: t('Email subject name (optional)'), + EMAIL_SUBJECT_ERROR_TEXT: t( + 'Please enter valid text. Spaces alone are not permitted.', + ), +}; + export const NotificationMethod: FunctionComponent = ({ setting = null, index, onUpdate, onRemove, + onInputChange, + email_subject, + defaultSubject, + setErrorSubject, }) => { const { method, recipients, options } = setting || {}; const [recipientValue, setRecipientValue] = useState( recipients || '', ); + const [error, setError] = useState(false); const theme = useTheme(); if (!setting) { @@ -100,6 +124,22 @@ export const NotificationMethod: FunctionComponent = ({ } }; + const onSubjectChange = ( + event: React.ChangeEvent, + ) => { + const { value } = event.target; + + if (onInputChange) { + onInputChange(event); + } + + const hasError = value.length > 0 && value.trim().length === 0; + setError(hasError); + if (setErrorSubject) { + setErrorSubject(hasError); + } + }; + // Set recipients if (!!recipients && recipientValue !== recipients) { setRecipientValue(recipients); @@ -138,23 +178,57 @@ export const NotificationMethod: FunctionComponent = ({ {method !== undefined ? ( - -
- {t('%s recipients', method)} - * + <> +
+ + {method === 'Email' ? ( + <> +
+ {TRANSLATIONS.EMAIL_SUBJECT_NAME} +
+
+ +
+ {error && ( +
+ {TRANSLATIONS.EMAIL_SUBJECT_ERROR_TEXT} +
+ )} + + ) : null} +
-
-