diff --git a/superset-frontend/src/dashboard/components/RefreshIntervalModal.test.tsx b/superset-frontend/src/dashboard/components/RefreshIntervalModal.test.tsx index 17c08e0701f80..961c9cacb5c54 100644 --- a/superset-frontend/src/dashboard/components/RefreshIntervalModal.test.tsx +++ b/superset-frontend/src/dashboard/components/RefreshIntervalModal.test.tsx @@ -171,6 +171,23 @@ test('should change selected value', async () => { expect(selectedValue.title).not.toMatch(/don't refresh/i); }); +test('should change selected value to custom value', async () => { + render(setup(editModeOnProps)); + await openRefreshIntervalModal(); + + // Initial selected value should be "Don't refresh" + const selectedValue = screen.getByText(/don't refresh/i); + expect(selectedValue.title).toMatch(/don't refresh/i); + + // Display options and select "Custom interval" + await displayOptions(); + userEvent.click(screen.getByText(/Custom interval/i)); + + // Selected value should now be "Custom interval" + expect(selectedValue.title).toMatch(/Custom interval/i); + expect(selectedValue.title).not.toMatch(/don't refresh/i); +}); + test('should save a newly-selected value', async () => { render(setup(editModeOnProps)); await openRefreshIntervalModal(); diff --git a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx index 6299b09c539a6..b20c8484a6355 100644 --- a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx +++ b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx @@ -21,6 +21,7 @@ import Select from 'src/components/Select/Select'; import { t, styled } from '@superset-ui/core'; import Alert from 'src/components/Alert'; import Button from 'src/components/Button'; +import { Input } from 'src/components/Input'; import ModalTrigger, { ModalTriggerRef } from 'src/components/ModalTrigger'; import { FormLabel } from 'src/components/Form'; @@ -36,6 +37,16 @@ const RefreshWarningContainer = styled.div` margin-top: ${({ theme }) => theme.gridUnit * 6}px; `; +const StyledDiv = styled.div` + display: flex; + margin-top: ${({ theme }) => theme.gridUnit * 3}px; +`; + +const InnerStyledDiv = styled.div` + width: 30%; + margin: auto; +`; + type RefreshIntervalModalProps = { addSuccessToast: (msg: string) => void; triggerNode: JSX.Element; @@ -49,6 +60,10 @@ type RefreshIntervalModalProps = { type RefreshIntervalModalState = { refreshFrequency: number; + custom_hour: number; + custom_min: number; + custom_sec: number; + custom_block: boolean; }; class RefreshIntervalModal extends React.PureComponent< @@ -67,6 +82,10 @@ class RefreshIntervalModal extends React.PureComponent< this.modalRef = React.createRef() as ModalTriggerRef; this.state = { refreshFrequency: props.refreshFrequency, + custom_hour: 0, + custom_min: 0, + custom_sec: 0, + custom_block: false, }; this.handleFrequencyChange = this.handleFrequencyChange.bind(this); this.onSave = this.onSave.bind(this); @@ -91,6 +110,85 @@ class RefreshIntervalModal extends React.PureComponent< this.setState({ refreshFrequency: value || refreshIntervalOptions[0][0], }); + + this.setState({ + custom_block: value === -1, + }); + + if (value === -1) { + this.setState({ + custom_hour: 0, + custom_min: 0, + custom_sec: 0, + }); + } + } + + onSaveValue(value: number) { + this.props.onChange(value, this.props.editMode); + this.modalRef?.current?.close(); + this.props.addSuccessToast(t('Refresh interval saved')); + } + + createIntervalOptions(refreshIntervalOptions: [number, string][]) { + const refresh_options = []; + + refresh_options.push({ value: -1, label: t('Custom interval') }); + refresh_options.push( + ...refreshIntervalOptions.map(option => ({ + value: option[0], + label: t(option[1]), + })), + ); + + return refresh_options; + } + + min_sec_options(min_or_sec: string) { + return Array.from({ length: 60 }, (_, i) => ({ + value: i, + label: `${i} ${min_or_sec}`, + })); + } + + refresh_custom_val( + custom_block: boolean, + custom_hour: number, + custom_min: number, + custom_sec: number, + ) { + if (custom_block === true) { + // Get hour value + const hour_value = custom_hour; + + // Get minutes value + const minute_value = custom_min; + + // Get seconds value + const second_value = custom_sec; + + if ( + hour_value < 0 || + minute_value < 0 || + second_value < 0 || + minute_value >= 60 || + second_value >= 60 + ) { + this.props.addSuccessToast( + t( + 'Put positive values and valid minute and second value less than 60', + ), + ); + } + // Convert given input to seconds + const value = hour_value * 60 * 60 + minute_value * 60 + second_value; + if (value === 0) { + this.props.addSuccessToast(t('Put some positive value greater than 0')); + return; + } + this.handleFrequencyChange(value); + this.onSaveValue(value); + } else this.onSave(); } render() { @@ -100,7 +198,13 @@ class RefreshIntervalModal extends React.PureComponent< editMode, refreshIntervalOptions, } = this.props; - const { refreshFrequency = 0 } = this.state; + const { + refreshFrequency = 0, + custom_hour = 0, + custom_min = 0, + custom_sec = 0, + custom_block = false, + } = this.state; const showRefreshWarning = !!refreshFrequency && !!refreshWarning && refreshFrequency < refreshLimit; @@ -111,17 +215,74 @@ class RefreshIntervalModal extends React.PureComponent< modalTitle={t('Refresh interval')} modalBody={