| /** |
| * 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. |
| */ |
| /* eslint-disable camelcase */ |
| import { isString } from 'lodash'; |
| import shortid from 'shortid'; |
| import { CategoricalColorNamespace } from '@superset-ui/core'; |
| |
| import { initSliceEntities } from 'src/dashboard/reducers/sliceEntities'; |
| import { getInitialState as getInitialNativeFilterState } from 'src/dashboard/reducers/nativeFilters'; |
| import { getParam } from 'src/modules/utils'; |
| import { applyDefaultFormData } from 'src/explore/store'; |
| import { buildActiveFilters } from 'src/dashboard/util/activeDashboardFilters'; |
| import { |
| DASHBOARD_FILTER_SCOPE_GLOBAL, |
| dashboardFilter, |
| } from './dashboardFilters'; |
| import { chart } from '../../chart/chartReducer'; |
| import { |
| DASHBOARD_HEADER_ID, |
| GRID_DEFAULT_CHART_WIDTH, |
| GRID_COLUMN_COUNT, |
| } from '../util/constants'; |
| import { |
| DASHBOARD_HEADER_TYPE, |
| CHART_TYPE, |
| ROW_TYPE, |
| } from '../util/componentTypes'; |
| import findFirstParentContainerId from '../util/findFirstParentContainer'; |
| import getEmptyLayout from '../util/getEmptyLayout'; |
| import getFilterConfigsFromFormdata from '../util/getFilterConfigsFromFormdata'; |
| import getLocationHash from '../util/getLocationHash'; |
| import newComponentFactory from '../util/newComponentFactory'; |
| import { TIME_RANGE } from '../../visualizations/FilterBox/FilterBox'; |
| |
| export default function getInitialState(bootstrapData) { |
| const { user_id, datasources, common, editMode, urlParams } = bootstrapData; |
| |
| const dashboard = { ...bootstrapData.dashboard_data }; |
| let preselectFilters = {}; |
| try { |
| // allow request parameter overwrite dashboard metadata |
| preselectFilters = JSON.parse( |
| getParam('preselect_filters') || dashboard.metadata.default_filters, |
| ); |
| } catch (e) { |
| // |
| } |
| |
| // Priming the color palette with user's label-color mapping provided in |
| // the dashboard's JSON metadata |
| if (dashboard.metadata && dashboard.metadata.label_colors) { |
| const scheme = dashboard.metadata.color_scheme; |
| const namespace = dashboard.metadata.color_namespace; |
| const colorMap = isString(dashboard.metadata.label_colors) |
| ? JSON.parse(dashboard.metadata.label_colors) |
| : dashboard.metadata.label_colors; |
| Object.keys(colorMap).forEach(label => { |
| CategoricalColorNamespace.getScale(scheme, namespace).setColor( |
| label, |
| colorMap[label], |
| ); |
| }); |
| } |
| |
| // dashboard layout |
| const { position_json: positionJson } = dashboard; |
| // new dash: positionJson could be {} or null |
| const layout = |
| positionJson && Object.keys(positionJson).length > 0 |
| ? positionJson |
| : getEmptyLayout(); |
| |
| // create a lookup to sync layout names with slice names |
| const chartIdToLayoutId = {}; |
| Object.values(layout).forEach(layoutComponent => { |
| if (layoutComponent.type === CHART_TYPE) { |
| chartIdToLayoutId[layoutComponent.meta.chartId] = layoutComponent.id; |
| } |
| }); |
| |
| // find root level chart container node for newly-added slices |
| const parentId = findFirstParentContainerId(layout); |
| const parent = layout[parentId]; |
| let newSlicesContainer; |
| let newSlicesContainerWidth = 0; |
| |
| const filterScopes = dashboard.metadata.filter_scopes || {}; |
| |
| const chartQueries = {}; |
| const dashboardFilters = {}; |
| const slices = {}; |
| const sliceIds = new Set(); |
| dashboard.slices.forEach(slice => { |
| const key = slice.slice_id; |
| const form_data = { |
| ...slice.form_data, |
| url_params: { |
| ...slice.form_data.url_params, |
| ...urlParams, |
| }, |
| }; |
| chartQueries[key] = { |
| ...chart, |
| id: key, |
| form_data, |
| formData: applyDefaultFormData(form_data), |
| }; |
| |
| slices[key] = { |
| slice_id: key, |
| slice_url: slice.slice_url, |
| slice_name: slice.slice_name, |
| form_data: slice.form_data, |
| viz_type: slice.form_data.viz_type, |
| datasource: slice.form_data.datasource, |
| description: slice.description, |
| description_markeddown: slice.description_markeddown, |
| owners: slice.owners, |
| modified: slice.modified, |
| changed_on: new Date(slice.changed_on).getTime(), |
| }; |
| |
| sliceIds.add(key); |
| |
| // if there are newly added slices from explore view, fill slices into 1 or more rows |
| if (!chartIdToLayoutId[key] && layout[parentId]) { |
| if ( |
| newSlicesContainerWidth === 0 || |
| newSlicesContainerWidth + GRID_DEFAULT_CHART_WIDTH > GRID_COLUMN_COUNT |
| ) { |
| newSlicesContainer = newComponentFactory( |
| ROW_TYPE, |
| (parent.parents || []).slice(), |
| ); |
| layout[newSlicesContainer.id] = newSlicesContainer; |
| parent.children.push(newSlicesContainer.id); |
| newSlicesContainerWidth = 0; |
| } |
| |
| const chartHolder = newComponentFactory( |
| CHART_TYPE, |
| { |
| chartId: slice.slice_id, |
| }, |
| (newSlicesContainer.parents || []).slice(), |
| ); |
| |
| layout[chartHolder.id] = chartHolder; |
| newSlicesContainer.children.push(chartHolder.id); |
| chartIdToLayoutId[chartHolder.meta.chartId] = chartHolder.id; |
| newSlicesContainerWidth += GRID_DEFAULT_CHART_WIDTH; |
| } |
| |
| // build DashboardFilters for interactive filter features |
| if ( |
| slice.form_data.viz_type === 'filter_box' || |
| slice.form_data.viz_type === 'filter_select' |
| ) { |
| const configs = getFilterConfigsFromFormdata(slice.form_data); |
| let { columns } = configs; |
| const { labels } = configs; |
| if (preselectFilters[key]) { |
| Object.keys(columns).forEach(col => { |
| if (preselectFilters[key][col]) { |
| columns = { |
| ...columns, |
| [col]: preselectFilters[key][col], |
| }; |
| } |
| }); |
| } |
| |
| const scopesByChartId = Object.keys(columns).reduce((map, column) => { |
| const scopeSettings = { |
| ...filterScopes[key], |
| }; |
| const { scope, immune } = { |
| ...DASHBOARD_FILTER_SCOPE_GLOBAL, |
| ...scopeSettings[column], |
| }; |
| |
| return { |
| ...map, |
| [column]: { |
| scope, |
| immune, |
| }, |
| }; |
| }, {}); |
| |
| const componentId = chartIdToLayoutId[key]; |
| const directPathToFilter = (layout[componentId].parents || []).slice(); |
| directPathToFilter.push(componentId); |
| dashboardFilters[key] = { |
| ...dashboardFilter, |
| chartId: key, |
| componentId, |
| datasourceId: slice.form_data.datasource, |
| filterName: slice.slice_name, |
| directPathToFilter, |
| columns, |
| labels, |
| scopes: scopesByChartId, |
| isInstantFilter: !!slice.form_data.instant_filtering, |
| isDateFilter: Object.keys(columns).includes(TIME_RANGE), |
| }; |
| } |
| |
| // sync layout names with current slice names in case a slice was edited |
| // in explore since the layout was updated. name updates go through layout for undo/redo |
| // functionality and python updates slice names based on layout upon dashboard save |
| const layoutId = chartIdToLayoutId[key]; |
| if (layoutId && layout[layoutId]) { |
| layout[layoutId].meta.sliceName = slice.slice_name; |
| } |
| }); |
| buildActiveFilters({ |
| dashboardFilters, |
| components: layout, |
| }); |
| |
| // store the header as a layout component so we can undo/redo changes |
| layout[DASHBOARD_HEADER_ID] = { |
| id: DASHBOARD_HEADER_ID, |
| type: DASHBOARD_HEADER_TYPE, |
| meta: { |
| text: dashboard.dashboard_title, |
| }, |
| }; |
| |
| const dashboardLayout = { |
| past: [], |
| present: layout, |
| future: [], |
| }; |
| |
| // find direct link component and path from root |
| const directLinkComponentId = getLocationHash(); |
| let directPathToChild = []; |
| if (layout[directLinkComponentId]) { |
| directPathToChild = (layout[directLinkComponentId].parents || []).slice(); |
| directPathToChild.push(directLinkComponentId); |
| } |
| |
| const nativeFilters = getInitialNativeFilterState({ |
| filterConfig: dashboard.metadata.native_filter_configuration || [], |
| filterSetsConfig: dashboard.metadata.filter_sets_configuration || [], |
| }); |
| |
| return { |
| datasources, |
| sliceEntities: { ...initSliceEntities, slices, isLoading: false }, |
| charts: chartQueries, |
| // read-only data |
| dashboardInfo: { |
| id: dashboard.id, |
| slug: dashboard.slug, |
| metadata: dashboard.metadata, |
| userId: user_id, |
| dash_edit_perm: dashboard.dash_edit_perm, |
| dash_save_perm: dashboard.dash_save_perm, |
| superset_can_explore: dashboard.superset_can_explore, |
| superset_can_csv: dashboard.superset_can_csv, |
| slice_can_edit: dashboard.slice_can_edit, |
| common: { |
| flash_messages: common.flash_messages, |
| conf: common.conf, |
| }, |
| lastModifiedTime: dashboard.last_modified_time, |
| }, |
| dashboardFilters, |
| nativeFilters, |
| dashboardState: { |
| sliceIds: Array.from(sliceIds), |
| directPathToChild, |
| directPathLastUpdated: Date.now(), |
| focusedFilterField: null, |
| expandedSlices: dashboard.metadata.expanded_slices || {}, |
| refreshFrequency: dashboard.metadata.refresh_frequency || 0, |
| // dashboard viewers can set refresh frequency for the current visit, |
| // only persistent refreshFrequency will be saved to backend |
| shouldPersistRefreshFrequency: false, |
| css: dashboard.css || '', |
| colorNamespace: dashboard.metadata.color_namespace, |
| colorScheme: dashboard.metadata.color_scheme, |
| editMode: dashboard.dash_edit_perm && editMode, |
| isPublished: dashboard.published, |
| hasUnsavedChanges: false, |
| maxUndoHistoryExceeded: false, |
| lastModifiedTime: dashboard.last_modified_time, |
| }, |
| dashboardLayout, |
| messageToasts: [], |
| impressionId: shortid.generate(), |
| }; |
| } |