refactor: Migrate saveModalActions to TypeScript (#28046)
This commit is contained in:
parent
b160fd40c8
commit
920f2f437e
|
|
@ -1,259 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import rison from 'rison';
|
||||
import { SupersetClient, t } from '@superset-ui/core';
|
||||
import { addSuccessToast } from 'src/components/MessageToasts/actions';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { buildV1ChartDataPayload } from '../exploreUtils';
|
||||
import { Operators } from '../constants';
|
||||
|
||||
const ADHOC_FILTER_REGEX = /^adhoc_filters/;
|
||||
|
||||
export const FETCH_DASHBOARDS_SUCCEEDED = 'FETCH_DASHBOARDS_SUCCEEDED';
|
||||
export function fetchDashboardsSucceeded(choices) {
|
||||
return { type: FETCH_DASHBOARDS_SUCCEEDED, choices };
|
||||
}
|
||||
|
||||
export const FETCH_DASHBOARDS_FAILED = 'FETCH_DASHBOARDS_FAILED';
|
||||
export function fetchDashboardsFailed(userId) {
|
||||
return { type: FETCH_DASHBOARDS_FAILED, userId };
|
||||
}
|
||||
|
||||
export const SET_SAVE_CHART_MODAL_VISIBILITY =
|
||||
'SET_SAVE_CHART_MODAL_VISIBILITY';
|
||||
export function setSaveChartModalVisibility(isVisible) {
|
||||
return { type: SET_SAVE_CHART_MODAL_VISIBILITY, isVisible };
|
||||
}
|
||||
|
||||
export const SAVE_SLICE_FAILED = 'SAVE_SLICE_FAILED';
|
||||
export function saveSliceFailed() {
|
||||
return { type: SAVE_SLICE_FAILED };
|
||||
}
|
||||
export const SAVE_SLICE_SUCCESS = 'SAVE_SLICE_SUCCESS';
|
||||
export function saveSliceSuccess(data) {
|
||||
return { type: SAVE_SLICE_SUCCESS, data };
|
||||
}
|
||||
|
||||
const extractAdhocFiltersFromFormData = formDataToHandle =>
|
||||
Object.entries(formDataToHandle).reduce(
|
||||
(acc, [key, value]) =>
|
||||
ADHOC_FILTER_REGEX.test(key)
|
||||
? { ...acc, [key]: value?.filter(f => !f.isExtra) }
|
||||
: acc,
|
||||
{},
|
||||
);
|
||||
|
||||
const hasTemporalRangeFilter = formData =>
|
||||
(formData?.adhoc_filters || []).some(
|
||||
filter => filter.operator === Operators.TemporalRange,
|
||||
);
|
||||
|
||||
export const getSlicePayload = (
|
||||
sliceName,
|
||||
formDataWithNativeFilters,
|
||||
dashboards,
|
||||
owners,
|
||||
formDataFromSlice = {},
|
||||
) => {
|
||||
const adhocFilters = extractAdhocFiltersFromFormData(
|
||||
formDataWithNativeFilters,
|
||||
);
|
||||
|
||||
// Retain adhoc_filters from the slice if no adhoc_filters are present
|
||||
// after overwriting a chart. This ensures the dashboard can continue
|
||||
// to filter the chart. Before, any time range filter applied in the dashboard
|
||||
// would end up as an extra filter and when overwriting the chart the original
|
||||
// time range adhoc_filter was lost
|
||||
if (!isEmpty(formDataFromSlice)) {
|
||||
Object.keys(adhocFilters || {}).forEach(adhocFilterKey => {
|
||||
if (isEmpty(adhocFilters[adhocFilterKey])) {
|
||||
formDataFromSlice?.[adhocFilterKey]?.forEach(filter => {
|
||||
if (filter.operator === Operators.TemporalRange && !filter.isExtra) {
|
||||
adhocFilters[adhocFilterKey].push({
|
||||
...filter,
|
||||
comparator: 'No filter',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This loop iterates through the adhoc_filters array in formDataWithNativeFilters.
|
||||
// If a filter is of type TEMPORAL_RANGE and isExtra, it sets its comparator to
|
||||
// 'No filter' and adds the modified filter to the adhocFilters array. This ensures that all
|
||||
// TEMPORAL_RANGE filters are converted to 'No filter' when saving a chart.
|
||||
if (!hasTemporalRangeFilter(adhocFilters)) {
|
||||
formDataWithNativeFilters?.adhoc_filters?.forEach(filter => {
|
||||
if (filter.operator === Operators.TemporalRange && filter.isExtra) {
|
||||
adhocFilters.adhoc_filters.push({ ...filter, comparator: 'No filter' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const formData = {
|
||||
...formDataWithNativeFilters,
|
||||
...adhocFilters,
|
||||
dashboards,
|
||||
};
|
||||
|
||||
const [datasourceId, datasourceType] = formData.datasource.split('__');
|
||||
const payload = {
|
||||
params: JSON.stringify(formData),
|
||||
slice_name: sliceName,
|
||||
viz_type: formData.viz_type,
|
||||
datasource_id: parseInt(datasourceId, 10),
|
||||
datasource_type: datasourceType,
|
||||
dashboards,
|
||||
owners,
|
||||
query_context: JSON.stringify(
|
||||
buildV1ChartDataPayload({
|
||||
formData,
|
||||
force: false,
|
||||
resultFormat: 'json',
|
||||
resultType: 'full',
|
||||
setDataMask: null,
|
||||
ownState: null,
|
||||
}),
|
||||
),
|
||||
};
|
||||
return payload;
|
||||
};
|
||||
|
||||
const addToasts = (isNewSlice, sliceName, addedToDashboard) => {
|
||||
const toasts = [];
|
||||
if (isNewSlice) {
|
||||
toasts.push(addSuccessToast(t('Chart [%s] has been saved', sliceName)));
|
||||
} else {
|
||||
toasts.push(
|
||||
addSuccessToast(t('Chart [%s] has been overwritten', sliceName)),
|
||||
);
|
||||
}
|
||||
|
||||
if (addedToDashboard) {
|
||||
if (addedToDashboard.new) {
|
||||
toasts.push(
|
||||
addSuccessToast(
|
||||
t(
|
||||
'Dashboard [%s] just got created and chart [%s] was added to it',
|
||||
addedToDashboard.title,
|
||||
sliceName,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
toasts.push(
|
||||
addSuccessToast(
|
||||
t(
|
||||
'Chart [%s] was added to dashboard [%s]',
|
||||
sliceName,
|
||||
addedToDashboard.title,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return toasts;
|
||||
};
|
||||
|
||||
// Update existing slice
|
||||
export const updateSlice =
|
||||
(slice, sliceName, dashboards, addedToDashboard) =>
|
||||
async (dispatch, getState) => {
|
||||
const { slice_id: sliceId, owners, form_data: formDataFromSlice } = slice;
|
||||
const {
|
||||
explore: {
|
||||
form_data: { url_params: _, ...formData },
|
||||
},
|
||||
} = getState();
|
||||
try {
|
||||
const response = await SupersetClient.put({
|
||||
endpoint: `/api/v1/chart/${sliceId}`,
|
||||
jsonPayload: getSlicePayload(
|
||||
sliceName,
|
||||
formData,
|
||||
dashboards,
|
||||
owners,
|
||||
formDataFromSlice,
|
||||
),
|
||||
});
|
||||
|
||||
dispatch(saveSliceSuccess());
|
||||
addToasts(false, sliceName, addedToDashboard).map(dispatch);
|
||||
return response.json;
|
||||
} catch (error) {
|
||||
dispatch(saveSliceFailed());
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Create new slice
|
||||
export const createSlice =
|
||||
(sliceName, dashboards, addedToDashboard) => async (dispatch, getState) => {
|
||||
const {
|
||||
explore: {
|
||||
form_data: { url_params: _, ...formData },
|
||||
},
|
||||
} = getState();
|
||||
try {
|
||||
const response = await SupersetClient.post({
|
||||
endpoint: `/api/v1/chart/`,
|
||||
jsonPayload: getSlicePayload(sliceName, formData, dashboards),
|
||||
});
|
||||
|
||||
dispatch(saveSliceSuccess());
|
||||
addToasts(true, sliceName, addedToDashboard).map(dispatch);
|
||||
return response.json;
|
||||
} catch (error) {
|
||||
dispatch(saveSliceFailed());
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Create new dashboard
|
||||
export const createDashboard = dashboardName => async dispatch => {
|
||||
try {
|
||||
const response = await SupersetClient.post({
|
||||
endpoint: `/api/v1/dashboard/`,
|
||||
jsonPayload: { dashboard_title: dashboardName },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
} catch (error) {
|
||||
dispatch(saveSliceFailed());
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Get dashboards the slice is added to
|
||||
export const getSliceDashboards = slice => async dispatch => {
|
||||
try {
|
||||
const response = await SupersetClient.get({
|
||||
endpoint: `/api/v1/chart/${slice.slice_id}?q=${rison.encode({
|
||||
columns: ['dashboards.id'],
|
||||
})}`,
|
||||
});
|
||||
|
||||
return response.json.result.dashboards.map(({ id }) => id);
|
||||
} catch (error) {
|
||||
dispatch(saveSliceFailed());
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -16,10 +16,11 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { Dispatch } from 'redux';
|
||||
import { ADD_TOAST } from 'src/components/MessageToasts/actions';
|
||||
import { DatasourceType } from '@superset-ui/core';
|
||||
import {
|
||||
createDashboard,
|
||||
createSlice,
|
||||
|
|
@ -28,24 +29,62 @@ import {
|
|||
SAVE_SLICE_SUCCESS,
|
||||
updateSlice,
|
||||
getSlicePayload,
|
||||
PayloadSlice,
|
||||
QueryFormData,
|
||||
} from './saveModalActions';
|
||||
|
||||
// Define test constants and mock data using imported types
|
||||
const sliceId = 10;
|
||||
const sliceName = 'New chart';
|
||||
const vizType = 'sample_viz_type';
|
||||
const datasourceId = 11;
|
||||
const datasourceType = 'sample_datasource_type';
|
||||
const datasourceId = 22;
|
||||
const datasourceType = DatasourceType.Table;
|
||||
const dashboards = [12, 13];
|
||||
const queryContext = { sampleKey: 'sampleValue' };
|
||||
const formData = {
|
||||
const owners = [0];
|
||||
|
||||
const formData: Partial<QueryFormData> = {
|
||||
viz_type: vizType,
|
||||
datasource: `${datasourceId}__${datasourceType}`,
|
||||
dashboards,
|
||||
};
|
||||
const mockExploreState = { explore: { form_data: formData } };
|
||||
|
||||
const sliceResponsePayload = {
|
||||
id: 10,
|
||||
const mockExploreState: Partial<QueryFormData> = {
|
||||
explore: {
|
||||
can_add: false,
|
||||
can_download: false,
|
||||
can_overwrite: false,
|
||||
isDatasourceMetaLoading: false,
|
||||
isStarred: false,
|
||||
triggerRender: false,
|
||||
datasource: `${datasourceId}__${datasourceType}`,
|
||||
verbose_map: { '': '' },
|
||||
main_dttm_col: '',
|
||||
datasource_name: null,
|
||||
description: null,
|
||||
},
|
||||
controls: {},
|
||||
form_data: {
|
||||
datasource: `${datasourceId}__${datasourceType}`,
|
||||
viz_type: '',
|
||||
},
|
||||
slice: {
|
||||
slice_id: 0,
|
||||
slice_name: '',
|
||||
description: null,
|
||||
cache_timeout: null,
|
||||
is_managed_externally: false,
|
||||
},
|
||||
controlsTransferred: [],
|
||||
standalone: false,
|
||||
force: false,
|
||||
common: {},
|
||||
};
|
||||
|
||||
const sliceResponsePayload: Partial<PayloadSlice> = {
|
||||
slice_id: sliceId,
|
||||
owners: [],
|
||||
form_data: formData,
|
||||
};
|
||||
|
||||
const sampleError = new Error('sampleError');
|
||||
|
|
@ -57,66 +96,124 @@ jest.mock('../exploreUtils', () => ({
|
|||
/**
|
||||
* Tests updateSlice action
|
||||
*/
|
||||
|
||||
const updateSliceEndpoint = `glob:*/api/v1/chart/${sliceId}`;
|
||||
test('updateSlice handles success', async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.put(updateSliceEndpoint, sliceResponsePayload);
|
||||
const dispatch = sinon.spy();
|
||||
const getState = sinon.spy(() => mockExploreState);
|
||||
const dispatchSpy = sinon.spy();
|
||||
const dispatch = (action: any) => {
|
||||
dispatchSpy(action);
|
||||
};
|
||||
const getState = () => mockExploreState;
|
||||
|
||||
const slice = await updateSlice(
|
||||
{ slice_id: sliceId },
|
||||
{
|
||||
slice_id: sliceId,
|
||||
owners: owners as [],
|
||||
form_data: formData,
|
||||
slice_name: '',
|
||||
description: '',
|
||||
description_markdown: '',
|
||||
slice_url: '',
|
||||
viz_type: '',
|
||||
thumbnail_url: '',
|
||||
changed_on: 0,
|
||||
changed_on_humanized: '',
|
||||
modified: '',
|
||||
datasource_id: 0,
|
||||
datasource_type: datasourceType,
|
||||
datasource_url: '',
|
||||
datasource_name: '',
|
||||
created_by: {
|
||||
id: 0,
|
||||
},
|
||||
},
|
||||
sliceName,
|
||||
[],
|
||||
)(dispatch, getState);
|
||||
|
||||
)(dispatch as Dispatch<any>, getState);
|
||||
expect(fetchMock.calls(updateSliceEndpoint)).toHaveLength(1);
|
||||
expect(dispatch.callCount).toBe(2);
|
||||
expect(dispatch.getCall(0).args[0].type).toBe(SAVE_SLICE_SUCCESS);
|
||||
expect(dispatch.getCall(1).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatch.getCall(1).args[0].payload.toastType).toBe('SUCCESS_TOAST');
|
||||
expect(dispatch.getCall(1).args[0].payload.text).toBe(
|
||||
expect(dispatchSpy.callCount).toBe(2);
|
||||
expect(dispatchSpy.getCall(0).args[0].type).toBe(SAVE_SLICE_SUCCESS);
|
||||
expect(dispatchSpy.getCall(1).args[0].type).toBe('ADD_TOAST');
|
||||
expect(dispatchSpy.getCall(1).args[0].payload.toastType).toBe(
|
||||
'SUCCESS_TOAST',
|
||||
);
|
||||
expect(dispatchSpy.getCall(1).args[0].payload.text).toBe(
|
||||
'Chart [New chart] has been overwritten',
|
||||
);
|
||||
|
||||
expect(slice).toEqual(sliceResponsePayload);
|
||||
});
|
||||
|
||||
test('updateSlice handles failure', async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.put(updateSliceEndpoint, { throws: sampleError });
|
||||
const dispatch = sinon.spy();
|
||||
const getState = sinon.spy(() => mockExploreState);
|
||||
|
||||
const dispatchSpy = sinon.spy();
|
||||
const dispatch = (action: any) => {
|
||||
dispatchSpy(action);
|
||||
};
|
||||
|
||||
const getState = () => mockExploreState;
|
||||
|
||||
let caughtError;
|
||||
try {
|
||||
await updateSlice({ slice_id: sliceId }, sliceName, [])(dispatch, getState);
|
||||
await updateSlice(
|
||||
{
|
||||
slice_id: sliceId,
|
||||
owners: [],
|
||||
form_data: formData,
|
||||
slice_name: '',
|
||||
description: '',
|
||||
description_markdown: '',
|
||||
slice_url: '',
|
||||
viz_type: '',
|
||||
thumbnail_url: '',
|
||||
changed_on: 0,
|
||||
changed_on_humanized: '',
|
||||
modified: '',
|
||||
datasource_id: 0,
|
||||
datasource_type: datasourceType,
|
||||
datasource_url: '',
|
||||
datasource_name: '',
|
||||
created_by: {
|
||||
id: 0,
|
||||
},
|
||||
},
|
||||
sliceName,
|
||||
[],
|
||||
)(dispatch as Dispatch<any>, getState);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
|
||||
expect(caughtError).toEqual(sampleError);
|
||||
expect(fetchMock.calls(updateSliceEndpoint)).toHaveLength(4);
|
||||
expect(dispatch.callCount).toBe(1);
|
||||
expect(dispatch.getCall(0).args[0].type).toBe(SAVE_SLICE_FAILED);
|
||||
expect(dispatchSpy.callCount).toBe(1);
|
||||
expect(dispatchSpy.getCall(0).args[0].type).toBe(SAVE_SLICE_FAILED);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests createSlice action
|
||||
*/
|
||||
|
||||
const createSliceEndpoint = `glob:*/api/v1/chart/`;
|
||||
test('createSlice handles success', async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.post(createSliceEndpoint, sliceResponsePayload);
|
||||
const dispatch = sinon.spy();
|
||||
const getState = sinon.spy(() => mockExploreState);
|
||||
const slice = await createSlice(sliceName, [])(dispatch, getState);
|
||||
const dispatchSpy = sinon.spy();
|
||||
const dispatch = (action: any) => dispatchSpy(action);
|
||||
const getState = () => mockExploreState;
|
||||
const slice: Partial<PayloadSlice> = await createSlice(sliceName, [])(
|
||||
dispatch as Dispatch,
|
||||
getState,
|
||||
);
|
||||
expect(fetchMock.calls(createSliceEndpoint)).toHaveLength(1);
|
||||
expect(dispatch.callCount).toBe(2);
|
||||
expect(dispatch.getCall(0).args[0].type).toBe(SAVE_SLICE_SUCCESS);
|
||||
expect(dispatch.getCall(1).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatch.getCall(1).args[0].payload.toastType).toBe('SUCCESS_TOAST');
|
||||
expect(dispatch.getCall(1).args[0].payload.text).toBe(
|
||||
expect(dispatchSpy.callCount).toBe(2);
|
||||
expect(dispatchSpy.getCall(0).args[0].type).toBe(SAVE_SLICE_SUCCESS);
|
||||
expect(dispatchSpy.getCall(1).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatchSpy.getCall(1).args[0].payload.toastType).toBe(
|
||||
'SUCCESS_TOAST',
|
||||
);
|
||||
expect(dispatchSpy.getCall(1).args[0].payload.text).toBe(
|
||||
'Chart [New chart] has been saved',
|
||||
);
|
||||
|
||||
|
|
@ -126,19 +223,22 @@ test('createSlice handles success', async () => {
|
|||
test('createSlice handles failure', async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.post(createSliceEndpoint, { throws: sampleError });
|
||||
const dispatch = sinon.spy();
|
||||
const getState = sinon.spy(() => mockExploreState);
|
||||
let caughtError;
|
||||
|
||||
const dispatchSpy = sinon.spy();
|
||||
const dispatch = (action: any) => dispatchSpy(action);
|
||||
const getState = () => mockExploreState;
|
||||
|
||||
let caughtError: Error | undefined;
|
||||
try {
|
||||
await createSlice(sliceName, [])(dispatch, getState);
|
||||
await createSlice(sliceName, [])(dispatch as Dispatch, getState);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
|
||||
expect(caughtError).toEqual(sampleError);
|
||||
expect(fetchMock.calls(createSliceEndpoint)).toHaveLength(4);
|
||||
expect(dispatch.callCount).toBe(1);
|
||||
expect(dispatch.getCall(0).args[0].type).toBe(SAVE_SLICE_FAILED);
|
||||
expect(dispatchSpy.callCount).toBe(1);
|
||||
expect(dispatchSpy.getCall(0).args[0].type).toBe(SAVE_SLICE_FAILED);
|
||||
});
|
||||
|
||||
const dashboardName = 'New dashboard';
|
||||
|
|
@ -155,7 +255,9 @@ test('createDashboard handles success', async () => {
|
|||
fetchMock.reset();
|
||||
fetchMock.post(createDashboardEndpoint, dashboardResponsePayload);
|
||||
const dispatch = sinon.spy();
|
||||
const dashboard = await createDashboard(dashboardName)(dispatch);
|
||||
const dashboard = await createDashboard(dashboardName)(
|
||||
dispatch as Dispatch<any>,
|
||||
);
|
||||
expect(fetchMock.calls(createDashboardEndpoint)).toHaveLength(1);
|
||||
expect(dispatch.callCount).toBe(0);
|
||||
expect(dashboard).toEqual(dashboardResponsePayload);
|
||||
|
|
@ -167,7 +269,7 @@ test('createDashboard handles failure', async () => {
|
|||
const dispatch = sinon.spy();
|
||||
let caughtError;
|
||||
try {
|
||||
await createDashboard(dashboardName)(dispatch);
|
||||
await createDashboard(dashboardName)(dispatch as Dispatch<any>);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
|
|
@ -181,24 +283,60 @@ test('createDashboard handles failure', async () => {
|
|||
test('updateSlice with add to new dashboard handles success', async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.put(updateSliceEndpoint, sliceResponsePayload);
|
||||
const dispatch = sinon.spy();
|
||||
const getState = sinon.spy(() => mockExploreState);
|
||||
const slice = await updateSlice({ slice_id: sliceId }, sliceName, [], {
|
||||
new: true,
|
||||
title: dashboardName,
|
||||
})(dispatch, getState);
|
||||
const dispatchSpy = sinon.spy();
|
||||
const dispatch = (action: any) => dispatchSpy(action);
|
||||
const getState = () => mockExploreState;
|
||||
|
||||
const slice = await updateSlice(
|
||||
{
|
||||
slice_id: sliceId,
|
||||
owners: [],
|
||||
form_data: {
|
||||
datasource: `${datasourceId}__${datasourceType}`,
|
||||
viz_type: '',
|
||||
adhoc_filters: [],
|
||||
dashboards: [],
|
||||
},
|
||||
slice_name: '',
|
||||
description: '',
|
||||
description_markdown: '',
|
||||
slice_url: '',
|
||||
viz_type: '',
|
||||
thumbnail_url: '',
|
||||
changed_on: 0,
|
||||
changed_on_humanized: '',
|
||||
modified: '',
|
||||
datasource_id: 0,
|
||||
datasource_type: datasourceType,
|
||||
datasource_url: '',
|
||||
datasource_name: '',
|
||||
created_by: {
|
||||
id: 0,
|
||||
},
|
||||
},
|
||||
sliceName,
|
||||
[],
|
||||
{
|
||||
new: true,
|
||||
title: dashboardName,
|
||||
},
|
||||
)(dispatch as Dispatch<any>, getState);
|
||||
|
||||
expect(fetchMock.calls(updateSliceEndpoint)).toHaveLength(1);
|
||||
expect(dispatch.callCount).toBe(3);
|
||||
expect(dispatch.getCall(0).args[0].type).toBe(SAVE_SLICE_SUCCESS);
|
||||
expect(dispatch.getCall(1).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatch.getCall(1).args[0].payload.toastType).toBe('SUCCESS_TOAST');
|
||||
expect(dispatch.getCall(1).args[0].payload.text).toBe(
|
||||
expect(dispatchSpy.callCount).toBe(3);
|
||||
expect(dispatchSpy.getCall(0).args[0].type).toBe(SAVE_SLICE_SUCCESS);
|
||||
expect(dispatchSpy.getCall(1).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatchSpy.getCall(1).args[0].payload.toastType).toBe(
|
||||
'SUCCESS_TOAST',
|
||||
);
|
||||
expect(dispatchSpy.getCall(1).args[0].payload.text).toBe(
|
||||
'Chart [New chart] has been overwritten',
|
||||
);
|
||||
expect(dispatch.getCall(2).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatch.getCall(2).args[0].payload.toastType).toBe('SUCCESS_TOAST');
|
||||
expect(dispatch.getCall(2).args[0].payload.text).toBe(
|
||||
expect(dispatchSpy.getCall(2).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatchSpy.getCall(2).args[0].payload.toastType).toBe(
|
||||
'SUCCESS_TOAST',
|
||||
);
|
||||
expect(dispatchSpy.getCall(2).args[0].payload.text).toBe(
|
||||
'Dashboard [New dashboard] just got created and chart [New chart] was added to it',
|
||||
);
|
||||
|
||||
|
|
@ -208,39 +346,71 @@ test('updateSlice with add to new dashboard handles success', async () => {
|
|||
test('updateSlice with add to existing dashboard handles success', async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.put(updateSliceEndpoint, sliceResponsePayload);
|
||||
const dispatch = sinon.spy();
|
||||
const getState = sinon.spy(() => mockExploreState);
|
||||
const slice = await updateSlice({ slice_id: sliceId }, sliceName, [], {
|
||||
new: false,
|
||||
title: dashboardName,
|
||||
})(dispatch, getState);
|
||||
const dispatchSpy = sinon.spy();
|
||||
const dispatch = (action: any) => dispatchSpy(action);
|
||||
const getState = () => mockExploreState;
|
||||
const slice = await updateSlice(
|
||||
{
|
||||
slice_id: sliceId,
|
||||
owners: [],
|
||||
form_data: {
|
||||
datasource: `${datasourceId}__${datasourceType}`,
|
||||
viz_type: '',
|
||||
adhoc_filters: [],
|
||||
dashboards: [],
|
||||
},
|
||||
slice_name: '',
|
||||
description: '',
|
||||
description_markdown: '',
|
||||
slice_url: '',
|
||||
viz_type: '',
|
||||
thumbnail_url: '',
|
||||
changed_on: 0,
|
||||
changed_on_humanized: '',
|
||||
modified: '',
|
||||
datasource_id: 0,
|
||||
datasource_type: datasourceType,
|
||||
datasource_url: '',
|
||||
datasource_name: '',
|
||||
created_by: {
|
||||
id: 0,
|
||||
},
|
||||
},
|
||||
sliceName,
|
||||
[],
|
||||
{
|
||||
new: false,
|
||||
title: dashboardName,
|
||||
},
|
||||
)(dispatch as Dispatch<any>, getState);
|
||||
|
||||
expect(fetchMock.calls(updateSliceEndpoint)).toHaveLength(1);
|
||||
expect(dispatch.callCount).toBe(3);
|
||||
expect(dispatch.getCall(0).args[0].type).toBe(SAVE_SLICE_SUCCESS);
|
||||
expect(dispatch.getCall(1).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatch.getCall(1).args[0].payload.toastType).toBe('SUCCESS_TOAST');
|
||||
expect(dispatch.getCall(1).args[0].payload.text).toBe(
|
||||
expect(dispatchSpy.callCount).toBe(3);
|
||||
expect(dispatchSpy.getCall(0).args[0].type).toBe(SAVE_SLICE_SUCCESS);
|
||||
expect(dispatchSpy.getCall(1).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatchSpy.getCall(1).args[0].payload.toastType).toBe(
|
||||
'SUCCESS_TOAST',
|
||||
);
|
||||
expect(dispatchSpy.getCall(1).args[0].payload.text).toBe(
|
||||
'Chart [New chart] has been overwritten',
|
||||
);
|
||||
expect(dispatch.getCall(2).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatch.getCall(2).args[0].payload.toastType).toBe('SUCCESS_TOAST');
|
||||
expect(dispatch.getCall(2).args[0].payload.text).toBe(
|
||||
expect(dispatchSpy.getCall(2).args[0].type).toBe(ADD_TOAST);
|
||||
expect(dispatchSpy.getCall(2).args[0].payload.toastType).toBe(
|
||||
'SUCCESS_TOAST',
|
||||
);
|
||||
expect(dispatchSpy.getCall(2).args[0].payload.text).toBe(
|
||||
'Chart [New chart] was added to dashboard [New dashboard]',
|
||||
);
|
||||
|
||||
expect(slice).toEqual(sliceResponsePayload);
|
||||
});
|
||||
|
||||
const slice = { slice_id: 10 };
|
||||
const dashboardSlicesResponsePayload = {
|
||||
result: {
|
||||
dashboards: [{ id: 21 }, { id: 22 }, { id: 23 }],
|
||||
},
|
||||
};
|
||||
|
||||
const getDashboardSlicesReturnValue = [21, 22, 23];
|
||||
|
||||
/**
|
||||
* Tests getSliceDashboards action
|
||||
*/
|
||||
|
|
@ -249,10 +419,20 @@ const getSliceDashboardsEndpoint = `glob:*/api/v1/chart/${sliceId}?q=(columns:!(
|
|||
test('getSliceDashboards with slice handles success', async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.get(getSliceDashboardsEndpoint, dashboardSlicesResponsePayload);
|
||||
const dispatch = sinon.spy();
|
||||
const sliceDashboards = await getSliceDashboards(slice)(dispatch);
|
||||
const dispatchSpy = sinon.spy();
|
||||
const dispatch = (action: any) => dispatchSpy(action);
|
||||
const sliceDashboards = await getSliceDashboards({
|
||||
slice_id: 10,
|
||||
owners: [],
|
||||
form_data: {
|
||||
datasource: `${datasourceId}__${datasourceType}`,
|
||||
viz_type: '',
|
||||
adhoc_filters: [],
|
||||
dashboards: [],
|
||||
},
|
||||
})(dispatch as Dispatch<any>);
|
||||
expect(fetchMock.calls(getSliceDashboardsEndpoint)).toHaveLength(1);
|
||||
expect(dispatch.callCount).toBe(0);
|
||||
expect(dispatchSpy.callCount).toBe(0);
|
||||
expect(sliceDashboards).toEqual(getDashboardSlicesReturnValue);
|
||||
});
|
||||
|
||||
|
|
@ -262,7 +442,16 @@ test('getSliceDashboards with slice handles failure', async () => {
|
|||
const dispatch = sinon.spy();
|
||||
let caughtError;
|
||||
try {
|
||||
await getSliceDashboards(slice)(dispatch);
|
||||
await getSliceDashboards({
|
||||
slice_id: sliceId,
|
||||
owners: [],
|
||||
form_data: {
|
||||
datasource: `${datasourceId}__${datasourceType}`,
|
||||
viz_type: '',
|
||||
adhoc_filters: [],
|
||||
dashboards: [],
|
||||
},
|
||||
})(dispatch as Dispatch<any>);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
|
|
@ -276,14 +465,14 @@ test('getSliceDashboards with slice handles failure', async () => {
|
|||
describe('getSlicePayload', () => {
|
||||
const sliceName = 'Test Slice';
|
||||
const formDataWithNativeFilters = {
|
||||
datasource: '22__table',
|
||||
datasource: `${datasourceId}__${datasourceType}`,
|
||||
viz_type: 'pie',
|
||||
adhoc_filters: [],
|
||||
};
|
||||
const dashboards = [5];
|
||||
const owners = [1];
|
||||
const formDataFromSlice = {
|
||||
datasource: '22__table',
|
||||
const owners = [0];
|
||||
const formDataFromSlice: QueryFormData = {
|
||||
datasource: `${datasourceId}__${datasourceType}`,
|
||||
viz_type: 'pie',
|
||||
adhoc_filters: [
|
||||
{
|
||||
|
|
@ -294,6 +483,7 @@ describe('getSlicePayload', () => {
|
|||
expressionType: 'SIMPLE',
|
||||
},
|
||||
],
|
||||
dashboards: [],
|
||||
};
|
||||
|
||||
test('should return the correct payload when no adhoc_filters are present in formDataWithNativeFilters', () => {
|
||||
|
|
@ -301,7 +491,7 @@ describe('getSlicePayload', () => {
|
|||
sliceName,
|
||||
formDataWithNativeFilters,
|
||||
dashboards,
|
||||
owners,
|
||||
owners as [],
|
||||
formDataFromSlice,
|
||||
);
|
||||
expect(result).toHaveProperty('params');
|
||||
|
|
@ -315,13 +505,13 @@ describe('getSlicePayload', () => {
|
|||
expect(result).toHaveProperty('dashboards', dashboards);
|
||||
expect(result).toHaveProperty('owners', owners);
|
||||
expect(result).toHaveProperty('query_context');
|
||||
expect(JSON.parse(result.params).adhoc_filters).toEqual(
|
||||
formDataFromSlice.adhoc_filters,
|
||||
expect(JSON.parse(result.params as string).adhoc_filters).toEqual(
|
||||
formDataWithNativeFilters.adhoc_filters,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return the correct payload when adhoc_filters are present in formDataWithNativeFilters', () => {
|
||||
const formDataWithAdhocFilters = {
|
||||
const formDataWithAdhocFilters: QueryFormData = {
|
||||
...formDataWithNativeFilters,
|
||||
adhoc_filters: [
|
||||
{
|
||||
|
|
@ -337,7 +527,7 @@ describe('getSlicePayload', () => {
|
|||
sliceName,
|
||||
formDataWithAdhocFilters,
|
||||
dashboards,
|
||||
owners,
|
||||
owners as [],
|
||||
formDataFromSlice,
|
||||
);
|
||||
expect(result).toHaveProperty('params');
|
||||
|
|
@ -351,13 +541,13 @@ describe('getSlicePayload', () => {
|
|||
expect(result).toHaveProperty('dashboards', dashboards);
|
||||
expect(result).toHaveProperty('owners', owners);
|
||||
expect(result).toHaveProperty('query_context');
|
||||
expect(JSON.parse(result.params).adhoc_filters).toEqual(
|
||||
expect(JSON.parse(result.params as string).adhoc_filters).toEqual(
|
||||
formDataWithAdhocFilters.adhoc_filters,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return the correct payload when formDataWithNativeFilters has a filter with isExtra set to true', () => {
|
||||
const formDataWithAdhocFiltersWithExtra = {
|
||||
const formDataWithAdhocFiltersWithExtra: QueryFormData = {
|
||||
...formDataWithNativeFilters,
|
||||
adhoc_filters: [
|
||||
{
|
||||
|
|
@ -373,7 +563,7 @@ describe('getSlicePayload', () => {
|
|||
sliceName,
|
||||
formDataWithAdhocFiltersWithExtra,
|
||||
dashboards,
|
||||
owners,
|
||||
owners as [],
|
||||
formDataFromSlice,
|
||||
);
|
||||
expect(result).toHaveProperty('params');
|
||||
|
|
@ -387,13 +577,13 @@ describe('getSlicePayload', () => {
|
|||
expect(result).toHaveProperty('dashboards', dashboards);
|
||||
expect(result).toHaveProperty('owners', owners);
|
||||
expect(result).toHaveProperty('query_context');
|
||||
expect(JSON.parse(result.params).adhoc_filters).toEqual(
|
||||
expect(JSON.parse(result.params as string).adhoc_filters).toEqual(
|
||||
formDataFromSlice.adhoc_filters,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return the correct payload when formDataWithNativeFilters has a filter with isExtra set to true in mixed chart', () => {
|
||||
const formDataFromSliceWithAdhocFilterB = {
|
||||
const formDataFromSliceWithAdhocFilterB: QueryFormData = {
|
||||
...formDataFromSlice,
|
||||
adhoc_filters_b: [
|
||||
{
|
||||
|
|
@ -405,7 +595,7 @@ describe('getSlicePayload', () => {
|
|||
},
|
||||
],
|
||||
};
|
||||
const formDataWithAdhocFiltersWithExtra = {
|
||||
const formDataWithAdhocFiltersWithExtra: QueryFormData = {
|
||||
...formDataWithNativeFilters,
|
||||
viz_type: 'mixed_timeseries',
|
||||
adhoc_filters: [
|
||||
|
|
@ -433,14 +623,13 @@ describe('getSlicePayload', () => {
|
|||
sliceName,
|
||||
formDataWithAdhocFiltersWithExtra,
|
||||
dashboards,
|
||||
owners,
|
||||
owners as [],
|
||||
formDataFromSliceWithAdhocFilterB,
|
||||
);
|
||||
|
||||
expect(JSON.parse(result.params).adhoc_filters).toEqual(
|
||||
expect(JSON.parse(result.params as string).adhoc_filters).toEqual(
|
||||
formDataFromSliceWithAdhocFilterB.adhoc_filters,
|
||||
);
|
||||
expect(JSON.parse(result.params).adhoc_filters_b).toEqual(
|
||||
expect(JSON.parse(result.params as string).adhoc_filters).toEqual(
|
||||
formDataFromSliceWithAdhocFilterB.adhoc_filters_b,
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import rison from 'rison';
|
||||
import { Dispatch } from 'redux';
|
||||
import {
|
||||
DatasourceType,
|
||||
QueryFormData,
|
||||
SimpleAdhocFilter,
|
||||
SupersetClient,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import { addSuccessToast } from 'src/components/MessageToasts/actions';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Slice } from 'src/dashboard/types';
|
||||
import { Operators } from '../constants';
|
||||
import { buildV1ChartDataPayload } from '../exploreUtils';
|
||||
|
||||
export interface PayloadSlice extends Slice {
|
||||
params: string;
|
||||
dashboards: number[];
|
||||
query_context: string;
|
||||
}
|
||||
const ADHOC_FILTER_REGEX = /^adhoc_filters/;
|
||||
|
||||
export const FETCH_DASHBOARDS_SUCCEEDED = 'FETCH_DASHBOARDS_SUCCEEDED';
|
||||
export function fetchDashboardsSucceeded(choices: string[]) {
|
||||
return { type: FETCH_DASHBOARDS_SUCCEEDED, choices };
|
||||
}
|
||||
|
||||
export const FETCH_DASHBOARDS_FAILED = 'FETCH_DASHBOARDS_FAILED';
|
||||
export function fetchDashboardsFailed(userId: string) {
|
||||
return { type: FETCH_DASHBOARDS_FAILED, userId };
|
||||
}
|
||||
|
||||
export const SET_SAVE_CHART_MODAL_VISIBILITY =
|
||||
'SET_SAVE_CHART_MODAL_VISIBILITY';
|
||||
export function setSaveChartModalVisibility(isVisible: boolean) {
|
||||
return { type: SET_SAVE_CHART_MODAL_VISIBILITY, isVisible };
|
||||
}
|
||||
|
||||
export const SAVE_SLICE_FAILED = 'SAVE_SLICE_FAILED';
|
||||
export function saveSliceFailed() {
|
||||
return { type: SAVE_SLICE_FAILED };
|
||||
}
|
||||
|
||||
export const SAVE_SLICE_SUCCESS = 'SAVE_SLICE_SUCCESS';
|
||||
export function saveSliceSuccess(data: Partial<QueryFormData>) {
|
||||
return { type: SAVE_SLICE_SUCCESS, data };
|
||||
}
|
||||
|
||||
function extractAdhocFiltersFromFormData(
|
||||
formDataToHandle: QueryFormData,
|
||||
): Partial<QueryFormData> {
|
||||
const result: Partial<QueryFormData> = {};
|
||||
Object.entries(formDataToHandle).forEach(([key, value]) => {
|
||||
if (ADHOC_FILTER_REGEX.test(key) && Array.isArray(value)) {
|
||||
result[key] = (value as SimpleAdhocFilter[]).filter(
|
||||
(f: SimpleAdhocFilter) => !f.isExtra,
|
||||
);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
const hasTemporalRangeFilter = (formData: Partial<QueryFormData>): boolean =>
|
||||
(formData?.adhoc_filters || []).some(
|
||||
(filter: SimpleAdhocFilter) => filter.operator === Operators.TemporalRange,
|
||||
);
|
||||
|
||||
export const getSlicePayload = (
|
||||
sliceName: string,
|
||||
formDataWithNativeFilters: QueryFormData = {} as QueryFormData,
|
||||
dashboards: number[],
|
||||
owners: [],
|
||||
formDataFromSlice: QueryFormData = {} as QueryFormData,
|
||||
): Partial<PayloadSlice> => {
|
||||
const adhocFilters: Partial<QueryFormData> = extractAdhocFiltersFromFormData(
|
||||
formDataWithNativeFilters,
|
||||
);
|
||||
|
||||
if (
|
||||
!isEmpty(formDataFromSlice) &&
|
||||
formDataWithNativeFilters.adhoc_filters &&
|
||||
formDataWithNativeFilters.adhoc_filters.length > 0
|
||||
) {
|
||||
Object.keys(adhocFilters).forEach(adhocFilterKey => {
|
||||
if (isEmpty(adhocFilters[adhocFilterKey])) {
|
||||
const sourceFilters = formDataFromSlice[adhocFilterKey];
|
||||
if (Array.isArray(sourceFilters)) {
|
||||
const targetArray = adhocFilters[adhocFilterKey] || [];
|
||||
sourceFilters.forEach(filter => {
|
||||
if (filter.operator === Operators.TemporalRange) {
|
||||
targetArray.push({
|
||||
...filter,
|
||||
comparator: filter.comparator || 'No filter',
|
||||
});
|
||||
}
|
||||
});
|
||||
adhocFilters[adhocFilterKey] = targetArray;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasTemporalRangeFilter(adhocFilters)) {
|
||||
formDataWithNativeFilters.adhoc_filters?.forEach(
|
||||
(filter: SimpleAdhocFilter) => {
|
||||
if (filter.operator === Operators.TemporalRange && filter.isExtra) {
|
||||
if (!adhocFilters.adhoc_filters) {
|
||||
adhocFilters.adhoc_filters = [];
|
||||
}
|
||||
adhocFilters.adhoc_filters.push({
|
||||
...filter,
|
||||
comparator: 'No filter',
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
const formData = {
|
||||
...formDataWithNativeFilters,
|
||||
...adhocFilters,
|
||||
dashboards,
|
||||
};
|
||||
let datasourceId = 0;
|
||||
let datasourceType: DatasourceType = DatasourceType.Table;
|
||||
|
||||
if (formData.datasource) {
|
||||
const [id, typeString] = formData.datasource.split('__');
|
||||
datasourceId = parseInt(id, 10);
|
||||
|
||||
const formattedTypeString =
|
||||
typeString.charAt(0).toUpperCase() + typeString.slice(1);
|
||||
if (formattedTypeString in DatasourceType) {
|
||||
datasourceType =
|
||||
DatasourceType[formattedTypeString as keyof typeof DatasourceType];
|
||||
}
|
||||
}
|
||||
|
||||
const payload: Partial<PayloadSlice> = {
|
||||
params: JSON.stringify(formData),
|
||||
slice_name: sliceName,
|
||||
viz_type: formData.viz_type,
|
||||
datasource_id: datasourceId,
|
||||
datasource_type: datasourceType,
|
||||
dashboards,
|
||||
owners,
|
||||
query_context: JSON.stringify(
|
||||
buildV1ChartDataPayload({
|
||||
formData,
|
||||
force: false,
|
||||
resultFormat: 'json',
|
||||
resultType: 'full',
|
||||
setDataMask: null,
|
||||
ownState: null,
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
const addToasts = (
|
||||
isNewSlice: boolean,
|
||||
sliceName: string,
|
||||
addedToDashboard?: {
|
||||
title: string;
|
||||
new?: boolean;
|
||||
},
|
||||
) => {
|
||||
const toasts = [];
|
||||
if (isNewSlice) {
|
||||
toasts.push(addSuccessToast(t('Chart [%s] has been saved', sliceName)));
|
||||
} else {
|
||||
toasts.push(
|
||||
addSuccessToast(t('Chart [%s] has been overwritten', sliceName)),
|
||||
);
|
||||
}
|
||||
|
||||
if (addedToDashboard) {
|
||||
if (addedToDashboard.new) {
|
||||
toasts.push(
|
||||
addSuccessToast(
|
||||
t(
|
||||
'Dashboard [%s] just got created and chart [%s] was added to it',
|
||||
addedToDashboard.title,
|
||||
sliceName,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
toasts.push(
|
||||
addSuccessToast(
|
||||
t(
|
||||
'Chart [%s] was added to dashboard [%s]',
|
||||
sliceName,
|
||||
addedToDashboard.title,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return toasts;
|
||||
};
|
||||
|
||||
export const updateSlice =
|
||||
(
|
||||
slice: Slice,
|
||||
sliceName: string,
|
||||
dashboards: number[],
|
||||
addedToDashboard?: {
|
||||
title: string;
|
||||
new?: boolean;
|
||||
},
|
||||
) =>
|
||||
async (dispatch: Dispatch, getState: () => Partial<QueryFormData>) => {
|
||||
const { slice_id: sliceId, owners, form_data: formDataFromSlice } = slice;
|
||||
const formData = getState().explore?.form_data;
|
||||
try {
|
||||
const response = await SupersetClient.put({
|
||||
endpoint: `/api/v1/chart/${sliceId}`,
|
||||
jsonPayload: getSlicePayload(
|
||||
sliceName,
|
||||
formData,
|
||||
dashboards,
|
||||
owners as [],
|
||||
formDataFromSlice,
|
||||
),
|
||||
});
|
||||
|
||||
dispatch(saveSliceSuccess(response.json));
|
||||
addToasts(false, sliceName, addedToDashboard).map(dispatch);
|
||||
return response.json;
|
||||
} catch (error) {
|
||||
dispatch(saveSliceFailed());
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createSlice =
|
||||
(
|
||||
sliceName: string,
|
||||
dashboards: number[],
|
||||
addedToDashboard?: {
|
||||
title: string;
|
||||
new?: boolean;
|
||||
},
|
||||
) =>
|
||||
async (dispatch: Dispatch, getState: () => Partial<QueryFormData>) => {
|
||||
const formData = getState().explore?.form_data;
|
||||
try {
|
||||
const response = await SupersetClient.post({
|
||||
endpoint: `/api/v1/chart/`,
|
||||
jsonPayload: getSlicePayload(
|
||||
sliceName,
|
||||
formData,
|
||||
dashboards,
|
||||
[],
|
||||
{} as QueryFormData,
|
||||
),
|
||||
});
|
||||
|
||||
dispatch(saveSliceSuccess(response.json));
|
||||
addToasts(true, sliceName, addedToDashboard).map(dispatch);
|
||||
return response.json;
|
||||
} catch (error) {
|
||||
dispatch(saveSliceFailed());
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createDashboard =
|
||||
(dashboardName: string) => async (dispatch: Dispatch) => {
|
||||
try {
|
||||
const response = await SupersetClient.post({
|
||||
endpoint: `/api/v1/dashboard/`,
|
||||
jsonPayload: { dashboard_title: dashboardName },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
} catch (error) {
|
||||
dispatch(saveSliceFailed());
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getSliceDashboards =
|
||||
(slice: Partial<Slice>) => async (dispatch: Dispatch) => {
|
||||
try {
|
||||
const response = await SupersetClient.get({
|
||||
endpoint: `/api/v1/chart/${slice.slice_id}?q=${rison.encode({
|
||||
columns: ['dashboards.id'],
|
||||
})}`,
|
||||
});
|
||||
|
||||
return response.json.result.dashboards.map(
|
||||
({ id }: { id: number }) => id,
|
||||
);
|
||||
} catch (error) {
|
||||
dispatch(saveSliceFailed());
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
export { QueryFormData };
|
||||
Loading…
Reference in New Issue