export function saveDashboardRequest()

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));
  };
}