airbnb/caravel

View on GitHub
superset-frontend/src/explore/actions/hydrateExplore.ts

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * 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 { ControlStateMapping } from '@superset-ui/chart-controls';

import {
  ChartState,
  ExplorePageInitialData,
  ExplorePageState,
} from 'src/explore/types';
import { getChartKey } from 'src/explore/exploreUtils';
import { getControlsState } from 'src/explore/store';
import { Dispatch } from 'redux';
import {
  ensureIsArray,
  getCategoricalSchemeRegistry,
  getColumnLabel,
  getSequentialSchemeRegistry,
  NO_TIME_RANGE,
  QueryFormColumn,
} from '@superset-ui/core';
import {
  getFormDataFromControls,
  applyMapStateToPropsToControl,
} from 'src/explore/controlUtils';
import { getDatasourceUid } from 'src/utils/getDatasourceUid';
import { getUrlParam } from 'src/utils/urlUtils';
import { URL_PARAMS } from 'src/constants';
import { findPermission } from 'src/utils/findPermission';

enum ColorSchemeType {
  CATEGORICAL = 'CATEGORICAL',
  SEQUENTIAL = 'SEQUENTIAL',
}

export const HYDRATE_EXPLORE = 'HYDRATE_EXPLORE';
export const hydrateExplore =
  ({
    form_data,
    slice,
    dataset,
    metadata,
    saveAction = null,
  }: ExplorePageInitialData) =>
  (dispatch: Dispatch, getState: () => ExplorePageState) => {
    const { user, datasources, charts, sliceEntities, common, explore } =
      getState();

    const sliceId = getUrlParam(URL_PARAMS.sliceId);
    const dashboardId = getUrlParam(URL_PARAMS.dashboardId);
    const fallbackSlice = sliceId ? sliceEntities?.slices?.[sliceId] : null;
    const initialSlice = slice ?? fallbackSlice;
    const initialFormData = form_data ?? initialSlice?.form_data;
    if (!initialFormData.viz_type) {
      const defaultVizType = common?.conf.DEFAULT_VIZ_TYPE || 'table';
      initialFormData.viz_type =
        getUrlParam(URL_PARAMS.vizType) || defaultVizType;
    }
    if (!initialFormData.time_range) {
      initialFormData.time_range =
        common?.conf?.DEFAULT_TIME_FILTER || NO_TIME_RANGE;
    }
    if (
      initialFormData.include_time &&
      initialFormData.granularity_sqla &&
      !initialFormData.groupby?.some(
        (col: QueryFormColumn) =>
          getColumnLabel(col) ===
          getColumnLabel(initialFormData.granularity_sqla!),
      )
    ) {
      initialFormData.groupby = [
        initialFormData.granularity_sqla,
        ...ensureIsArray(initialFormData.groupby),
      ];
      initialFormData.granularity_sqla = undefined;
    }

    if (dashboardId) {
      initialFormData.dashboardId = dashboardId;
    }

    const initialDatasource = dataset;

    const initialExploreState = {
      form_data: initialFormData,
      slice: initialSlice,
      datasource: initialDatasource,
    };
    const initialControls = getControlsState(
      initialExploreState,
      initialFormData,
    ) as ControlStateMapping;
    const colorSchemeKey = initialControls.color_scheme && 'color_scheme';
    const linearColorSchemeKey =
      initialControls.linear_color_scheme && 'linear_color_scheme';
    // if the selected color scheme does not exist anymore
    // fallbacks and selects the available default one
    const verifyColorScheme = (type: ColorSchemeType) => {
      const schemes =
        type === 'CATEGORICAL'
          ? getCategoricalSchemeRegistry()
          : getSequentialSchemeRegistry();
      const key =
        type === 'CATEGORICAL' ? colorSchemeKey : linearColorSchemeKey;
      const registryDefaultScheme = schemes.defaultKey;
      const defaultScheme =
        type === 'CATEGORICAL' ? 'supersetColors' : 'superset_seq_1';
      const currentScheme = initialFormData[key];
      const colorSchemeExists = !!schemes.get(currentScheme, true);

      if (currentScheme && !colorSchemeExists) {
        initialControls[key].value = registryDefaultScheme || defaultScheme;
      }
    };

    if (colorSchemeKey) verifyColorScheme(ColorSchemeType.CATEGORICAL);
    if (linearColorSchemeKey) verifyColorScheme(ColorSchemeType.SEQUENTIAL);

    const exploreState = {
      // note this will add `form_data` to state,
      // which will be manipulable by future reducers.
      can_add: findPermission('can_write', 'Chart', user?.roles),
      can_download: findPermission('can_csv', 'Superset', user?.roles),
      can_overwrite: ensureIsArray(slice?.owners).includes(
        user?.userId as number,
      ),
      isDatasourceMetaLoading: false,
      isStarred: false,
      triggerRender: false,
      // duplicate datasource in exploreState - it's needed by getControlsState
      datasource: initialDatasource,
      // Initial control state will skip `control.mapStateToProps`
      // because `bootstrapData.controls` is undefined.
      controls: initialControls,
      form_data: initialFormData,
      slice: initialSlice,
      controlsTransferred: explore.controlsTransferred,
      standalone: getUrlParam(URL_PARAMS.standalone),
      force: getUrlParam(URL_PARAMS.force),
      metadata,
      saveAction,
      common,
    };

    // apply initial mapStateToProps for all controls, must execute AFTER
    // bootstrapState has initialized `controls`. Order of execution is not
    // guaranteed, so controls shouldn't rely on each other's mapped state.
    Object.entries(exploreState.controls).forEach(([key, controlState]) => {
      exploreState.controls[key] = applyMapStateToPropsToControl(
        controlState,
        exploreState,
      );
    });
    const sliceFormData = initialSlice
      ? getFormDataFromControls(initialControls)
      : null;

    const chartKey: number = getChartKey(initialExploreState);
    const chart: ChartState = {
      id: chartKey,
      chartAlert: null,
      chartStatus: null,
      chartStackTrace: null,
      chartUpdateEndTime: null,
      chartUpdateStartTime: 0,
      latestQueryFormData: getFormDataFromControls(exploreState.controls),
      sliceFormData,
      queryController: null,
      queriesResponse: null,
      triggerQuery: false,
      lastRendered: 0,
    };

    return dispatch({
      type: HYDRATE_EXPLORE,
      data: {
        charts: {
          ...charts,
          [chartKey]: chart,
        },
        datasources: {
          ...datasources,
          [getDatasourceUid(initialDatasource)]: initialDatasource,
        },
        saveModal: {
          dashboards: [],
          saveModalAlert: null,
          isVisible: false,
        },
        explore: exploreState,
      },
    });
  };

export type HydrateExplore = {
  type: typeof HYDRATE_EXPLORE;
  data: ExplorePageState;
};