in superset-frontend/src/features/reports/ReportModal/index.tsx [110:351]
function ReportModal({
onHide,
show = false,
dashboardId,
chart,
userId,
userEmail,
ccEmail,
bccEmail,
creationMethod,
dashboardName,
chartName,
}: ReportProps) {
const vizType = chart?.sliceFormData?.viz_type;
const isChart = !!chart;
const isTextBasedChart =
isChart && vizType && TEXT_BASED_VISUALIZATION_TYPES.includes(vizType);
const defaultNotificationFormat = isTextBasedChart
? NotificationFormats.Text
: NotificationFormats.PNG;
const entityName = dashboardName || chartName;
const initialState: ReportObjectState = useMemo(
() => ({
...INITIAL_STATE,
name: entityName
? t('Weekly Report for %s', entityName)
: t('Weekly Report'),
}),
[entityName],
);
const reportReducer = useCallback(
(state: ReportObjectState | null, action: 'reset' | ReportObjectState) => {
if (action === 'reset') {
return initialState;
}
return {
...state,
...action,
};
},
[initialState],
);
const [currentReport, setCurrentReport] = useReducer(
reportReducer,
initialState,
);
const [cronError, setCronError] = useState<CronError>();
const dispatch = useDispatch();
// Report fetch logic
const report = useSelector<any, ReportObject>(state => {
const resourceType = dashboardId
? CreationMethod.Dashboards
: CreationMethod.Charts;
return (
reportSelector(state, resourceType, dashboardId || chart?.id) ||
EMPTY_OBJECT
);
});
const isEditMode = report && Object.keys(report).length;
useEffect(() => {
if (isEditMode) {
setCurrentReport(report);
} else {
setCurrentReport('reset');
}
}, [isEditMode, report]);
const onSave = async () => {
// Create new Report
const newReportValues: Partial<ReportObject> = {
type: 'Report',
active: true,
force_screenshot: false,
custom_width: currentReport.custom_width,
creation_method: creationMethod,
dashboard: dashboardId,
chart: chart?.id,
owners: [userId],
recipients: [
{
recipient_config_json: {
target: userEmail,
ccTarget: ccEmail,
bccTarget: bccEmail,
},
type: 'Email',
},
],
name: currentReport.name,
description: currentReport.description,
crontab: currentReport.crontab,
report_format: currentReport.report_format || defaultNotificationFormat,
timezone: currentReport.timezone,
};
setCurrentReport({ isSubmitting: true, error: undefined });
try {
if (isEditMode) {
await dispatch(
editReport(currentReport.id, newReportValues as ReportObject),
);
} else {
await dispatch(addReport(newReportValues as ReportObject));
}
onHide();
} catch (e) {
const { error } = await getClientErrorObject(e);
setCurrentReport({ error });
}
setCurrentReport({ isSubmitting: false });
};
const wrappedTitle = (
<StyledIconWrapper>
<Icons.CalendarOutlined />
<span className="text">
{isEditMode ? t('Edit email report') : t('Schedule a new email report')}
</span>
</StyledIconWrapper>
);
const renderModalFooter = (
<>
<StyledFooterButton key="back" onClick={onHide}>
{t('Cancel')}
</StyledFooterButton>
<StyledFooterButton
key="submit"
buttonStyle="primary"
onClick={onSave}
disabled={!currentReport.name}
loading={currentReport.isSubmitting}
>
{isEditMode ? t('Save') : t('Add')}
</StyledFooterButton>
</>
);
const renderMessageContentSection = (
<>
<StyledMessageContentTitle>
<h4>{t('Message content')}</h4>
</StyledMessageContentTitle>
<div className="inline-container">
<Radio.GroupWrapper
spaceConfig={{
direction: 'vertical',
size: 'middle',
align: 'start',
wrap: false,
}}
onChange={(event: RadioChangeEvent) => {
setCurrentReport({ report_format: event.target.value });
}}
value={currentReport.report_format || defaultNotificationFormat}
options={[
{
label: t('Text embedded in email'),
value: NotificationFormats.Text,
},
{
label: t('Image (PNG) embedded in email'),
value: NotificationFormats.PNG,
},
{
label: t('Formatted CSV attached in email'),
value: NotificationFormats.CSV,
},
]}
/>
</div>
</>
);
const renderCustomWidthSection = (
<StyledInputContainer>
<div className="control-label" css={CustomWidthHeaderStyle}>
{t('Screenshot width')}
</div>
<div className="input-container">
<Input
type="number"
name="custom_width"
value={currentReport?.custom_width || ''}
placeholder={t('Input custom width in pixels')}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setCurrentReport({
custom_width: parseInt(event.target.value, 10) || null,
});
}}
/>
</div>
</StyledInputContainer>
);
return (
<StyledModal
show={show}
onHide={onHide}
title={wrappedTitle}
footer={renderModalFooter}
width="432"
centered
>
<StyledTopSection>
<LabeledErrorBoundInput
id="name"
name="name"
value={currentReport.name || ''}
placeholder={initialState.name}
required
validationMethods={{
onChange: ({ target }: { target: HTMLInputElement }) =>
setCurrentReport({ name: target.value }),
}}
label={t('Report Name')}
data-test="report-name-test"
/>
<LabeledErrorBoundInput
id="description"
name="description"
value={currentReport?.description || ''}
validationMethods={{
onChange: ({ target }: { target: HTMLInputElement }) => {
setCurrentReport({ description: target.value });
},
}}
label={t('Description')}
placeholder={t(
'Include a description that will be sent with your report',
)}
css={noBottomMargin}
data-test="report-description-test"
/>
</StyledTopSection>
<StyledBottomSection>
<StyledScheduleTitle>
<h4 css={(theme: SupersetTheme) => SectionHeaderStyle(theme)}>