| /** |
| * 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 { |
| Currency, |
| ensureIsArray, |
| getCategoricalSchemeRegistry, |
| getColumnLabel, |
| getSequentialSchemeRegistry, |
| NO_TIME_RANGE, |
| QueryFormColumn, |
| VizType, |
| } 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 || VizType.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; |
| initialDatasource.currency_formats = Object.fromEntries( |
| (initialDatasource.metrics ?? []) |
| .filter(metric => !!metric.currency) |
| .map((metric): [string, Currency] => [ |
| metric.metric_name, |
| metric.currency!, |
| ]), |
| ); |
| |
| 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; |
| }; |