feat: Allow users to bust cache in report dashboard + alerts charts + alert dashboards (#18795)
* wip * add force cahce bypass option to alerts * remove default for alerts to bypass cache * save for now * save for now * fix * commenting out for now * fix linting * remove link * add back force id test * add frontend test * address
This commit is contained in:
parent
329855170e
commit
8c52fe3476
|
|
@ -27,6 +27,8 @@ import Loading from 'src/components/Loading';
|
||||||
import { EmptyStateBig } from 'src/components/EmptyState';
|
import { EmptyStateBig } from 'src/components/EmptyState';
|
||||||
import ErrorBoundary from 'src/components/ErrorBoundary';
|
import ErrorBoundary from 'src/components/ErrorBoundary';
|
||||||
import { Logger, LOG_ACTIONS_RENDER_CHART } from 'src/logger/LogUtils';
|
import { Logger, LOG_ACTIONS_RENDER_CHART } from 'src/logger/LogUtils';
|
||||||
|
import { URL_PARAMS } from 'src/constants';
|
||||||
|
import { getUrlParam } from 'src/utils/urlUtils';
|
||||||
import ChartRenderer from './ChartRenderer';
|
import ChartRenderer from './ChartRenderer';
|
||||||
import { ChartErrorMessage } from './ChartErrorMessage';
|
import { ChartErrorMessage } from './ChartErrorMessage';
|
||||||
|
|
||||||
|
|
@ -157,7 +159,7 @@ class Chart extends React.PureComponent {
|
||||||
// Load saved chart with a GET request
|
// Load saved chart with a GET request
|
||||||
this.props.actions.getSavedChart(
|
this.props.actions.getSavedChart(
|
||||||
this.props.formData,
|
this.props.formData,
|
||||||
this.props.force,
|
this.props.force || getUrlParam(URL_PARAMS.force), // allow override via url params force=true
|
||||||
this.props.timeout,
|
this.props.timeout,
|
||||||
this.props.chartId,
|
this.props.chartId,
|
||||||
this.props.dashboardId,
|
this.props.dashboardId,
|
||||||
|
|
@ -167,7 +169,7 @@ class Chart extends React.PureComponent {
|
||||||
// Create chart with POST request
|
// Create chart with POST request
|
||||||
this.props.actions.postChartFormData(
|
this.props.actions.postChartFormData(
|
||||||
this.props.formData,
|
this.props.formData,
|
||||||
this.props.force,
|
this.props.force || getUrlParam(URL_PARAMS.force), // allow override via url params force=true
|
||||||
this.props.timeout,
|
this.props.timeout,
|
||||||
this.props.chartId,
|
this.props.chartId,
|
||||||
this.props.dashboardId,
|
this.props.dashboardId,
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,10 @@ export const URL_PARAMS = {
|
||||||
name: 'dataset_id',
|
name: 'dataset_id',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
|
force: {
|
||||||
|
name: 'force',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -340,4 +340,22 @@ describe('AlertReportModal', () => {
|
||||||
await waitForComponentToPaint(wrapper);
|
await waitForComponentToPaint(wrapper);
|
||||||
expect(wrapper.find('textarea[name="recipients"]')).toHaveLength(1);
|
expect(wrapper.find('textarea[name="recipients"]')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders bypass cache checkbox', async () => {
|
||||||
|
const bypass = wrapper.find('[data-test="bypass-cache"]');
|
||||||
|
expect(bypass).toExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders no bypass cache checkbox when alert', async () => {
|
||||||
|
const props = {
|
||||||
|
...mockedProps,
|
||||||
|
alert: mockData,
|
||||||
|
isReport: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const alertWrapper = await mountAndWait(props);
|
||||||
|
|
||||||
|
const bypass = alertWrapper.find('[data-test="bypass-cache"]');
|
||||||
|
expect(bypass).not.toExist();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -940,7 +940,6 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||||
(!currentAlert || currentAlert.id || (isHidden && show))
|
(!currentAlert || currentAlert.id || (isHidden && show))
|
||||||
) {
|
) {
|
||||||
setCurrentAlert({ ...DEFAULT_ALERT });
|
setCurrentAlert({ ...DEFAULT_ALERT });
|
||||||
setForceScreenshot(contentType === 'chart' && !isReport);
|
|
||||||
setNotificationSettings([]);
|
setNotificationSettings([]);
|
||||||
setNotificationAddState('active');
|
setNotificationAddState('active');
|
||||||
}
|
}
|
||||||
|
|
@ -1369,19 +1368,20 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
||||||
)}
|
)}
|
||||||
</StyledRadioGroup>
|
</StyledRadioGroup>
|
||||||
</div>
|
</div>
|
||||||
{isReport && (
|
|
||||||
<div className="inline-container">
|
|
||||||
<StyledCheckbox
|
|
||||||
className="checkbox"
|
|
||||||
checked={forceScreenshot}
|
|
||||||
onChange={onForceScreenshotChange}
|
|
||||||
>
|
|
||||||
Ignore cache when generating screenshot
|
|
||||||
</StyledCheckbox>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{(isReport || contentType === 'dashboard') && (
|
||||||
|
<div className="inline-container">
|
||||||
|
<StyledCheckbox
|
||||||
|
data-test="bypass-cache"
|
||||||
|
className="checkbox"
|
||||||
|
checked={forceScreenshot}
|
||||||
|
onChange={onForceScreenshotChange}
|
||||||
|
>
|
||||||
|
Ignore cache when generating screenshot
|
||||||
|
</StyledCheckbox>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<StyledSectionTitle>
|
<StyledSectionTitle>
|
||||||
<h4>{t('Notification method')}</h4>
|
<h4>{t('Notification method')}</h4>
|
||||||
<span className="required">*</span>
|
<span className="required">*</span>
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,6 @@ class BaseReportState:
|
||||||
Get the url for this report schedule: chart or dashboard
|
Get the url for this report schedule: chart or dashboard
|
||||||
"""
|
"""
|
||||||
force = "true" if self._report_schedule.force_screenshot else "false"
|
force = "true" if self._report_schedule.force_screenshot else "false"
|
||||||
|
|
||||||
if self._report_schedule.chart:
|
if self._report_schedule.chart:
|
||||||
if result_format in {
|
if result_format in {
|
||||||
ChartDataResultFormat.CSV,
|
ChartDataResultFormat.CSV,
|
||||||
|
|
@ -173,7 +172,7 @@ class BaseReportState:
|
||||||
user_friendly=user_friendly,
|
user_friendly=user_friendly,
|
||||||
dashboard_id_or_slug=self._report_schedule.dashboard_id,
|
dashboard_id_or_slug=self._report_schedule.dashboard_id,
|
||||||
standalone=DashboardStandaloneMode.REPORT.value,
|
standalone=DashboardStandaloneMode.REPORT.value,
|
||||||
# force=force, TODO (betodealmeida) implement this
|
force=force,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,18 @@ def create_report_email_dashboard():
|
||||||
cleanup_report_schedule(report_schedule)
|
cleanup_report_schedule(report_schedule)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def create_report_email_dashboard_force_screenshot():
|
||||||
|
with app.app_context():
|
||||||
|
dashboard = db.session.query(Dashboard).first()
|
||||||
|
report_schedule = create_report_notification(
|
||||||
|
email_target="target@email.com", dashboard=dashboard, force_screenshot=True
|
||||||
|
)
|
||||||
|
yield report_schedule
|
||||||
|
|
||||||
|
cleanup_report_schedule(report_schedule)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def create_report_email_tabbed_dashboard(tabbed_dashboard):
|
def create_report_email_tabbed_dashboard(tabbed_dashboard):
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
|
@ -1002,6 +1014,41 @@ def test_email_dashboard_report_schedule(
|
||||||
assert_log(ReportState.SUCCESS)
|
assert_log(ReportState.SUCCESS)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures(
|
||||||
|
"load_birth_names_dashboard_with_slices",
|
||||||
|
"create_report_email_dashboard_force_screenshot",
|
||||||
|
)
|
||||||
|
@patch("superset.reports.notifications.email.send_email_smtp")
|
||||||
|
@patch("superset.utils.screenshots.DashboardScreenshot.get_screenshot")
|
||||||
|
def test_email_dashboard_report_schedule_force_screenshot(
|
||||||
|
screenshot_mock, email_mock, create_report_email_dashboard_force_screenshot
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
ExecuteReport Command: Test dashboard email report schedule
|
||||||
|
"""
|
||||||
|
# setup screenshot mock
|
||||||
|
screenshot_mock.return_value = SCREENSHOT_FILE
|
||||||
|
|
||||||
|
with freeze_time("2020-01-01T00:00:00Z"):
|
||||||
|
AsyncExecuteReportScheduleCommand(
|
||||||
|
TEST_ID,
|
||||||
|
create_report_email_dashboard_force_screenshot.id,
|
||||||
|
datetime.utcnow(),
|
||||||
|
).run()
|
||||||
|
|
||||||
|
notification_targets = get_target_from_report_schedule(
|
||||||
|
create_report_email_dashboard_force_screenshot
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert the email smtp address
|
||||||
|
assert email_mock.call_args[0][0] == notification_targets[0]
|
||||||
|
# Assert the email inline screenshot
|
||||||
|
smtp_images = email_mock.call_args[1]["images"]
|
||||||
|
assert smtp_images[list(smtp_images.keys())[0]] == SCREENSHOT_FILE
|
||||||
|
# Assert logs are correct
|
||||||
|
assert_log(ReportState.SUCCESS)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(
|
@pytest.mark.usefixtures(
|
||||||
"load_birth_names_dashboard_with_slices", "create_report_slack_chart"
|
"load_birth_names_dashboard_with_slices", "create_report_slack_chart"
|
||||||
)
|
)
|
||||||
|
|
@ -1772,7 +1819,7 @@ def test_when_tabs_are_selected_it_takes_screenshots_for_every_tabs(
|
||||||
assert dashboard_screenshot_mock.call_count == 2
|
assert dashboard_screenshot_mock.call_count == 2
|
||||||
for index, tab in enumerate(tabs):
|
for index, tab in enumerate(tabs):
|
||||||
assert dashboard_screenshot_mock.call_args_list[index].args == (
|
assert dashboard_screenshot_mock.call_args_list[index].args == (
|
||||||
f"http://0.0.0.0:8080/superset/dashboard/{dashboard.id}/?standalone=3#{tab}",
|
f"http://0.0.0.0:8080/superset/dashboard/{dashboard.id}/?standalone=3&force=false#{tab}",
|
||||||
f"{dashboard.digest}",
|
f"{dashboard.digest}",
|
||||||
)
|
)
|
||||||
assert send_email_smtp_mock.called is True
|
assert send_email_smtp_mock.called is True
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue