Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(alerts): enable tab selection for dashboard alerts/reports #29096

Merged
merged 3 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions superset-frontend/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export {
Steps,
Tag,
Tree,
TreeSelect,
Typography,
Upload,
} from 'antd';
Expand Down
22 changes: 22 additions & 0 deletions superset-frontend/src/features/alerts/AlertReportModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,18 @@ const ownersEndpoint = 'glob:*/api/v1/alert/related/owners?*';
const databaseEndpoint = 'glob:*/api/v1/alert/related/database?*';
const dashboardEndpoint = 'glob:*/api/v1/alert/related/dashboard?*';
const chartEndpoint = 'glob:*/api/v1/alert/related/chart?*';
const tabsEndpoint = 'glob:*/api/v1/dashboard/1/tabs';

fetchMock.get(ownersEndpoint, { result: [] });
fetchMock.get(databaseEndpoint, { result: [] });
fetchMock.get(dashboardEndpoint, { result: [] });
fetchMock.get(chartEndpoint, { result: [{ text: 'table chart', value: 1 }] });
fetchMock.get(tabsEndpoint, {
result: {
all_tabs: {},
tab_tree: [],
},
});

// Create a valid alert with all required fields entered for validation check

Expand Down Expand Up @@ -413,6 +420,21 @@ test('renders screenshot options when dashboard is selected', async () => {
).toBeInTheDocument();
});

test('renders tab selection when Dashboard is selected', async () => {
render(<AlertReportModal {...generateMockedProps(false, true, true)} />, {
useRedux: true,
});
userEvent.click(screen.getByTestId('contents-panel'));
await screen.findByText(/test dashboard/i);
expect(
screen.getByRole('combobox', { name: /select content type/i }),
).toBeInTheDocument();
expect(
screen.getByRole('combobox', { name: /dashboard/i }),
).toBeInTheDocument();
expect(screen.getByText(/select tab/i)).toBeInTheDocument();
fisjac marked this conversation as resolved.
Show resolved Hide resolved
});

test('changes to content options when chart is selected', async () => {
render(<AlertReportModal {...generateMockedProps(false, true, true)} />, {
useRedux: true,
Expand Down
154 changes: 115 additions & 39 deletions superset-frontend/src/features/alerts/AlertReportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
import { propertyComparator } from 'src/components/Select/utils';
import withToasts from 'src/components/MessageToasts/withToasts';
import Owner from 'src/types/Owner';
import { AntdCheckbox, AsyncSelect, Select } from 'src/components';
import { AntdCheckbox, AsyncSelect, Select, TreeSelect } from 'src/components';
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
import { useCommonConf } from 'src/features/databases/state';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
Expand All @@ -57,12 +57,16 @@
ChartObject,
DashboardObject,
DatabaseObject,
Extra,
MetaObject,
Operator,
Recipient,
AlertsReportsConfig,
ValidationObject,
Sections,
TabNode,
SelectValue,
ContentType,
} from 'src/features/alerts/types';
import { useSelector } from 'react-redux';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
Expand All @@ -80,11 +84,6 @@
'paired_ttest',
];

type SelectValue = {
value: string;
label: string;
};

export interface AlertReportModalProps {
addSuccessToast: (msg: string) => void;
addDangerToast: (msg: string) => void;
Expand All @@ -103,6 +102,12 @@
NotificationMethodOption.Email,
];
const DEFAULT_NOTIFICATION_FORMAT = 'PNG';
const DEFAULT_EXTRA_DASHBOARD_OPTIONS: Extra = {
dashboard: {
anchor: '',
},
};

const CONDITIONS = [
{
label: t('< (Smaller than)'),
Expand Down Expand Up @@ -217,6 +222,10 @@
}
`;

const StyledTreeSelect = styled(TreeSelect)`
width: 100%;
`;

const StyledSwitchContainer = styled.div`
display: flex;
align-items: center;
Expand Down Expand Up @@ -439,6 +448,8 @@
const [sourceOptions, setSourceOptions] = useState<MetaObject[]>([]);
const [dashboardOptions, setDashboardOptions] = useState<MetaObject[]>([]);
const [chartOptions, setChartOptions] = useState<MetaObject[]>([]);
const [tabOptions, setTabOptions] = useState<TabNode[]>([]);

// Validation
const [validationStatus, setValidationStatus] = useState<ValidationObject>({
[Sections.General]: {
Expand Down Expand Up @@ -489,6 +500,7 @@
const isEditMode = alert !== null;
const formatOptionEnabled =
isFeatureEnabled(FeatureFlag.AlertsAttachReports) || isReport;
const tabsEnabled = isFeatureEnabled(FeatureFlag.AlertReportTabs);

const [notificationAddState, setNotificationAddState] =
useState<NotificationAddStatus>('active');
Expand Down Expand Up @@ -545,6 +557,7 @@
active: true,
creation_method: 'alerts_reports',
crontab: ALERT_REPORTS_DEFAULT_CRON_VALUE,
extra: DEFAULT_EXTRA_DASHBOARD_OPTIONS,
log_retention: ALERT_REPORTS_DEFAULT_RETENTION,
working_timeout: ALERT_REPORTS_DEFAULT_WORKING_TIMEOUT,
name: '',
Expand Down Expand Up @@ -593,6 +606,22 @@
setNotificationAddState('active');
};

const updateAnchorState = (value: any) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use more specific type?

Copy link
Contributor Author

@fisjac fisjac Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I'd like for this to be typed as string | undefined, but I'm running into issues from antd's typing for the onSelect prop which is typed as 'RawValueType | LabelValueType' and throws an error on compile when typed as such. I've used any as a workaround.

setCurrentAlert(currentAlertData => {
const dashboardState = currentAlertData?.extra?.dashboard;
const extra = {
dashboard: {
...dashboardState,

Check warning on line 614 in superset-frontend/src/features/alerts/AlertReportModal.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/features/alerts/AlertReportModal.tsx#L612-L614

Added lines #L612 - L614 were not covered by tests
anchor: value,
},
};
return {
...currentAlertData,
extra,

Check warning on line 620 in superset-frontend/src/features/alerts/AlertReportModal.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/features/alerts/AlertReportModal.tsx#L620

Added line #L620 was not covered by tests
};
});
};

// Alert fetch logic
const {
state: { loading, resource, error: fetchError },
Expand Down Expand Up @@ -627,7 +656,8 @@
}
});

const shouldEnableForceScreenshot = contentType === 'chart' && !isReport;
const shouldEnableForceScreenshot =
contentType === ContentType.Chart && !isReport;
const data: any = {
...currentAlert,
type: isReport ? 'Report' : 'Alert',
Expand All @@ -636,24 +666,27 @@
validator_config_json: conditionNotNull
? {}
: currentAlert?.validator_config_json,
chart: contentType === 'chart' ? currentAlert?.chart?.value : null,
chart:
contentType === ContentType.Chart ? currentAlert?.chart?.value : null,
dashboard:
contentType === 'dashboard' ? currentAlert?.dashboard?.value : null,
contentType === ContentType.Dashboard
? currentAlert?.dashboard?.value
: null,
custom_width: isScreenshot ? currentAlert?.custom_width : undefined,
database: currentAlert?.database?.value,
owners: (currentAlert?.owners || []).map(
owner => (owner as MetaObject).value || owner.id,
),
recipients,
report_format: reportFormat || DEFAULT_NOTIFICATION_FORMAT,
extra: contentType === ContentType.Dashboard ? currentAlert?.extra : null,
};

if (data.recipients && !data.recipients.length) {
delete data.recipients;
}

data.context_markdown = 'string';

if (isEditMode) {
// Edit
if (currentAlert?.id) {
Expand Down Expand Up @@ -776,6 +809,28 @@
[],
);

const dashboard = currentAlert?.dashboard;
useEffect(() => {
if (!tabsEnabled) return;

if (dashboard?.value) {
SupersetClient.get({
fisjac marked this conversation as resolved.
Show resolved Hide resolved
endpoint: `/api/v1/dashboard/${dashboard.value}/tabs`,
})
.then(response => {
const { tab_tree: tabTree, all_tabs: allTabs } = response.json.result;
setTabOptions(tabTree);
const anchor = currentAlert?.extra?.dashboard?.anchor;
if (anchor && !(anchor in allTabs)) {
updateAnchorState(undefined);
}
})
.catch(() => {
addDangerToast(t('There was an error retrieving dashboard tabs.'));

Check warning on line 829 in superset-frontend/src/features/alerts/AlertReportModal.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/features/alerts/AlertReportModal.tsx#L829

Added line #L829 was not covered by tests
});
}
}, [dashboard, tabsEnabled, currentAlert?.extra, addDangerToast]);

Check warning on line 833 in superset-frontend/src/features/alerts/AlertReportModal.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/features/alerts/AlertReportModal.tsx#L833

Added line #L833 was not covered by tests
const databaseLabel = currentAlert?.database && !currentAlert.database.label;
useEffect(() => {
// Find source if current alert has one set
Expand Down Expand Up @@ -887,6 +942,27 @@
endpoint: `/api/v1/chart/${chart.value}`,
}).then(response => setChartVizType(response.json.result.viz_type));

const updateEmailSubject = () => {

Check warning on line 945 in superset-frontend/src/features/alerts/AlertReportModal.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/features/alerts/AlertReportModal.tsx#L945

Added line #L945 was not covered by tests
const chartLabel = currentAlert?.chart?.label;
const dashboardLabel = currentAlert?.dashboard?.label;

Check warning on line 947 in superset-frontend/src/features/alerts/AlertReportModal.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/features/alerts/AlertReportModal.tsx#L947

Added line #L947 was not covered by tests
if (!currentAlert?.name) {
setEmailSubject('');
return;
}
switch (contentType) {
case ContentType.Chart:
setEmailSubject(`${currentAlert?.name}: ${chartLabel || ''}`);
break;

case ContentType.Dashboard:
setEmailSubject(`${currentAlert?.name}: ${dashboardLabel || ''}`);
break;

default:
setEmailSubject('');
}
};

// Handle input/textarea updates
const onInputChange = (
event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
Expand Down Expand Up @@ -939,6 +1015,10 @@
const onDashboardChange = (dashboard: SelectValue) => {
updateAlertState('dashboard', dashboard || undefined);
updateAlertState('chart', null);
if (tabsEnabled) {
setTabOptions([]);
updateAnchorState('');
}

Check warning on line 1021 in superset-frontend/src/features/alerts/AlertReportModal.tsx

View check run for this annotation

Codecov / codecov/patch

superset-frontend/src/features/alerts/AlertReportModal.tsx#L1021

Added line #L1021 was not covered by tests
};

const onChartChange = (chart: SelectValue) => {
Expand Down Expand Up @@ -1028,8 +1108,8 @@
const errors = [];
if (
!(
(contentType === 'dashboard' && !!currentAlert?.dashboard) ||
(contentType === 'chart' && !!currentAlert?.chart)
(contentType === ContentType.Dashboard && !!currentAlert?.dashboard) ||
(contentType === ContentType.Chart && !!currentAlert?.chart)
)
) {
errors.push(TRANSLATIONS.CONTENT_ERROR_TEXT);
Expand Down Expand Up @@ -1162,7 +1242,9 @@
? 'hidden'
: 'active',
);
setContentType(resource.chart ? 'chart' : 'dashboard');
setContentType(
resource.chart ? ContentType.Chart : ContentType.Dashboard,
);
setReportFormat(resource.report_format || DEFAULT_NOTIFICATION_FORMAT);
const validatorConfig =
typeof resource.validator_config_json === 'string'
Expand Down Expand Up @@ -1277,28 +1359,6 @@
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);
};
Expand Down Expand Up @@ -1542,7 +1602,7 @@
/>
</StyledInputContainer>
<StyledInputContainer>
{contentType === 'chart' ? (
{contentType === ContentType.Chart ? (
<>
<div className="control-label">
{t('Select chart')}
Expand Down Expand Up @@ -1605,7 +1665,7 @@
onChange={onFormatChange}
value={reportFormat}
options={
contentType === 'dashboard'
contentType === ContentType.Dashboard
? ['pdf', 'png'].map(key => FORMAT_OPTIONS[key])
: /* If chart is of text based viz type: show text
format option */
Expand All @@ -1618,9 +1678,25 @@
</>
)}
</StyledInputContainer>
{tabsEnabled && contentType === ContentType.Dashboard && (
<StyledInputContainer>
<>
<div className="control-label">{t('Select tab')}</div>
<StyledTreeSelect
disabled={tabOptions?.length === 0}
treeData={tabOptions}
value={currentAlert?.extra?.dashboard?.anchor}
onSelect={updateAnchorState}
placeholder={t('Select a tab')}
/>
</>
</StyledInputContainer>
)}
{isScreenshot && (
<StyledInputContainer
css={!isReport && contentType === 'chart' && noMarginBottom}
css={
!isReport && contentType === ContentType.Chart && noMarginBottom
}
>
<div className="control-label">{t('Screenshot width')}</div>
<div className="input-container">
Expand All @@ -1636,7 +1712,7 @@
</div>
</StyledInputContainer>
)}
{(isReport || contentType === 'dashboard') && (
{(isReport || contentType === ContentType.Dashboard) && (
<div className="inline-container">
<StyledCheckbox
data-test="bypass-cache"
Expand Down
Loading
Loading