in superset-frontend/src/dashboard/actions/dashboardState.js [271:531]
export function saveDashboardRequest(data, id, saveType) {
return (dispatch, getState) => {
dispatch({ type: UPDATE_COMPONENTS_PARENTS_LIST });
dispatch(saveDashboardStarted());
const { dashboardFilters, dashboardLayout } = getState();
const layout = dashboardLayout.present;
Object.values(dashboardFilters).forEach(filter => {
const { chartId } = filter;
const componentId = filter.directPathToFilter.slice().pop();
const directPathToFilter = (layout[componentId]?.parents || []).slice();
directPathToFilter.push(componentId);
dispatch(updateDirectPathToFilter(chartId, directPathToFilter));
});
// serialize selected values for each filter field, grouped by filter id
const serializedFilters = serializeActiveFilterValues(getActiveFilters());
// serialize filter scope for each filter field, grouped by filter id
const serializedFilterScopes = serializeFilterScopes(dashboardFilters);
const {
certified_by,
certification_details,
css,
dashboard_title,
owners,
roles,
slug,
} = data;
const hasId = item => item.id !== undefined;
const metadataCrossFiltersEnabled = data.metadata?.cross_filters_enabled;
const colorScheme = data.metadata?.color_scheme;
const customLabelsColor = data.metadata?.label_colors || {};
const sharedLabelsColor = enforceSharedLabelsColorsArray(
data.metadata?.shared_label_colors,
);
const cleanedData = {
...data,
certified_by: certified_by || '',
certification_details:
certified_by && certification_details ? certification_details : '',
css: css || '',
dashboard_title: dashboard_title || t('[ untitled dashboard ]'),
owners: ensureIsArray(owners).map(o => (hasId(o) ? o.id : o)),
roles: !isFeatureEnabled(FeatureFlag.DashboardRbac)
? undefined
: ensureIsArray(roles).map(r => (hasId(r) ? r.id : r)),
slug: slug || null,
metadata: {
...data.metadata,
color_namespace: getColorNamespace(data.metadata?.color_namespace),
color_scheme: colorScheme || '',
color_scheme_domain: colorScheme
? getColorSchemeDomain(colorScheme)
: [],
expanded_slices: data.metadata?.expanded_slices || {},
label_colors: customLabelsColor,
shared_label_colors: getFreshSharedLabels(sharedLabelsColor),
map_label_colors: getFreshLabelsColorMapEntries(customLabelsColor),
refresh_frequency: data.metadata?.refresh_frequency || 0,
timed_refresh_immune_slices:
data.metadata?.timed_refresh_immune_slices || [],
// cross-filters should be enabled by default
cross_filters_enabled: isCrossFiltersEnabled(
metadataCrossFiltersEnabled,
),
},
};
const handleChartConfiguration = () => {
const {
dashboardLayout,
charts,
dashboardInfo: { metadata },
} = getState();
return getCrossFiltersConfiguration(
dashboardLayout.present,
metadata,
charts,
);
};
const onCopySuccess = response => {
const lastModifiedTime = response.json.result.last_modified_time;
if (lastModifiedTime) {
dispatch(saveDashboardRequestSuccess(lastModifiedTime));
}
const { chartConfiguration, globalChartConfiguration } =
handleChartConfiguration();
dispatch(
saveChartConfiguration({
chartConfiguration,
globalChartConfiguration,
}),
);
dispatch(saveDashboardFinished());
dispatch(addSuccessToast(t('This dashboard was saved successfully.')));
return response;
};
const onUpdateSuccess = response => {
const updatedDashboard = response.json.result;
const lastModifiedTime = response.json.last_modified_time;
// syncing with the backend transformations of the metadata
if (updatedDashboard.json_metadata) {
const metadata = JSON.parse(updatedDashboard.json_metadata);
dispatch(setDashboardMetadata(metadata));
if (metadata.chart_configuration) {
dispatch({
type: SAVE_CHART_CONFIG_COMPLETE,
chartConfiguration: metadata.chart_configuration,
});
}
if (metadata.native_filter_configuration) {
dispatch({
type: SET_IN_SCOPE_STATUS_OF_FILTERS,
filterConfig: metadata.native_filter_configuration,
});
}
// fetch datasets to make sure they are up to date
SupersetClient.get({
endpoint: `/api/v1/dashboard/${id}/datasets`,
headers: { 'Content-Type': 'application/json' },
}).then(({ json }) => {
const datasources = json?.result ?? [];
if (datasources.length) {
dispatch(setDatasources(datasources));
}
});
}
if (lastModifiedTime) {
dispatch(saveDashboardRequestSuccess(lastModifiedTime));
}
dispatch(saveDashboardFinished());
// redirect to the new slug or id
navigateWithState(`/superset/dashboard/${slug || id}/`, {
event: 'dashboard_properties_changed',
});
dispatch(addSuccessToast(t('This dashboard was saved successfully.')));
dispatch(setOverrideConfirm(undefined));
return response;
};
const onError = async response => {
const { error, message } = await getClientErrorObject(response);
let errorText = t('Sorry, an unknown error occurred');
if (error) {
errorText = t(
'Sorry, there was an error saving this dashboard: %s',
error,
);
}
if (typeof message === 'string' && message === 'Forbidden') {
errorText = t('You do not have permission to edit this dashboard');
}
dispatch(saveDashboardFinished());
dispatch(addDangerToast(errorText));
};
if (
[SAVE_TYPE_OVERWRITE, SAVE_TYPE_OVERWRITE_CONFIRMED].includes(saveType)
) {
const { chartConfiguration, globalChartConfiguration } =
handleChartConfiguration();
const updatedDashboard =
saveType === SAVE_TYPE_OVERWRITE_CONFIRMED
? data
: {
certified_by: cleanedData.certified_by,
certification_details: cleanedData.certification_details,
css: cleanedData.css,
dashboard_title: cleanedData.dashboard_title,
slug: cleanedData.slug,
owners: cleanedData.owners,
roles: cleanedData.roles,
json_metadata: safeStringify({
...(cleanedData?.metadata || {}),
default_filters: safeStringify(serializedFilters),
filter_scopes: serializedFilterScopes,
chart_configuration: chartConfiguration,
global_chart_configuration: globalChartConfiguration,
}),
};
const updateDashboard = () =>
SupersetClient.put({
endpoint: `/api/v1/dashboard/${id}`,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedDashboard),
})
.then(response => onUpdateSuccess(response))
.catch(response => onError(response));
return new Promise((resolve, reject) => {
if (
!isFeatureEnabled(FeatureFlag.ConfirmDashboardDiff) ||
saveType === SAVE_TYPE_OVERWRITE_CONFIRMED
) {
// skip overwrite precheck
resolve();
return;
}
// precheck for overwrite items
SupersetClient.get({
endpoint: `/api/v1/dashboard/${id}`,
}).then(response => {
const dashboard = response.json.result;
const overwriteConfirmItems = getOverwriteItems(
dashboard,
updatedDashboard,
);
if (overwriteConfirmItems.length > 0) {
dispatch(
setOverrideConfirm({
updatedAt: dashboard.changed_on,
updatedBy: dashboard.changed_by_name,
overwriteConfirmItems,
dashboardId: id,
data: updatedDashboard,
}),
);
return reject(overwriteConfirmItems);
}
return resolve();
});
})
.then(updateDashboard)
.catch(overwriteConfirmItems => {
const errorText = t('Please confirm the overwrite values.');
dispatch(
logEvent(LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA, {
dashboard_id: id,
items: overwriteConfirmItems,
}),
);
dispatch(addDangerToast(errorText));
});
}
// changing the data as the endpoint requires
if ('positions' in cleanedData && !('positions' in cleanedData.metadata)) {
cleanedData.metadata.positions = cleanedData.positions;
}
cleanedData.metadata.default_filters = safeStringify(serializedFilters);
cleanedData.metadata.filter_scopes = serializedFilterScopes;
const copyPayload = {
dashboard_title: cleanedData.dashboard_title,
css: cleanedData.css,
duplicate_slices: cleanedData.duplicate_slices,
json_metadata: JSON.stringify(cleanedData.metadata),
};
return SupersetClient.post({
endpoint: `/api/v1/dashboard/${id}/copy/`,
jsonPayload: copyPayload,
})
.then(response => onCopySuccess(response))
.catch(response => onError(response));
};
}