suddjian commented on a change in pull request #13306:
URL: https://github.com/apache/superset/pull/13306#discussion_r604243917



##########
File path: superset-frontend/src/dashboard/actions/hydrate.js
##########
@@ -0,0 +1,352 @@
+/**
+ * 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, keyBy } from 'lodash';
+import shortid from 'shortid';
+import { CategoricalColorNamespace } from '@superset-ui/core';
+import querystring from 'query-string';
+
+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 getPermissions from 'src/dashboard/util/getPermissions';
+import {
+  DASHBOARD_FILTER_SCOPE_GLOBAL,
+  dashboardFilter,
+} from '../reducers/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';
+
+const reservedQueryParams = new Set(['standalone', 'edit']);
+
+/**
+ * Returns the url params that are used to customize queries
+ * in datasets built using sql lab.
+ * We may want to extract this to some kind of util in the future.
+ */
+const extractUrlParams = queryParams =>
+  Object.entries(queryParams).reduce((acc, [key, value]) => {
+    if (reservedQueryParams.has(key)) return acc;
+    if (Array.isArray(value)) {
+      return {
+        ...acc,
+        [key]: value[0],
+      };
+    }
+    return { ...acc, [key]: value };
+  }, {});
+
+export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD';
+
+export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => 
(
+  dispatch,
+  getState,
+) => {
+  const { user, common } = getState();
+  const metadata = JSON.parse(dashboardData.json_metadata);
+  const queryParams = querystring.parse(window.location.search);
+  const urlParams = extractUrlParams(queryParams);
+  const editMode = queryParams.edit === 'true';
+
+  let preselectFilters = {};
+
+  chartData.forEach(chart => {
+    // eslint-disable-next-line no-param-reassign
+    chart.slice_id = chart.form_data.slice_id;
+  });
+  try {
+    // allow request parameter overwrite dashboard metadata
+    preselectFilters = JSON.parse(
+      getParam('preselect_filters') || metadata.default_filters,
+    );
+  } catch (e) {
+    //
+  }
+
+  // Priming the color palette with user's label-color mapping provided in
+  // the dashboard's JSON metadata
+  if (metadata && metadata.label_colors) {
+    const scheme = metadata.color_scheme;
+    const namespace = metadata.color_namespace;
+    const colorMap = isString(metadata.label_colors)
+      ? JSON.parse(metadata.label_colors)
+      : metadata.label_colors;
+    Object.keys(colorMap).forEach(label => {
+      CategoricalColorNamespace.getScale(scheme, namespace).setColor(
+        label,
+        colorMap[label],
+      );
+    });
+  }
+
+  // dashboard layout
+  const positionJson = JSON.parse(dashboardData.position_json);
+  // 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 = (metadata && metadata.filter_scopes) || {};
+
+  const chartQueries = {};
+  const dashboardFilters = {};
+  const slices = {};
+  const sliceIds = new Set();
+  chartData.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: dashboardData.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: metadata?.filter_configuration || [],
+    filterSetsConfig: metadata?.filter_sets_configuration || [],
+  });
+
+  const { roles } = getState().user;
+
+  return dispatch({
+    type: HYDRATE_DASHBOARD,
+    data: {
+      datasources: keyBy(datasourcesData, 'uid'),
+      sliceEntities: { ...initSliceEntities, slices, isLoading: false },
+      charts: chartQueries,
+      // read-only data
+      dashboardInfo: {
+        id: dashboardData.id,
+        slug: dashboardData.slug,
+        metadata,
+        userId: String(user.userId), // legacy, please use state.user instead
+        dash_edit_perm: getPermissions('can_write', 'Dashboard', roles),

Review comment:
       `getPermission` is used here instead of having the API place permissions 
in the dashboard entity.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
[email protected]



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to