mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
fix: Explore misleading save action (#24862)
This commit is contained in:
parent
f7e76d02b7
commit
bf1b1a4c46
@ -49,11 +49,6 @@ export function saveSliceSuccess(data) {
|
|||||||
return { type: SAVE_SLICE_SUCCESS, data };
|
return { type: SAVE_SLICE_SUCCESS, data };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const REMOVE_SAVE_MODAL_ALERT = 'REMOVE_SAVE_MODAL_ALERT';
|
|
||||||
export function removeSaveModalAlert() {
|
|
||||||
return { type: REMOVE_SAVE_MODAL_ALERT };
|
|
||||||
}
|
|
||||||
|
|
||||||
const extractAddHocFiltersFromFormData = formDataToHandle =>
|
const extractAddHocFiltersFromFormData = formDataToHandle =>
|
||||||
Object.entries(formDataToHandle).reduce(
|
Object.entries(formDataToHandle).reduce(
|
||||||
(acc, [key, value]) =>
|
(acc, [key, value]) =>
|
||||||
|
@ -24,7 +24,6 @@ import { bindActionCreators } from 'redux';
|
|||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { Radio } from 'src/components/Radio';
|
import { Radio } from 'src/components/Radio';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import sinon from 'sinon';
|
|
||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
import * as saveModalActions from 'src/explore/actions/saveModalActions';
|
import * as saveModalActions from 'src/explore/actions/saveModalActions';
|
||||||
@ -131,7 +130,7 @@ test('renders a Modal with the right set of components', () => {
|
|||||||
expect(footerWrapper.find(Button)).toHaveLength(3);
|
expect(footerWrapper.find(Button)).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders the right footer buttons when existing dashboard selected', () => {
|
test('renders the right footer buttons', () => {
|
||||||
const wrapper = getWrapper();
|
const wrapper = getWrapper();
|
||||||
const footerWrapper = shallow(wrapper.find(StyledModal).props().footer);
|
const footerWrapper = shallow(wrapper.find(StyledModal).props().footer);
|
||||||
const saveAndGoDash = footerWrapper
|
const saveAndGoDash = footerWrapper
|
||||||
@ -142,18 +141,43 @@ test('renders the right footer buttons when existing dashboard selected', () =>
|
|||||||
expect(saveAndGoDash.props.children).toBe('Save & go to dashboard');
|
expect(saveAndGoDash.props.children).toBe('Save & go to dashboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders the right footer buttons when new dashboard selected', () => {
|
test('does not render a message when overriding', () => {
|
||||||
|
const wrapper = getWrapper();
|
||||||
|
wrapper.setState({
|
||||||
|
action: 'overwrite',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
wrapper.find('[message="A new chart will be created."]'),
|
||||||
|
).not.toExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders a message when saving as', () => {
|
||||||
|
const wrapper = getWrapper();
|
||||||
|
wrapper.setState({
|
||||||
|
action: 'saveas',
|
||||||
|
});
|
||||||
|
expect(wrapper.find('[message="A new chart will be created."]')).toExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders a message when a new dashboard is selected', () => {
|
||||||
const wrapper = getWrapper();
|
const wrapper = getWrapper();
|
||||||
wrapper.setState({
|
wrapper.setState({
|
||||||
dashboard: { label: 'Test new dashboard', value: 'Test new dashboard' },
|
dashboard: { label: 'Test new dashboard', value: 'Test new dashboard' },
|
||||||
});
|
});
|
||||||
const footerWrapper = shallow(wrapper.find(StyledModal).props().footer);
|
expect(
|
||||||
const saveAndGoDash = footerWrapper
|
wrapper.find('[message="A new dashboard will be created."]'),
|
||||||
.find('#btn_modal_save_goto_dash')
|
).toExist();
|
||||||
.getElement();
|
});
|
||||||
const save = footerWrapper.find('#btn_modal_save').getElement();
|
|
||||||
expect(save.props.children).toBe('Save to new dashboard');
|
test('renders a message when saving as with new dashboard', () => {
|
||||||
expect(saveAndGoDash.props.children).toBe('Save & go to new dashboard');
|
const wrapper = getWrapper();
|
||||||
|
wrapper.setState({
|
||||||
|
action: 'saveas',
|
||||||
|
dashboard: { label: 'Test new dashboard', value: 'Test new dashboard' },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
wrapper.find('[message="A new chart and dashboard will be created."]'),
|
||||||
|
).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('disables overwrite option for new slice', () => {
|
test('disables overwrite option for new slice', () => {
|
||||||
@ -197,17 +221,6 @@ test('updates slice name and selected dashboard', () => {
|
|||||||
expect(wrapper.state().dashboard.value).toBe(dashboardId);
|
expect(wrapper.state().dashboard.value).toBe(dashboardId);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('removes alert', () => {
|
|
||||||
sinon.spy(defaultProps.actions, 'removeSaveModalAlert');
|
|
||||||
const wrapper = getWrapper();
|
|
||||||
wrapper.setProps({ alert: 'old alert' });
|
|
||||||
|
|
||||||
wrapper.instance().removeAlert();
|
|
||||||
expect(defaultProps.actions.removeSaveModalAlert.callCount).toBe(1);
|
|
||||||
expect(wrapper.state().alert).toBeNull();
|
|
||||||
defaultProps.actions.removeSaveModalAlert.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('set dataset name when chart source is query', () => {
|
test('set dataset name when chart source is query', () => {
|
||||||
const wrapper = getWrapper(queryDefaultProps, queryStore);
|
const wrapper = getWrapper(queryDefaultProps, queryStore);
|
||||||
expect(wrapper.find('[data-test="new-dataset-name"]')).toExist();
|
expect(wrapper.find('[data-test="new-dataset-name"]')).toExist();
|
||||||
|
@ -67,7 +67,6 @@ interface SaveModalProps extends RouteComponentProps {
|
|||||||
type SaveModalState = {
|
type SaveModalState = {
|
||||||
newSliceName?: string;
|
newSliceName?: string;
|
||||||
datasetName: string;
|
datasetName: string;
|
||||||
alert: string | null;
|
|
||||||
action: SaveActionType;
|
action: SaveActionType;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
saveStatus?: string | null;
|
saveStatus?: string | null;
|
||||||
@ -92,7 +91,6 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
|
|||||||
this.state = {
|
this.state = {
|
||||||
newSliceName: props.sliceName,
|
newSliceName: props.sliceName,
|
||||||
datasetName: props.datasource?.name,
|
datasetName: props.datasource?.name,
|
||||||
alert: null,
|
|
||||||
action: this.canOverwriteSlice() ? 'overwrite' : 'saveas',
|
action: this.canOverwriteSlice() ? 'overwrite' : 'saveas',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
vizType: props.form_data?.viz_type,
|
vizType: props.form_data?.viz_type,
|
||||||
@ -103,7 +101,6 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
|
|||||||
this.changeAction = this.changeAction.bind(this);
|
this.changeAction = this.changeAction.bind(this);
|
||||||
this.saveOrOverwrite = this.saveOrOverwrite.bind(this);
|
this.saveOrOverwrite = this.saveOrOverwrite.bind(this);
|
||||||
this.isNewDashboard = this.isNewDashboard.bind(this);
|
this.isNewDashboard = this.isNewDashboard.bind(this);
|
||||||
this.removeAlert = this.removeAlert.bind(this);
|
|
||||||
this.onHide = this.onHide.bind(this);
|
this.onHide = this.onHide.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,8 +160,7 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveOrOverwrite(gotodash: boolean) {
|
async saveOrOverwrite(gotodash: boolean) {
|
||||||
this.setState({ alert: null, isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
this.props.actions.removeSaveModalAlert();
|
|
||||||
|
|
||||||
// Create or retrieve dashboard
|
// Create or retrieve dashboard
|
||||||
type DashboardGetResponse = {
|
type DashboardGetResponse = {
|
||||||
@ -324,89 +320,115 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
renderSaveChartModal = () => (
|
renderSaveChartModal = () => {
|
||||||
<Form data-test="save-modal-body" layout="vertical">
|
const info = this.info();
|
||||||
{(this.state.alert || this.props.alert) && (
|
return (
|
||||||
<Alert
|
<Form data-test="save-modal-body" layout="vertical">
|
||||||
type="warning"
|
<FormItem data-test="radio-group">
|
||||||
message={this.state.alert || this.props.alert}
|
<Radio
|
||||||
onClose={this.removeAlert}
|
id="overwrite-radio"
|
||||||
/>
|
disabled={!this.canOverwriteSlice()}
|
||||||
)}
|
checked={this.state.action === 'overwrite'}
|
||||||
<FormItem data-test="radio-group">
|
onChange={() => this.changeAction('overwrite')}
|
||||||
<Radio
|
data-test="save-overwrite-radio"
|
||||||
id="overwrite-radio"
|
>
|
||||||
disabled={!this.canOverwriteSlice()}
|
{t('Save (Overwrite)')}
|
||||||
checked={this.state.action === 'overwrite'}
|
</Radio>
|
||||||
onChange={() => this.changeAction('overwrite')}
|
<Radio
|
||||||
data-test="save-overwrite-radio"
|
id="saveas-radio"
|
||||||
>
|
data-test="saveas-radio"
|
||||||
{t('Save (Overwrite)')}
|
checked={this.state.action === 'saveas'}
|
||||||
</Radio>
|
onChange={() => this.changeAction('saveas')}
|
||||||
<Radio
|
>
|
||||||
id="saveas-radio"
|
{t('Save as...')}
|
||||||
data-test="saveas-radio"
|
</Radio>
|
||||||
checked={this.state.action === 'saveas'}
|
</FormItem>
|
||||||
onChange={() => this.changeAction('saveas')}
|
<hr />
|
||||||
>
|
<FormItem label={t('Chart name')} required>
|
||||||
{t('Save as...')}
|
|
||||||
</Radio>
|
|
||||||
</FormItem>
|
|
||||||
<hr />
|
|
||||||
<FormItem label={t('Chart name')} required>
|
|
||||||
<Input
|
|
||||||
name="new_slice_name"
|
|
||||||
type="text"
|
|
||||||
placeholder="Name"
|
|
||||||
value={this.state.newSliceName}
|
|
||||||
onChange={this.onSliceNameChange}
|
|
||||||
data-test="new-chart-name"
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
{this.props.datasource?.type === 'query' && (
|
|
||||||
<FormItem label={t('Dataset Name')} required>
|
|
||||||
<InfoTooltipWithTrigger
|
|
||||||
tooltip={t('A reusable dataset will be saved with your chart.')}
|
|
||||||
placement="right"
|
|
||||||
/>
|
|
||||||
<Input
|
<Input
|
||||||
name="dataset_name"
|
name="new_slice_name"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Dataset Name"
|
placeholder="Name"
|
||||||
value={this.state.datasetName}
|
value={this.state.newSliceName}
|
||||||
onChange={this.handleDatasetNameChange}
|
onChange={this.onSliceNameChange}
|
||||||
data-test="new-dataset-name"
|
data-test="new-chart-name"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
{this.props.datasource?.type === 'query' && (
|
||||||
{!(
|
<FormItem label={t('Dataset Name')} required>
|
||||||
isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) &&
|
<InfoTooltipWithTrigger
|
||||||
this.state.vizType === 'filter_box'
|
tooltip={t('A reusable dataset will be saved with your chart.')}
|
||||||
) && (
|
placement="right"
|
||||||
<FormItem
|
/>
|
||||||
label={t('Add to dashboard')}
|
<Input
|
||||||
data-test="save-chart-modal-select-dashboard-form"
|
name="dataset_name"
|
||||||
>
|
type="text"
|
||||||
<AsyncSelect
|
placeholder="Dataset Name"
|
||||||
allowClear
|
value={this.state.datasetName}
|
||||||
allowNewOptions
|
onChange={this.handleDatasetNameChange}
|
||||||
ariaLabel={t('Select a dashboard')}
|
data-test="new-dataset-name"
|
||||||
options={this.loadDashboards}
|
/>
|
||||||
onChange={this.onDashboardChange}
|
</FormItem>
|
||||||
value={this.state.dashboard}
|
)}
|
||||||
placeholder={
|
{!(
|
||||||
<div>
|
isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) &&
|
||||||
<b>{t('Select')}</b>
|
this.state.vizType === 'filter_box'
|
||||||
{t(' a dashboard OR ')}
|
) && (
|
||||||
<b>{t('create')}</b>
|
<FormItem
|
||||||
{t(' a new one')}
|
label={t('Add to dashboard')}
|
||||||
</div>
|
data-test="save-chart-modal-select-dashboard-form"
|
||||||
}
|
>
|
||||||
|
<AsyncSelect
|
||||||
|
allowClear
|
||||||
|
allowNewOptions
|
||||||
|
ariaLabel={t('Select a dashboard')}
|
||||||
|
options={this.loadDashboards}
|
||||||
|
onChange={this.onDashboardChange}
|
||||||
|
value={this.state.dashboard}
|
||||||
|
placeholder={
|
||||||
|
<div>
|
||||||
|
<b>{t('Select')}</b>
|
||||||
|
{t(' a dashboard OR ')}
|
||||||
|
<b>{t('create')}</b>
|
||||||
|
{t(' a new one')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
{info && <Alert type="info" message={info} closable={false} />}
|
||||||
|
{this.props.alert && (
|
||||||
|
<Alert
|
||||||
|
css={{ marginTop: info ? 16 : undefined }}
|
||||||
|
type="warning"
|
||||||
|
message={this.props.alert}
|
||||||
|
closable={false}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
)}
|
||||||
)}
|
</Form>
|
||||||
</Form>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
|
info = () => {
|
||||||
|
const isNewDashboard = this.isNewDashboard();
|
||||||
|
let chartWillBeCreated = false;
|
||||||
|
if (
|
||||||
|
this.props.slice &&
|
||||||
|
(this.state.action !== 'overwrite' || !this.canOverwriteSlice())
|
||||||
|
) {
|
||||||
|
chartWillBeCreated = true;
|
||||||
|
}
|
||||||
|
if (chartWillBeCreated && isNewDashboard) {
|
||||||
|
return t('A new chart and dashboard will be created.');
|
||||||
|
}
|
||||||
|
if (chartWillBeCreated) {
|
||||||
|
return t('A new chart will be created.');
|
||||||
|
}
|
||||||
|
if (isNewDashboard) {
|
||||||
|
return t('A new dashboard will be created.');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
renderFooter = () => (
|
renderFooter = () => (
|
||||||
<div data-test="save-modal-footer">
|
<div data-test="save-modal-footer">
|
||||||
@ -426,9 +448,7 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
|
|||||||
}
|
}
|
||||||
onClick={() => this.saveOrOverwrite(true)}
|
onClick={() => this.saveOrOverwrite(true)}
|
||||||
>
|
>
|
||||||
{this.isNewDashboard()
|
{t('Save & go to dashboard')}
|
||||||
? t('Save & go to new dashboard')
|
|
||||||
: t('Save & go to dashboard')}
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
id="btn_modal_save"
|
id="btn_modal_save"
|
||||||
@ -443,22 +463,11 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
|
|||||||
}
|
}
|
||||||
data-test="btn-modal-save"
|
data-test="btn-modal-save"
|
||||||
>
|
>
|
||||||
{!this.canOverwriteSlice() && this.props.slice
|
{t('Save')}
|
||||||
? t('Save as new chart')
|
|
||||||
: this.isNewDashboard()
|
|
||||||
? t('Save to new dashboard')
|
|
||||||
: t('Save')}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
removeAlert() {
|
|
||||||
if (this.props.alert) {
|
|
||||||
this.props.actions.removeSaveModalAlert();
|
|
||||||
}
|
|
||||||
this.setState({ alert: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<StyledModal
|
<StyledModal
|
||||||
|
@ -40,9 +40,6 @@ export default function saveModalReducer(state = {}, action) {
|
|||||||
[actions.SAVE_SLICE_SUCCESS](data) {
|
[actions.SAVE_SLICE_SUCCESS](data) {
|
||||||
return { ...state, data };
|
return { ...state, data };
|
||||||
},
|
},
|
||||||
[actions.REMOVE_SAVE_MODAL_ALERT]() {
|
|
||||||
return { ...state, saveModalAlert: null };
|
|
||||||
},
|
|
||||||
[HYDRATE_EXPLORE]() {
|
[HYDRATE_EXPLORE]() {
|
||||||
return { ...action.data.saveModal };
|
return { ...action.data.saveModal };
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user