Revert "chore(spa refactor): refactoring dashboard to use api's instead of bootstrapdata (#13306)"

This reverts commit 4bb29b6f04c2c23585805bbac7349f2d2c9876bb.
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts
index 99f5f72..b03cdd2 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts
@@ -23,28 +23,12 @@
 } from './dashboard.helper';
 
 describe('Dashboard load', () => {
-  beforeEach(() => {
+  before(() => {
     cy.login();
+    cy.visit(WORLD_HEALTH_DASHBOARD);
   });
 
   it('should load dashboard', () => {
-    cy.visit(WORLD_HEALTH_DASHBOARD);
     WORLD_HEALTH_CHARTS.forEach(waitForChartLoad);
   });
-
-  it('should load in edit mode', () => {
-    cy.visit(`${WORLD_HEALTH_DASHBOARD}?edit=true&standalone=true`);
-    cy.get('[data-test="discard-changes-button"]').should('be.visible');
-  });
-
-  it('should load in standalone mode', () => {
-    cy.visit(`${WORLD_HEALTH_DASHBOARD}?edit=true&standalone=true`);
-    cy.get('#app-menu').should('not.exist');
-  });
-
-  it('should load in edit/standalone mode', () => {
-    cy.visit(`${WORLD_HEALTH_DASHBOARD}?edit=true&standalone=true`);
-    cy.get('[data-test="discard-changes-button"]').should('be.visible');
-    cy.get('#app-menu').should('not.exist');
-  });
 });
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
index fbb1aea..f279b8e 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
@@ -61,12 +61,9 @@
       .click()
       .type('Country name');
 
-    cy.get('.ant-modal')
-      .find('[data-test="datasource-input"]')
-      .click()
-      .type('wb_health_population');
+    cy.get('.ant-modal').find('[data-test="datasource-input"]').click();
 
-    cy.get('.ant-modal [data-test="datasource-input"] .Select__menu')
+    cy.get('[data-test="datasource-input"]')
       .contains('wb_health_population')
       .click();
 
@@ -158,12 +155,9 @@
         .click()
         .type('Country name');
 
-      cy.get('.ant-modal')
-        .find('[data-test="datasource-input"]')
-        .click()
-        .type('wb_health_population');
+      cy.get('.ant-modal').find('[data-test="datasource-input"]').click();
 
-      cy.get('.ant-modal [data-test="datasource-input"] .Select__menu')
+      cy.get('[data-test="datasource-input"]')
         .contains('wb_health_population')
         .click();
 
@@ -193,10 +187,9 @@
       cy.get('.ant-modal')
         .find('[data-test="datasource-input"]')
         .last()
-        .click()
-        .type('wb_health_population');
+        .click();
 
-      cy.get('.ant-modal [data-test="datasource-input"] .Select__menu')
+      cy.get('[data-test="datasource-input"]')
         .last()
         .contains('wb_health_population')
         .click();
diff --git a/superset-frontend/src/chart/chartReducer.ts b/superset-frontend/src/chart/chartReducer.ts
index d6e42df..3f68c04 100644
--- a/superset-frontend/src/chart/chartReducer.ts
+++ b/superset-frontend/src/chart/chartReducer.ts
@@ -18,10 +18,9 @@
  */
 /* eslint camelcase: 0 */
 import { t } from '@superset-ui/core';
-import { HYDRATE_DASHBOARD } from 'src/dashboard/actions/hydrate';
 import { ChartState } from 'src/explore/types';
 import { getFormDataFromControls } from 'src/explore/controlUtils';
-import { now } from 'src/modules/dates';
+import { now } from '../modules/dates';
 import * as actions from './chartAction';
 
 export const chart: ChartState = {
@@ -193,9 +192,7 @@
     delete charts[key];
     return charts;
   }
-  if (action.type === HYDRATE_DASHBOARD) {
-    return { ...action.data.charts };
-  }
+
   if (action.type in actionHandlers) {
     return {
       ...charts,
diff --git a/superset-frontend/src/common/hooks/apiResources/dashboards.ts b/superset-frontend/src/common/hooks/apiResources/dashboards.ts
deleted file mode 100644
index 0bb21f1..0000000
--- a/superset-frontend/src/common/hooks/apiResources/dashboards.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * 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 Dashboard from 'src/types/Dashboard';
-import { useApiV1Resource, useTransformedResource } from './apiResources';
-
-export const useDashboard = (idOrSlug: string | number) =>
-  useTransformedResource(
-    useApiV1Resource<Dashboard>(`/api/v1/dashboard/${idOrSlug}`),
-    dashboard => ({
-      ...dashboard,
-      metadata: JSON.parse(dashboard.json_metadata),
-      position_data: JSON.parse(dashboard.position_json),
-    }),
-  );
-
-// gets the chart definitions for a dashboard
-export const useDashboardCharts = (idOrSlug: string | number) =>
-  useApiV1Resource(`/api/v1/dashboard/${idOrSlug}/charts`);
-
-// gets the datasets for a dashboard
-// important: this endpoint only returns the fields in the dataset
-// that are necessary for rendering the given dashboard
-export const useDashboardDatasets = (idOrSlug: string | number) =>
-  useApiV1Resource(`/api/v1/dashboard/${idOrSlug}/datasets`);
diff --git a/superset-frontend/src/common/hooks/apiResources/index.ts b/superset-frontend/src/common/hooks/apiResources/index.ts
index 5e63920..8befc73 100644
--- a/superset-frontend/src/common/hooks/apiResources/index.ts
+++ b/superset-frontend/src/common/hooks/apiResources/index.ts
@@ -26,5 +26,4 @@
 // A central catalog of API Resource hooks.
 // Add new API hooks here, organized under
 // different files for different resource types.
-export * from './charts';
-export * from './dashboards';
+export { useChartOwnerNames } from './charts';
diff --git a/superset-frontend/src/components/ErrorBoundary/index.jsx b/superset-frontend/src/components/ErrorBoundary/index.jsx
index 0a1d0c7..7bc0075 100644
--- a/superset-frontend/src/components/ErrorBoundary/index.jsx
+++ b/superset-frontend/src/components/ErrorBoundary/index.jsx
@@ -38,7 +38,7 @@
   }
 
   componentDidCatch(error, info) {
-    if (this.props.onError) this.props.onError(error, info);
+    this.props.onError(error, info);
     this.setState({ error, info });
   }
 
diff --git a/superset-frontend/src/dashboard/App.jsx b/superset-frontend/src/dashboard/App.jsx
index 43d00f5..da06a01 100644
--- a/superset-frontend/src/dashboard/App.jsx
+++ b/superset-frontend/src/dashboard/App.jsx
@@ -25,28 +25,22 @@
 import { DynamicPluginProvider } from 'src/components/DynamicPlugins';
 import setupApp from '../setup/setupApp';
 import setupPlugins from '../setup/setupPlugins';
-import DashboardPage from './containers/DashboardPage';
+import DashboardContainer from './containers/Dashboard';
 import { theme } from '../preamble';
 
 setupApp();
 setupPlugins();
 
-const App = ({ store }) => {
-  const dashboardIdOrSlug = window.location.pathname.split('/')[3];
-  return (
-    <Provider store={store}>
-      <DndProvider backend={HTML5Backend}>
-        <ThemeProvider theme={theme}>
-          <DynamicPluginProvider>
-            <DashboardPage
-              store={store}
-              dashboardIdOrSlug={dashboardIdOrSlug}
-            />
-          </DynamicPluginProvider>
-        </ThemeProvider>
-      </DndProvider>
-    </Provider>
-  );
-};
+const App = ({ store }) => (
+  <Provider store={store}>
+    <DndProvider backend={HTML5Backend}>
+      <ThemeProvider theme={theme}>
+        <DynamicPluginProvider>
+          <DashboardContainer />
+        </DynamicPluginProvider>
+      </ThemeProvider>
+    </DndProvider>
+  </Provider>
+);
 
 export default hot(App);
diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js
deleted file mode 100644
index 8065bf6..0000000
--- a/superset-frontend/src/dashboard/actions/hydrate.js
+++ /dev/null
@@ -1,361 +0,0 @@
-/**
- * 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 { chart } from 'src/chart/chartReducer';
-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 'src/dashboard/reducers/dashboardFilters';
-import {
-  DASHBOARD_HEADER_ID,
-  GRID_DEFAULT_CHART_WIDTH,
-  GRID_COLUMN_COUNT,
-} from 'src/dashboard/util/constants';
-import {
-  DASHBOARD_HEADER_TYPE,
-  CHART_TYPE,
-  ROW_TYPE,
-} from 'src/dashboard/util/componentTypes';
-import findFirstParentContainerId from 'src/dashboard/util/findFirstParentContainer';
-import getEmptyLayout from 'src/dashboard/util/getEmptyLayout';
-import getFilterConfigsFromFormdata from 'src/dashboard/util/getFilterConfigsFromFormdata';
-import getLocationHash from 'src/dashboard/util/getLocationHash';
-import newComponentFactory from 'src/dashboard/util/newComponentFactory';
-import { TIME_RANGE } from 'src/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 multiple url params share the same key (?foo=bar&foo=baz), they will appear as an array.
-    // Only one value can be used for a given query param, so we just take the first one.
-    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 } = dashboardData;
-  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?.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 { position_data } = dashboardData;
-  // new dash: position_json could be {} or null
-  const layout =
-    position_data && Object.keys(position_data).length > 0
-      ? position_data
-      : 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?.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?.native_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: {
-        ...dashboardData,
-        userId: String(user.userId), // legacy, please use state.user instead
-        dash_edit_perm: getPermissions('can_write', 'Dashboard', roles),
-        dash_save_perm: getPermissions('can_save_dash', 'Superset', roles),
-        dash_share_perm: getPermissions(
-          'can_share_dashboard',
-          'Superset',
-          roles,
-        ),
-        superset_can_explore: getPermissions('can_explore', 'Superset', roles),
-        superset_can_share: getPermissions(
-          'can_share_chart',
-          'Superset',
-          roles,
-        ),
-        superset_can_csv: getPermissions('can_csv', 'Superset', roles),
-        slice_can_edit: getPermissions('can_slice', 'Superset', roles),
-        common: {
-          // legacy, please use state.common instead
-          flash_messages: common.flash_messages,
-          conf: common.conf,
-        },
-      },
-      dashboardFilters,
-      nativeFilters,
-      dashboardState: {
-        sliceIds: Array.from(sliceIds),
-        directPathToChild,
-        directPathLastUpdated: Date.now(),
-        focusedFilterField: null,
-        expandedSlices: metadata?.expanded_slices || {},
-        refreshFrequency: 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: dashboardData.css || '',
-        colorNamespace: metadata?.color_namespace || null,
-        colorScheme: metadata?.color_scheme || null,
-        editMode: getPermissions('can_write', 'Dashboard', roles) && editMode,
-        isPublished: dashboardData.published,
-        hasUnsavedChanges: false,
-        maxUndoHistoryExceeded: false,
-        lastModifiedTime: dashboardData.changed_on,
-      },
-      dashboardLayout,
-      messageToasts: [],
-      impressionId: shortid.generate(),
-    },
-  });
-};
diff --git a/superset-frontend/src/dashboard/actions/nativeFilters.ts b/superset-frontend/src/dashboard/actions/nativeFilters.ts
index 2fc5541..fca59f2 100644
--- a/superset-frontend/src/dashboard/actions/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/actions/nativeFilters.ts
@@ -19,16 +19,12 @@
 
 import { makeApi } from '@superset-ui/core';
 import { Dispatch } from 'redux';
-import {
-  Filter,
-  FilterConfiguration,
-} from 'src/dashboard/components/nativeFilters/types';
+import { FilterConfiguration } from 'src/dashboard/components/nativeFilters/types';
 import { DataMaskType, DataMaskStateWithId } from 'src/dataMask/types';
 import {
   SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE,
   SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL,
 } from 'src/dataMask/actions';
-import { HYDRATE_DASHBOARD } from './hydrate';
 import { dashboardInfoChanged } from './dashboardInfo';
 import { DashboardInfo, FilterSet } from '../reducers/types';
 
@@ -109,18 +105,6 @@
   }
 };
 
-type BootstrapData = {
-  nativeFilters: {
-    filters: Filter;
-    filtersState: object;
-  };
-};
-
-export interface SetBooststapData {
-  type: typeof HYDRATE_DASHBOARD;
-  data: BootstrapData;
-}
-
 export const setFilterSetsConfiguration = (
   filterSetsConfig: FilterSet[],
 ) => async (dispatch: Dispatch, getState: () => any) => {
@@ -189,5 +173,4 @@
   | SetFilterSetsConfigBegin
   | SetFilterSetsConfigComplete
   | SetFilterSetsConfigFail
-  | SaveFilterSets
-  | SetBooststapData;
+  | SaveFilterSets;
diff --git a/superset-frontend/src/dashboard/components/DashboardGrid.jsx b/superset-frontend/src/dashboard/components/DashboardGrid.jsx
index 9fb0fb5..6889c91 100644
--- a/superset-frontend/src/dashboard/components/DashboardGrid.jsx
+++ b/superset-frontend/src/dashboard/components/DashboardGrid.jsx
@@ -123,6 +123,7 @@
       width,
       isComponentVisible,
     } = this.props;
+
     const columnPlusGutterWidth =
       (width + GRID_GUTTER_SIZE) / GRID_COLUMN_COUNT;
 
diff --git a/superset-frontend/src/dashboard/components/SaveModal.tsx b/superset-frontend/src/dashboard/components/SaveModal.tsx
index 1d3141d..0bbc327 100644
--- a/superset-frontend/src/dashboard/components/SaveModal.tsx
+++ b/superset-frontend/src/dashboard/components/SaveModal.tsx
@@ -140,7 +140,7 @@
     // check refresh frequency is for current session or persist
     const refreshFrequency = shouldPersistRefreshFrequency
       ? currentRefreshFrequency
-      : dashboardInfo.metadata?.refresh_frequency; // eslint-disable camelcase
+      : dashboardInfo.metadata.refresh_frequency; // eslint-disable camelcase
 
     const data = {
       positions,
diff --git a/superset-frontend/src/dashboard/containers/DashboardHeader.jsx b/superset-frontend/src/dashboard/containers/DashboardHeader.jsx
index 6351561..3ffd51a 100644
--- a/superset-frontend/src/dashboard/containers/DashboardHeader.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardHeader.jsx
@@ -85,7 +85,7 @@
     maxUndoHistoryExceeded: !!dashboardState.maxUndoHistoryExceeded,
     lastModifiedTime: Math.max(
       dashboardState.lastModifiedTime,
-      dashboardInfo.last_modified_time,
+      dashboardInfo.lastModifiedTime,
     ),
     editMode: !!dashboardState.editMode,
     slug: dashboardInfo.slug,
diff --git a/superset-frontend/src/dashboard/containers/DashboardPage.tsx b/superset-frontend/src/dashboard/containers/DashboardPage.tsx
deleted file mode 100644
index 6e391e0..0000000
--- a/superset-frontend/src/dashboard/containers/DashboardPage.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-/**
- * 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 React, { useEffect, useState, FC } from 'react';
-import { useDispatch } from 'react-redux';
-import Loading from 'src/components/Loading';
-import ErrorBoundary from 'src/components/ErrorBoundary';
-import {
-  useDashboard,
-  useDashboardCharts,
-  useDashboardDatasets,
-} from 'src/common/hooks/apiResources';
-import { ResourceStatus } from 'src/common/hooks/apiResources/apiResources';
-import { usePrevious } from 'src/common/hooks/usePrevious';
-import { hydrateDashboard } from 'src/dashboard/actions/hydrate';
-import DashboardContainer from 'src/dashboard/containers/Dashboard';
-
-interface DashboardRouteProps {
-  dashboardIdOrSlug: string;
-}
-
-const DashboardPage: FC<DashboardRouteProps> = ({
-  dashboardIdOrSlug, // eventually get from react router
-}) => {
-  const dispatch = useDispatch();
-  const [isLoaded, setLoaded] = useState(false);
-  const dashboardResource = useDashboard(dashboardIdOrSlug);
-  const chartsResource = useDashboardCharts(dashboardIdOrSlug);
-  const datasetsResource = useDashboardDatasets(dashboardIdOrSlug);
-  const isLoading = [dashboardResource, chartsResource, datasetsResource].some(
-    resource => resource.status === ResourceStatus.LOADING,
-  );
-  const wasLoading = usePrevious(isLoading);
-  const error = [dashboardResource, chartsResource, datasetsResource].find(
-    resource => resource.status === ResourceStatus.ERROR,
-  )?.error;
-  useEffect(() => {
-    if (
-      wasLoading &&
-      dashboardResource.status === ResourceStatus.COMPLETE &&
-      chartsResource.status === ResourceStatus.COMPLETE &&
-      datasetsResource.status === ResourceStatus.COMPLETE
-    ) {
-      dispatch(
-        hydrateDashboard(
-          dashboardResource.result,
-          chartsResource.result,
-          datasetsResource.result,
-        ),
-      );
-      setLoaded(true);
-    }
-  }, [
-    dispatch,
-    wasLoading,
-    dashboardResource,
-    chartsResource,
-    datasetsResource,
-  ]);
-
-  if (error) throw error; // caught in error boundary
-
-  if (!isLoaded) return <Loading />;
-  return <DashboardContainer />;
-};
-
-const DashboardPageWithErrorBoundary = ({
-  dashboardIdOrSlug,
-}: DashboardRouteProps) => (
-  <ErrorBoundary>
-    <DashboardPage dashboardIdOrSlug={dashboardIdOrSlug} />
-  </ErrorBoundary>
-);
-
-export default DashboardPageWithErrorBoundary;
diff --git a/superset-frontend/src/dashboard/index.jsx b/superset-frontend/src/dashboard/index.jsx
index 1a287c0..5d696bd 100644
--- a/superset-frontend/src/dashboard/index.jsx
+++ b/superset-frontend/src/dashboard/index.jsx
@@ -22,6 +22,7 @@
 import { createStore, applyMiddleware, compose } from 'redux';
 import { initFeatureFlags } from 'src/featureFlags';
 import { initEnhancer } from '../reduxUtils';
+import getInitialState from './reducers/getInitialState';
 import rootReducer from './reducers/index';
 import logger from '../middleware/loggerMiddleware';
 import App from './App';
@@ -29,16 +30,10 @@
 const appContainer = document.getElementById('app');
 const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
 initFeatureFlags(bootstrapData.common.feature_flags);
-
-const initialState = {
-  user: bootstrapData.user,
-  common: bootstrapData.common,
-  datasources: bootstrapData.datasources,
-};
-
+const initState = getInitialState(bootstrapData);
 const store = createStore(
   rootReducer,
-  initialState,
+  initState,
   compose(applyMiddleware(thunk, logger), initEnhancer(false)),
 );
 
diff --git a/superset-frontend/src/dashboard/reducers/dashboardFilters.js b/superset-frontend/src/dashboard/reducers/dashboardFilters.js
index d31af82..f508c1b 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardFilters.js
+++ b/superset-frontend/src/dashboard/reducers/dashboardFilters.js
@@ -25,7 +25,6 @@
   UPDATE_LAYOUT_COMPONENTS,
   UPDATE_DASHBOARD_FILTERS_SCOPE,
 } from '../actions/dashboardFilters';
-import { HYDRATE_DASHBOARD } from '../actions/hydrate';
 import { TIME_RANGE } from '../../visualizations/FilterBox/FilterBox';
 import { DASHBOARD_ROOT_ID } from '../util/constants';
 import getFilterConfigsFromFormdata from '../util/getFilterConfigsFromFormdata';
@@ -162,10 +161,6 @@
 
     return updatedFilters;
   }
-  if (action.type === HYDRATE_DASHBOARD) {
-    return action.data.dashboardFilters;
-  }
-
   if (action.type in actionHandlers) {
     const updatedFilters = {
       ...dashboardFilters,
@@ -173,6 +168,7 @@
         dashboardFilters[action.chartId],
       ),
     };
+
     if (CHANGE_FILTER_VALUE_ACTIONS.includes(action.type)) {
       buildActiveFilters({ dashboardFilters: updatedFilters });
     }
diff --git a/superset-frontend/src/dashboard/reducers/dashboardInfo.js b/superset-frontend/src/dashboard/reducers/dashboardInfo.js
index fdd39fa..01346d7 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardInfo.js
+++ b/superset-frontend/src/dashboard/reducers/dashboardInfo.js
@@ -18,7 +18,6 @@
  */
 
 import { DASHBOARD_INFO_UPDATED } from '../actions/dashboardInfo';
-import { HYDRATE_DASHBOARD } from '../actions/hydrate';
 
 export default function dashboardStateReducer(state = {}, action) {
   switch (action.type) {
@@ -27,13 +26,7 @@
         ...state,
         ...action.newInfo,
         // server-side compare last_modified_time in second level
-        last_modified_time: Math.round(new Date().getTime() / 1000),
-      };
-    case HYDRATE_DASHBOARD:
-      return {
-        ...state,
-        ...action.data.dashboardInfo,
-        // set async api call data
+        lastModifiedTime: Math.round(new Date().getTime() / 1000),
       };
     default:
       return state;
diff --git a/superset-frontend/src/dashboard/reducers/dashboardLayout.js b/superset-frontend/src/dashboard/reducers/dashboardLayout.js
index 30ad33c..ffc5613 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardLayout.js
+++ b/superset-frontend/src/dashboard/reducers/dashboardLayout.js
@@ -43,15 +43,7 @@
   DASHBOARD_TITLE_CHANGED,
 } from '../actions/dashboardLayout';
 
-import { HYDRATE_DASHBOARD } from '../actions/hydrate';
-
 const actionHandlers = {
-  [HYDRATE_DASHBOARD](state, action) {
-    return {
-      ...action.data.dashboardLayout.present,
-    };
-  },
-
   [UPDATE_COMPONENTS](state, action) {
     const {
       payload: { nextComponents },
diff --git a/superset-frontend/src/dashboard/reducers/dashboardState.js b/superset-frontend/src/dashboard/reducers/dashboardState.js
index 6f16208..b948e2c 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardState.js
+++ b/superset-frontend/src/dashboard/reducers/dashboardState.js
@@ -36,13 +36,9 @@
   SET_FOCUSED_FILTER_FIELD,
   UNSET_FOCUSED_FILTER_FIELD,
 } from '../actions/dashboardState';
-import { HYDRATE_DASHBOARD } from '../actions/hydrate';
 
 export default function dashboardStateReducer(state = {}, action) {
   const actionHandlers = {
-    [HYDRATE_DASHBOARD]() {
-      return { ...state, ...action.data.dashboardState };
-    },
     [UPDATE_CSS]() {
       return { ...state, css: action.css };
     },
diff --git a/superset-frontend/src/dashboard/reducers/datasources.js b/superset-frontend/src/dashboard/reducers/datasources.js
index 616c3c1..0cf7e1b 100644
--- a/superset-frontend/src/dashboard/reducers/datasources.js
+++ b/superset-frontend/src/dashboard/reducers/datasources.js
@@ -17,29 +17,22 @@
  * under the License.
  */
 import { SET_DATASOURCE } from '../actions/datasources';
-import { HYDRATE_DASHBOARD } from '../actions/hydrate';
 
 export default function datasourceReducer(datasources = {}, action) {
   const actionHandlers = {
-    [HYDRATE_DASHBOARD]() {
-      return action.data.datasources;
-    },
     [SET_DATASOURCE]() {
       return action.datasource;
     },
   };
 
   if (action.type in actionHandlers) {
-    if (action.key) {
-      return {
-        ...datasources,
-        [action.key]: actionHandlers[action.type](
-          datasources[action.key],
-          action,
-        ),
-      };
-    }
-    return actionHandlers[action.type]();
+    return {
+      ...datasources,
+      [action.key]: actionHandlers[action.type](
+        datasources[action.key],
+        action,
+      ),
+    };
   }
   return datasources;
 }
diff --git a/superset-frontend/src/dashboard/reducers/getInitialState.js b/superset-frontend/src/dashboard/reducers/getInitialState.js
new file mode 100644
index 0000000..19ea54e
--- /dev/null
+++ b/superset-frontend/src/dashboard/reducers/getInitialState.js
@@ -0,0 +1,312 @@
+/**
+ * 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(),
+  };
+}
diff --git a/superset-frontend/src/dashboard/reducers/index.js b/superset-frontend/src/dashboard/reducers/index.js
index 28804a7..481f167 100644
--- a/superset-frontend/src/dashboard/reducers/index.js
+++ b/superset-frontend/src/dashboard/reducers/index.js
@@ -32,8 +32,6 @@
 const impressionId = (state = '') => state;
 
 export default combineReducers({
-  user: (state = null) => state,
-  common: (state = null) => state,
   charts,
   datasources,
   dashboardInfo,
diff --git a/superset-frontend/src/dashboard/reducers/nativeFilters.ts b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
index 8b8dc4f..d860cbe 100644
--- a/superset-frontend/src/dashboard/reducers/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
@@ -24,7 +24,6 @@
 } from 'src/dashboard/actions/nativeFilters';
 import { FilterSet, NativeFiltersState } from './types';
 import { FilterConfiguration } from '../components/nativeFilters/types';
-import { HYDRATE_DASHBOARD } from '../actions/hydrate';
 
 export function getInitialState({
   filterSetsConfig,
@@ -70,10 +69,6 @@
 ) {
   const { filterSets } = state;
   switch (action.type) {
-    case HYDRATE_DASHBOARD:
-      return {
-        filters: action.data.nativeFilters.filters,
-      };
     case SAVE_FILTER_SETS:
       return {
         ...state,
diff --git a/superset-frontend/src/dashboard/reducers/sliceEntities.js b/superset-frontend/src/dashboard/reducers/sliceEntities.js
index 70b66db..f34a0b6 100644
--- a/superset-frontend/src/dashboard/reducers/sliceEntities.js
+++ b/superset-frontend/src/dashboard/reducers/sliceEntities.js
@@ -23,7 +23,6 @@
   FETCH_ALL_SLICES_STARTED,
   SET_ALL_SLICES,
 } from '../actions/sliceEntities';
-import { HYDRATE_DASHBOARD } from '../actions/hydrate';
 
 export const initSliceEntities = {
   slices: {},
@@ -37,11 +36,6 @@
   action,
 ) {
   const actionHandlers = {
-    [HYDRATE_DASHBOARD]() {
-      return {
-        ...action.data.sliceEntities,
-      };
-    },
     [FETCH_ALL_SLICES_STARTED]() {
       return {
         ...state,
diff --git a/superset-frontend/src/dashboard/reducers/undoableDashboardLayout.js b/superset-frontend/src/dashboard/reducers/undoableDashboardLayout.js
index 2edb51d..49e0186 100644
--- a/superset-frontend/src/dashboard/reducers/undoableDashboardLayout.js
+++ b/superset-frontend/src/dashboard/reducers/undoableDashboardLayout.js
@@ -29,17 +29,13 @@
   HANDLE_COMPONENT_DROP,
 } from '../actions/dashboardLayout';
 
-import { HYDRATE_DASHBOARD } from '../actions/hydrate';
-
 import dashboardLayout from './dashboardLayout';
 
 export default undoable(dashboardLayout, {
   // +1 because length of history seems max out at limit - 1
   // +1 again so we can detect if we've exceeded the limit
   limit: UNDO_LIMIT + 2,
-  ignoreInitialState: true,
   filter: includeAction([
-    HYDRATE_DASHBOARD,
     UPDATE_COMPONENTS,
     DELETE_COMPONENT,
     CREATE_COMPONENT,
diff --git a/superset-frontend/src/dashboard/util/getPermissions.ts b/superset-frontend/src/dashboard/util/getPermissions.ts
deleted file mode 100644
index 0208fd6..0000000
--- a/superset-frontend/src/dashboard/util/getPermissions.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * 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 memoizeOne from 'memoize-one';
-
-const findPermissions = (perm: string, view: string, roles: object) => {
-  const roleList = Object.entries(roles);
-  if (roleList.length === 0) return false;
-  let bool;
-
-  roleList.forEach(([role, permissions]) => {
-    bool = Boolean(
-      permissions.find(
-        (permission: Array<string>) =>
-          permission[0] === perm && permission[1] === view,
-      ),
-    );
-  });
-  return bool;
-};
-
-const getPermissions = memoizeOne(findPermissions);
-
-export default getPermissions;
diff --git a/superset-frontend/src/types/Dashboard.ts b/superset-frontend/src/types/Dashboard.ts
deleted file mode 100644
index 9608cc1..0000000
--- a/superset-frontend/src/types/Dashboard.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * 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 Owner from './Owner';
-import Role from './Role';
-
-type Dashboard = {
-  id: number;
-  slug: string;
-  url: string;
-  dashboard_title: string;
-  thumbnail_url: string;
-  published: boolean;
-  css: string;
-  json_metadata: string;
-  position_json: string;
-  changed_by_name: string;
-  changed_by: Owner;
-  changed_on: string;
-  charts: string[]; // just chart names, unfortunately...
-  owners: Owner[];
-  roles: Role[];
-};
-
-export default Dashboard;
diff --git a/superset-frontend/src/types/Role.ts b/superset-frontend/src/types/Role.ts
deleted file mode 100644
index 54f6876..0000000
--- a/superset-frontend/src/types/Role.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * 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.
- */
-type Role = {
-  id: number;
-  name: string;
-};
-
-export default Role;
diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py
index ef11d84..3853a7c 100644
--- a/superset/charts/schemas.py
+++ b/superset/charts/schemas.py
@@ -139,7 +139,6 @@
     slice_name = fields.String(description=slice_name_description)
     cache_timeout = fields.Integer(description=cache_timeout_description)
     changed_on = fields.String(description=changed_on_description)
-    modified = fields.String()
     datasource = fields.String(description=datasource_name_description)
     description = fields.String(description=description_description)
     description_markeddown = fields.String(
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index 458e59c..9d6258e 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -307,7 +307,7 @@
         except DashboardNotFoundError:
             return self.response_404()
 
-    @expose("/<id_or_slug>/charts", methods=["GET"])
+    @expose("/<pk>/charts", methods=["GET"])
     @protect()
     @safe
     @statsd_metrics
@@ -315,7 +315,7 @@
         action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.get_charts",
         log_to_statsd=False,
     )
-    def get_charts(self, id_or_slug: str) -> Response:
+    def get_charts(self, pk: int) -> Response:
         """Gets the chart definitions for a given dashboard
         ---
         get:
@@ -324,8 +324,8 @@
           parameters:
           - in: path
             schema:
-              type: string
-            name: id_or_slug
+              type: integer
+            name: pk
           responses:
             200:
               description: Dashboard chart definitions
@@ -348,16 +348,8 @@
               $ref: '#/components/responses/404'
         """
         try:
-            charts = DashboardDAO.get_charts_for_dashboard(id_or_slug)
+            charts = DashboardDAO.get_charts_for_dashboard(pk)
             result = [self.chart_entity_response_schema.dump(chart) for chart in charts]
-
-            if is_feature_enabled("REMOVE_SLICE_LEVEL_LABEL_COLORS"):
-                # dashboard metadata has dashboard-level label_colors,
-                # so remove slice-level label_colors from its form_data
-                for chart in result:
-                    form_data = chart.get("form_data")
-                    form_data.pop("label_colors", None)
-
             return self.response(200, result=result)
         except DashboardNotFoundError:
             return self.response_404()
diff --git a/superset/dashboards/dao.py b/superset/dashboards/dao.py
index 800ee66..0951f47 100644
--- a/superset/dashboards/dao.py
+++ b/superset/dashboards/dao.py
@@ -82,12 +82,12 @@
         return data
 
     @staticmethod
-    def get_charts_for_dashboard(id_or_slug: str) -> List[Slice]:
+    def get_charts_for_dashboard(dashboard_id: int) -> List[Slice]:
         query = (
             db.session.query(Dashboard)
             .outerjoin(Slice, Dashboard.slices)
             .outerjoin(Slice.table)
-            .filter(id_or_slug_filter(id_or_slug))
+            .filter(Dashboard.id == dashboard_id)
             .options(contains_eager(Dashboard.slices))
         )
         # Apply dashboard base filters
diff --git a/superset/views/core.py b/superset/views/core.py
index af2cabe..0093f50 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -800,7 +800,6 @@
             "slice": slc.data if slc else None,
             "standalone": standalone_mode,
             "user_id": user_id,
-            "user": bootstrap_user_data(g.user, include_perms=True),
             "forced_height": request.args.get("height"),
             "common": common_bootstrap_payload(),
         }
@@ -1812,11 +1811,13 @@
         if not dashboard:
             abort(404)
 
+        data = dashboard.full_data()
+
         if config["ENABLE_ACCESS_REQUEST"]:
-            for datasource in dashboard.datasources:
+            for datasource in data["datasources"].values():
                 datasource = ConnectorRegistry.get_datasource(
-                    datasource_type=datasource.type,
-                    datasource_id=datasource.id,
+                    datasource_type=datasource["type"],
+                    datasource_id=datasource["id"],
                     session=db.session(),
                 )
                 if datasource and not security_manager.can_access_datasource(
@@ -1835,6 +1836,10 @@
         dash_edit_perm = check_ownership(
             dashboard, raise_if_false=False
         ) and security_manager.can_access("can_save_dash", "Superset")
+        dash_save_perm = security_manager.can_access("can_save_dash", "Superset")
+        superset_can_explore = security_manager.can_access("can_explore", "Superset")
+        superset_can_csv = security_manager.can_access("can_csv", "Superset")
+        slice_can_edit = security_manager.can_access("can_edit", "SliceModelView")
         standalone_mode = ReservedUrlParameters.is_standalone_mode()
         edit_mode = (
             request.args.get(utils.ReservedUrlParameters.EDIT_MODE.value) == "true"
@@ -1847,11 +1852,41 @@
             edit_mode=edit_mode,
         )
 
-        bootstrap_data = {
-            "user": bootstrap_user_data(g.user, include_perms=True),
-            "common": common_bootstrap_payload(),
+        if is_feature_enabled("REMOVE_SLICE_LEVEL_LABEL_COLORS"):
+            # dashboard metadata has dashboard-level label_colors,
+            # so remove slice-level label_colors from its form_data
+            for slc in data["slices"]:
+                form_data = slc.get("form_data")
+                form_data.pop("label_colors", None)
+
+        url_params = {
+            key: value
+            for key, value in request.args.items()
+            if key not in [param.value for param in utils.ReservedUrlParameters]
         }
 
+        bootstrap_data = {
+            "user_id": g.user.get_id(),
+            "common": common_bootstrap_payload(),
+            "editMode": edit_mode,
+            "urlParams": url_params,
+            "dashboard_data": {
+                **data["dashboard"],
+                "standalone_mode": standalone_mode,
+                "dash_save_perm": dash_save_perm,
+                "dash_edit_perm": dash_edit_perm,
+                "superset_can_explore": superset_can_explore,
+                "superset_can_csv": superset_can_csv,
+                "slice_can_edit": slice_can_edit,
+            },
+            "datasources": data["datasources"],
+        }
+
+        if request.args.get("json") == "true":
+            return json_success(
+                json.dumps(bootstrap_data, default=utils.pessimistic_json_iso_dttm_ser)
+            )
+
         return self.render_template(
             "superset/dashboard.html",
             entry="dashboard",
diff --git a/tests/dashboard_tests.py b/tests/dashboard_tests.py
index 97d9e7b..f243400 100644
--- a/tests/dashboard_tests.py
+++ b/tests/dashboard_tests.py
@@ -128,10 +128,25 @@
         dash_count_before = db.session.query(func.count(Dashboard.id)).first()[0]
         url = "/dashboard/new/"
         resp = self.get_resp(url)
+        self.assertIn("[ untitled dashboard ]", resp)
         dash_count_after = db.session.query(func.count(Dashboard.id)).first()[0]
         self.assertEqual(dash_count_before + 1, dash_count_after)
 
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
+    def test_dashboard_modes(self):
+        self.login(username="admin")
+        dash = db.session.query(Dashboard).filter_by(slug="births").first()
+        url = dash.url
+        if dash.url.find("?") == -1:
+            url += "?"
+        else:
+            url += "&"
+        resp = self.get_resp(url + "edit=true&standalone=true")
+        self.assertIn("editMode&#34;: true", resp)
+        self.assertIn("standalone_mode&#34;: true", resp)
+        self.assertIn('<body class="standalone">', resp)
+
+    @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
     def test_save_dash(self, username="admin"):
         self.login(username=username)
         dash = db.session.query(Dashboard).filter_by(slug="births").first()
@@ -175,6 +190,9 @@
         self.assertIn("world_health", new_url)
         self.assertNotIn("preselect_filters", new_url)
 
+        resp = self.get_resp(new_url)
+        self.assertIn("North America", resp)
+
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     def test_save_dash_with_invalid_filters(self, username="admin"):
         self.login(username=username)
@@ -390,6 +408,8 @@
         resp = self.get_resp("/api/v1/dashboard/")
         self.assertIn("/superset/dashboard/births/", resp)
 
+        self.assertIn("Births", self.get_resp("/superset/dashboard/births/"))
+
         # Confirm that public doesn't have access to other datasets.
         resp = self.get_resp("/api/v1/chart/")
         self.assertNotIn("wb_health_population", resp)
diff --git a/tests/dashboards/api_tests.py b/tests/dashboards/api_tests.py
index 4a329e4..fadabe2 100644
--- a/tests/dashboards/api_tests.py
+++ b/tests/dashboards/api_tests.py
@@ -239,22 +239,6 @@
         )
 
     @pytest.mark.usefixtures("create_dashboards")
-    def test_get_dashboard_charts_by_slug(self):
-        """
-        Dashboard API: Test getting charts belonging to a dashboard
-        """
-        self.login(username="admin")
-        dashboard = self.dashboards[0]
-        uri = f"api/v1/dashboard/{dashboard.slug}/charts"
-        response = self.get_assert_metric(uri, "get_charts")
-        self.assertEqual(response.status_code, 200)
-        data = json.loads(response.data.decode("utf-8"))
-        self.assertEqual(len(data["result"]), 1)
-        self.assertEqual(
-            data["result"][0]["slice_name"], dashboard.slices[0].slice_name
-        )
-
-    @pytest.mark.usefixtures("create_dashboards")
     def test_get_dashboard_charts_not_found(self):
         """
         Dashboard API: Test getting charts belonging to a dashboard that does not exist