You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2023/04/06 13:10:48 UTC

[superset] branch master updated: feat: Shows user charts by default when editing a dashboard (#23547)

This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new bccd2670cc feat: Shows user charts by default when editing a dashboard (#23547)
bccd2670cc is described below

commit bccd2670cc1d85eeba748ac2cad1ea6fe751473e
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Thu Apr 6 10:10:37 2023 -0300

    feat: Shows user charts by default when editing a dashboard (#23547)
---
 .../cypress/integration/dashboard/editmode.test.ts |   2 +-
 .../src/dashboard/actions/sliceEntities.js         | 173 --------------------
 .../src/dashboard/actions/sliceEntities.test.js    | 102 ------------
 .../src/dashboard/actions/sliceEntities.ts         | 178 +++++++++++++++++++++
 .../src/dashboard/components/SliceAdder.jsx        | 151 +++++++++++++----
 .../src/dashboard/components/SliceAdder.test.jsx   |  56 +++----
 .../src/dashboard/containers/SliceAdder.jsx        |  14 +-
 .../src/dashboard/reducers/sliceEntities.js        |  12 +-
 .../src/dashboard/reducers/sliceEntities.test.js   |   4 +-
 superset-frontend/src/dashboard/types.ts           |  21 +++
 superset-frontend/src/utils/localStorageHelpers.ts |   2 +
 11 files changed, 365 insertions(+), 350 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/editmode.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/editmode.test.ts
index 4251b6a7ae..50fd153146 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/editmode.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/editmode.test.ts
@@ -752,7 +752,7 @@ describe('Dashboard edit', () => {
     });
 
     it('should remove added charts', () => {
-      dragComponent('Pivot Table');
+      dragComponent('Unicode Cloud');
       cy.getBySel('dashboard-component-chart-holder').should('have.length', 1);
       cy.getBySel('dashboard-delete-component-button').click();
       cy.getBySel('dashboard-component-chart-holder').should('have.length', 0);
diff --git a/superset-frontend/src/dashboard/actions/sliceEntities.js b/superset-frontend/src/dashboard/actions/sliceEntities.js
deleted file mode 100644
index 99a0627211..0000000000
--- a/superset-frontend/src/dashboard/actions/sliceEntities.js
+++ /dev/null
@@ -1,173 +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 camelcase: 0 */
-import { FeatureFlag, SupersetClient, t } from '@superset-ui/core';
-import rison from 'rison';
-
-import { addDangerToast } from 'src/components/MessageToasts/actions';
-import { getClientErrorObject } from 'src/utils/getClientErrorObject';
-import { isFeatureEnabled } from 'src/featureFlags';
-
-export const SET_ALL_SLICES = 'SET_ALL_SLICES';
-const FETCH_SLICES_PAGE_SIZE = 200;
-
-export function getDatasourceParameter(datasourceId, datasourceType) {
-  return `${datasourceId}__${datasourceType}`;
-}
-
-export function setAllSlices(slices) {
-  return { type: SET_ALL_SLICES, payload: { slices } };
-}
-
-export const FETCH_ALL_SLICES_STARTED = 'FETCH_ALL_SLICES_STARTED';
-export function fetchAllSlicesStarted() {
-  return { type: FETCH_ALL_SLICES_STARTED };
-}
-
-export const FETCH_ALL_SLICES_FAILED = 'FETCH_ALL_SLICES_FAILED';
-export function fetchAllSlicesFailed(error) {
-  return { type: FETCH_ALL_SLICES_FAILED, payload: { error } };
-}
-
-export function fetchSlices(
-  userId,
-  dispatch,
-  filter_value,
-  sortColumn = 'changed_on',
-  slices = {},
-) {
-  const additional_filters = filter_value
-    ? [{ col: 'slice_name', opr: 'chart_all_text', value: filter_value }]
-    : [];
-
-  if (isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS)) {
-    additional_filters.push({
-      col: 'viz_type',
-      opr: 'neq',
-      value: 'filter_box',
-    });
-  }
-
-  const cloneSlices = { ...slices };
-
-  return SupersetClient.get({
-    endpoint: `/api/v1/chart/?q=${rison.encode({
-      columns: [
-        'changed_on_delta_humanized',
-        'changed_on_utc',
-        'datasource_id',
-        'datasource_type',
-        'datasource_url',
-        'datasource_name_text',
-        'description_markeddown',
-        'description',
-        'id',
-        'params',
-        'slice_name',
-        'thumbnail_url',
-        'url',
-        'viz_type',
-      ],
-      filters: [...additional_filters],
-      page_size: FETCH_SLICES_PAGE_SIZE,
-      order_column:
-        sortColumn === 'changed_on' ? 'changed_on_delta_humanized' : sortColumn,
-      order_direction: sortColumn === 'changed_on' ? 'desc' : 'asc',
-    })}`,
-  })
-    .then(({ json }) => {
-      const { result } = json;
-      result.forEach(slice => {
-        let form_data = JSON.parse(slice.params);
-        form_data = {
-          ...form_data,
-          // force using datasource stored in relational table prop
-          datasource:
-            getDatasourceParameter(
-              slice.datasource_id,
-              slice.datasource_type,
-            ) || form_data.datasource,
-        };
-        cloneSlices[slice.id] = {
-          slice_id: slice.id,
-          slice_url: slice.url,
-          slice_name: slice.slice_name,
-          form_data,
-          datasource_name: slice.datasource_name_text,
-          datasource_url: slice.datasource_url,
-          datasource_id: slice.datasource_id,
-          datasource_type: slice.datasource_type,
-          changed_on: new Date(slice.changed_on_utc).getTime(),
-          description: slice.description,
-          description_markdown: slice.description_markeddown,
-          viz_type: slice.viz_type,
-          modified: slice.changed_on_delta_humanized,
-          changed_on_humanized: slice.changed_on_delta_humanized,
-          thumbnail_url: slice.thumbnail_url,
-        };
-      });
-
-      return dispatch(setAllSlices(cloneSlices));
-    })
-    .catch(errorResponse =>
-      getClientErrorObject(errorResponse).then(({ error }) => {
-        dispatch(
-          fetchAllSlicesFailed(error || t('Could not fetch all saved charts')),
-        );
-        dispatch(
-          addDangerToast(
-            t('Sorry there was an error fetching saved charts: ') + error,
-          ),
-        );
-      }),
-    );
-}
-
-export function fetchAllSlices(userId) {
-  return (dispatch, getState) => {
-    const { sliceEntities } = getState();
-    if (sliceEntities.lastUpdated === 0) {
-      dispatch(fetchAllSlicesStarted());
-      return fetchSlices(userId, dispatch, undefined);
-    }
-
-    return dispatch(setAllSlices(sliceEntities.slices));
-  };
-}
-
-export function fetchSortedSlices(userId, order_column) {
-  return dispatch => {
-    dispatch(fetchAllSlicesStarted());
-    return fetchSlices(userId, dispatch, undefined, order_column);
-  };
-}
-
-export function fetchFilteredSlices(userId, filter_value) {
-  return (dispatch, getState) => {
-    dispatch(fetchAllSlicesStarted());
-    const { sliceEntities } = getState();
-    return fetchSlices(
-      userId,
-      dispatch,
-      filter_value,
-      undefined,
-      sliceEntities.slices,
-    );
-  };
-}
diff --git a/superset-frontend/src/dashboard/actions/sliceEntities.test.js b/superset-frontend/src/dashboard/actions/sliceEntities.test.js
deleted file mode 100644
index 1f3d0cb440..0000000000
--- a/superset-frontend/src/dashboard/actions/sliceEntities.test.js
+++ /dev/null
@@ -1,102 +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 sinon from 'sinon';
-import { SupersetClient } from '@superset-ui/core';
-
-import {
-  FETCH_ALL_SLICES_STARTED,
-  fetchSortedSlices,
-  fetchFilteredSlices,
-  fetchAllSlices,
-} from './sliceEntities';
-
-describe('slice entity actions', () => {
-  const mockState = {
-    sliceEntities: { slices: {} },
-    isLoading: true,
-    errorMessage: null,
-    lastUpdated: 0,
-  };
-
-  function setup(stateOverrides) {
-    const state = { ...mockState, ...stateOverrides };
-    const getState = sinon.spy(() => state);
-    const dispatch = sinon.spy();
-
-    return { getState, dispatch, state };
-  }
-
-  let spy;
-
-  beforeEach(() => {
-    spy = sinon.spy(SupersetClient);
-  });
-
-  afterEach(() => {
-    sinon.restore();
-  });
-
-  describe('fetchSortedSlices', () => {
-    it('should dispatch an fetchAllSlicesStarted action', async () => {
-      const { dispatch } = setup();
-      const thunk1 = fetchSortedSlices('userId', false, 'orderColumn');
-      await thunk1(dispatch);
-      expect(dispatch.getCall(0).args[0]).toEqual({
-        type: FETCH_ALL_SLICES_STARTED,
-      });
-      expect(spy.get.callCount).toBe(1);
-    });
-  });
-
-  describe('fetchFilteredSlices', () => {
-    it('should dispatch an fetchAllSlicesStarted action', async () => {
-      const { dispatch, getState } = setup();
-      const thunk1 = fetchFilteredSlices('userId', 'filter_value');
-      await thunk1(dispatch, getState);
-      expect(dispatch.getCall(0).args[0]).toEqual({
-        type: FETCH_ALL_SLICES_STARTED,
-      });
-      expect(spy.get.callCount).toBe(1);
-    });
-  });
-
-  describe('fetchAllSlices', () => {
-    it('should not trigger fetchSlices when sliceEntities lastUpdate is not 0', async () => {
-      const { dispatch, getState } = setup({
-        sliceEntities: { slices: {}, lastUpdated: 1 },
-      });
-
-      const thunk1 = fetchAllSlices('userId', 'filter_value');
-      await thunk1(dispatch, getState);
-
-      expect(spy.get.callCount).toBe(0);
-    });
-
-    it('should trigger fetchSlices when sliceEntities lastUpdate is 0', async () => {
-      const { dispatch, getState } = setup({
-        sliceEntities: { slices: {}, lastUpdated: 0 },
-      });
-
-      const thunk1 = fetchAllSlices('userId', false, 'filter_value');
-      await thunk1(dispatch, getState);
-
-      expect(spy.get.callCount).toBe(1);
-    });
-  });
-});
diff --git a/superset-frontend/src/dashboard/actions/sliceEntities.ts b/superset-frontend/src/dashboard/actions/sliceEntities.ts
new file mode 100644
index 0000000000..a30470aad1
--- /dev/null
+++ b/superset-frontend/src/dashboard/actions/sliceEntities.ts
@@ -0,0 +1,178 @@
+/**
+ * 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 rison from 'rison';
+import {
+  DatasourceType,
+  FeatureFlag,
+  SupersetClient,
+  t,
+} from '@superset-ui/core';
+import { addDangerToast } from 'src/components/MessageToasts/actions';
+import { getClientErrorObject } from 'src/utils/getClientErrorObject';
+import { isFeatureEnabled } from 'src/featureFlags';
+import { Dispatch } from 'redux';
+import { Slice } from '../types';
+
+const FETCH_SLICES_PAGE_SIZE = 200;
+
+export function getDatasourceParameter(
+  datasourceId: number,
+  datasourceType: DatasourceType,
+) {
+  return `${datasourceId}__${datasourceType}`;
+}
+
+export const ADD_SLICES = 'ADD_SLICES';
+function addSlices(slices: { [id: number]: Slice }) {
+  return { type: ADD_SLICES, payload: { slices } };
+}
+
+export const SET_SLICES = 'SET_SLICES';
+function setSlices(slices: { [id: number]: Slice }) {
+  return { type: SET_SLICES, payload: { slices } };
+}
+
+export const FETCH_ALL_SLICES_STARTED = 'FETCH_ALL_SLICES_STARTED';
+function fetchAllSlicesStarted() {
+  return { type: FETCH_ALL_SLICES_STARTED };
+}
+
+export const FETCH_ALL_SLICES_FAILED = 'FETCH_ALL_SLICES_FAILED';
+function fetchAllSlicesFailed(error: string) {
+  return { type: FETCH_ALL_SLICES_FAILED, payload: { error } };
+}
+
+const parseResult = (result: any[]) =>
+  result.reduce((slices, slice: any) => {
+    let form_data = JSON.parse(slice.params);
+    form_data = {
+      ...form_data,
+      // force using datasource stored in relational table prop
+      datasource:
+        getDatasourceParameter(slice.datasource_id, slice.datasource_type) ||
+        form_data.datasource,
+    };
+    return {
+      ...slices,
+      [slice.id]: {
+        slice_id: slice.id,
+        slice_url: slice.url,
+        slice_name: slice.slice_name,
+        form_data,
+        datasource_name: slice.datasource_name_text,
+        datasource_url: slice.datasource_url,
+        datasource_id: slice.datasource_id,
+        datasource_type: slice.datasource_type,
+        changed_on: new Date(slice.changed_on_utc).getTime(),
+        description: slice.description,
+        description_markdown: slice.description_markeddown,
+        viz_type: slice.viz_type,
+        modified: slice.changed_on_delta_humanized,
+        changed_on_humanized: slice.changed_on_delta_humanized,
+        thumbnail_url: slice.thumbnail_url,
+        owners: slice.owners,
+        created_by: slice.created_by,
+      },
+    };
+  }, {});
+
+export function updateSlices(slices: { [id: number]: Slice }) {
+  return (dispatch: Dispatch) => {
+    dispatch(setSlices(slices));
+  };
+}
+
+export function fetchSlices(
+  userId?: number,
+  filter_value?: string,
+  sortColumn = 'changed_on',
+) {
+  return (dispatch: Dispatch) => {
+    dispatch(fetchAllSlicesStarted());
+
+    const filters: {
+      col: string;
+      opr: string;
+      value: string | number;
+    }[] = filter_value
+      ? [{ col: 'slice_name', opr: 'chart_all_text', value: filter_value }]
+      : [];
+
+    if (isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS)) {
+      filters.push({
+        col: 'viz_type',
+        opr: 'neq',
+        value: 'filter_box',
+      });
+    }
+
+    if (userId) {
+      filters.push({ col: 'owners', opr: 'rel_m_m', value: userId });
+    }
+
+    return SupersetClient.get({
+      endpoint: `/api/v1/chart/?q=${rison.encode({
+        columns: [
+          'changed_on_delta_humanized',
+          'changed_on_utc',
+          'datasource_id',
+          'datasource_type',
+          'datasource_url',
+          'datasource_name_text',
+          'description_markeddown',
+          'description',
+          'id',
+          'params',
+          'slice_name',
+          'thumbnail_url',
+          'url',
+          'viz_type',
+          'owners.id',
+          'created_by.id',
+        ],
+        filters,
+        page_size: FETCH_SLICES_PAGE_SIZE,
+        order_column:
+          sortColumn === 'changed_on'
+            ? 'changed_on_delta_humanized'
+            : sortColumn,
+        order_direction: sortColumn === 'changed_on' ? 'desc' : 'asc',
+      })}`,
+    })
+      .then(({ json }) => {
+        const { result } = json;
+        const slices = parseResult(result);
+        return dispatch(addSlices(slices));
+      })
+      .catch(errorResponse =>
+        getClientErrorObject(errorResponse).then(({ error }) => {
+          dispatch(
+            fetchAllSlicesFailed(
+              error || t('Could not fetch all saved charts'),
+            ),
+          );
+          dispatch(
+            addDangerToast(
+              t('Sorry there was an error fetching saved charts: ') + error,
+            ),
+          );
+        }),
+      );
+  };
+}
diff --git a/superset-frontend/src/dashboard/components/SliceAdder.jsx b/superset-frontend/src/dashboard/components/SliceAdder.jsx
index 95f6c930ab..617fd32e8f 100644
--- a/superset-frontend/src/dashboard/components/SliceAdder.jsx
+++ b/superset-frontend/src/dashboard/components/SliceAdder.jsx
@@ -28,6 +28,11 @@ import { Select } from 'src/components';
 import Loading from 'src/components/Loading';
 import Button from 'src/components/Button';
 import Icons from 'src/components/Icons';
+import {
+  LocalStorageKeys,
+  getItem,
+  setItem,
+} from 'src/utils/localStorageHelpers';
 import {
   CHART_TYPE,
   NEW_COMPONENT_SOURCE_TYPE,
@@ -37,18 +42,21 @@ import {
   NEW_COMPONENTS_SOURCE_ID,
 } from 'src/dashboard/util/constants';
 import { slicePropShape } from 'src/dashboard/util/propShapes';
-import _ from 'lodash';
+import { debounce, pickBy } from 'lodash';
+import Checkbox from 'src/components/Checkbox';
+import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
 import AddSliceCard from './AddSliceCard';
 import AddSliceDragPreview from './dnd/AddSliceDragPreview';
 import DragDroppable from './dnd/DragDroppable';
 
 const propTypes = {
-  fetchAllSlices: PropTypes.func.isRequired,
+  fetchSlices: PropTypes.func.isRequired,
+  updateSlices: PropTypes.func.isRequired,
   isLoading: PropTypes.bool.isRequired,
   slices: PropTypes.objectOf(slicePropShape).isRequired,
   lastUpdated: PropTypes.number.isRequired,
   errorMessage: PropTypes.string,
-  userId: PropTypes.string.isRequired,
+  userId: PropTypes.number.isRequired,
   selectedSliceIds: PropTypes.arrayOf(PropTypes.number),
   editMode: PropTypes.bool,
   dashboardId: PropTypes.number,
@@ -68,15 +76,20 @@ const KEYS_TO_SORT = {
   changed_on: t('recent'),
 };
 
-const DEFAULT_SORT_KEY = 'changed_on';
+export const DEFAULT_SORT_KEY = 'changed_on';
 
 const DEFAULT_CELL_HEIGHT = 128;
 
 const Controls = styled.div`
-  display: flex;
-  flex-direction: row;
-  padding: ${({ theme }) => theme.gridUnit * 3}px;
-  padding-top: ${({ theme }) => theme.gridUnit * 4}px;
+  ${({ theme }) => `
+    display: flex;
+    flex-direction: row;
+    padding:
+      ${theme.gridUnit * 4}px
+      ${theme.gridUnit * 3}px
+      ${theme.gridUnit * 4}px
+      ${theme.gridUnit * 3}px;
+  `}
 `;
 
 const StyledSelect = styled(Select)`
@@ -133,23 +146,35 @@ class SliceAdder extends React.Component {
       searchTerm: '',
       sortBy: DEFAULT_SORT_KEY,
       selectedSliceIdsSet: new Set(props.selectedSliceIds),
+      showOnlyMyCharts: getItem(
+        LocalStorageKeys.dashboard__editor_show_only_my_charts,
+        true,
+      ),
     };
     this.rowRenderer = this.rowRenderer.bind(this);
     this.searchUpdated = this.searchUpdated.bind(this);
-    this.handleKeyPress = this.handleKeyPress.bind(this);
     this.handleSelect = this.handleSelect.bind(this);
+    this.userIdForFetch = this.userIdForFetch.bind(this);
+    this.onShowOnlyMyCharts = this.onShowOnlyMyCharts.bind(this);
+  }
+
+  userIdForFetch() {
+    return this.state.showOnlyMyCharts ? this.props.userId : undefined;
   }
 
   componentDidMount() {
-    this.slicesRequest = this.props.fetchAllSlices(this.props.userId);
+    this.slicesRequest = this.props.fetchSlices(this.userIdForFetch());
   }
 
   UNSAFE_componentWillReceiveProps(nextProps) {
     const nextState = {};
     if (nextProps.lastUpdated !== this.props.lastUpdated) {
-      nextState.filteredSlices = Object.values(nextProps.slices)
-        .filter(createFilter(this.state.searchTerm, KEYS_TO_FILTERS))
-        .sort(SliceAdder.sortByComparator(this.state.sortBy));
+      nextState.filteredSlices = this.getFilteredSortedSlices(
+        nextProps.slices,
+        this.state.searchTerm,
+        this.state.sortBy,
+        this.state.showOnlyMyCharts,
+      );
     }
 
     if (nextProps.selectedSliceIds !== this.props.selectedSliceIds) {
@@ -162,38 +187,46 @@ class SliceAdder extends React.Component {
   }
 
   componentWillUnmount() {
+    // Clears the redux store keeping only selected items
+    const selectedSlices = pickBy(this.props.slices, value =>
+      this.state.selectedSliceIdsSet.has(value.slice_id),
+    );
+    this.props.updateSlices(selectedSlices);
     if (this.slicesRequest && this.slicesRequest.abort) {
       this.slicesRequest.abort();
     }
   }
 
-  getFilteredSortedSlices(searchTerm, sortBy) {
-    return Object.values(this.props.slices)
+  getFilteredSortedSlices(slices, searchTerm, sortBy, showOnlyMyCharts) {
+    return Object.values(slices)
+      .filter(slice =>
+        showOnlyMyCharts
+          ? (slice.owners &&
+              slice.owners.find(owner => owner.id === this.props.userId)) ||
+            (slice.created_by && slice.created_by.id === this.props.userId)
+          : true,
+      )
       .filter(createFilter(searchTerm, KEYS_TO_FILTERS))
       .sort(SliceAdder.sortByComparator(sortBy));
   }
 
-  handleKeyPress(ev) {
-    if (ev.key === 'Enter') {
-      ev.preventDefault();
-
-      this.searchUpdated(ev.target.value);
-    }
-  }
-
-  handleChange = _.debounce(value => {
+  handleChange = debounce(value => {
     this.searchUpdated(value);
-
-    const { userId } = this.props;
-    this.slicesRequest = this.props.fetchFilteredSlices(userId, value);
+    this.slicesRequest = this.props.fetchSlices(
+      this.userIdForFetch(),
+      value,
+      this.state.sortBy,
+    );
   }, 300);
 
   searchUpdated(searchTerm) {
     this.setState(prevState => ({
       searchTerm,
       filteredSlices: this.getFilteredSortedSlices(
+        this.props.slices,
         searchTerm,
         prevState.sortBy,
+        prevState.showOnlyMyCharts,
       ),
     }));
   }
@@ -202,13 +235,17 @@ class SliceAdder extends React.Component {
     this.setState(prevState => ({
       sortBy,
       filteredSlices: this.getFilteredSortedSlices(
+        this.props.slices,
         prevState.searchTerm,
         sortBy,
+        prevState.showOnlyMyCharts,
       ),
     }));
-
-    const { userId } = this.props;
-    this.slicesRequest = this.props.fetchSortedSlices(userId, sortBy);
+    this.slicesRequest = this.props.fetchSlices(
+      this.userIdForFetch(),
+      this.state.searchTerm,
+      sortBy,
+    );
   }
 
   rowRenderer({ key, index, style }) {
@@ -258,6 +295,29 @@ class SliceAdder extends React.Component {
     );
   }
 
+  onShowOnlyMyCharts(showOnlyMyCharts) {
+    if (!showOnlyMyCharts) {
+      this.slicesRequest = this.props.fetchSlices(
+        undefined,
+        this.state.searchTerm,
+        this.state.sortBy,
+      );
+    }
+    this.setState(prevState => ({
+      showOnlyMyCharts,
+      filteredSlices: this.getFilteredSortedSlices(
+        this.props.slices,
+        prevState.searchTerm,
+        prevState.sortBy,
+        showOnlyMyCharts,
+      ),
+    }));
+    setItem(
+      LocalStorageKeys.dashboard__editor_show_only_my_charts,
+      showOnlyMyCharts,
+    );
+  }
+
   render() {
     return (
       <div
@@ -285,10 +345,13 @@ class SliceAdder extends React.Component {
         </NewChartButtonContainer>
         <Controls>
           <Input
-            placeholder={t('Filter your charts')}
+            placeholder={
+              this.state.showOnlyMyCharts
+                ? t('Filter your charts')
+                : t('Filter charts')
+            }
             className="search-input"
             onChange={ev => this.handleChange(ev.target.value)}
-            onKeyPress={this.handleKeyPress}
             data-test="dashboard-charts-filter-search-input"
           />
           <StyledSelect
@@ -302,6 +365,30 @@ class SliceAdder extends React.Component {
             placeholder={t('Sort by')}
           />
         </Controls>
+        <div
+          css={theme => css`
+            display: flex;
+            flex-direction: row;
+            justify-content: flex-start;
+            align-items: center;
+            gap: ${theme.gridUnit}px;
+            padding: 0 ${theme.gridUnit * 3}px ${theme.gridUnit * 4}px
+              ${theme.gridUnit * 3}px;
+          `}
+        >
+          <Checkbox
+            onChange={this.onShowOnlyMyCharts}
+            checked={this.state.showOnlyMyCharts}
+          />
+          {t('Show only my charts')}
+          <InfoTooltipWithTrigger
+            placement="top"
+            tooltip={t(
+              `You can choose to display all charts that you have access to or only the ones you own.
+              Your filter selection will be saved and remain active until you choose to change it.`,
+            )}
+          />
+        </div>
         {this.props.isLoading && <Loading />}
         {!this.props.isLoading && this.state.filteredSlices.length > 0 && (
           <ChartList>
diff --git a/superset-frontend/src/dashboard/components/SliceAdder.test.jsx b/superset-frontend/src/dashboard/components/SliceAdder.test.jsx
index 72327d63c7..eda368bcaf 100644
--- a/superset-frontend/src/dashboard/components/SliceAdder.test.jsx
+++ b/superset-frontend/src/dashboard/components/SliceAdder.test.jsx
@@ -20,24 +20,26 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import sinon from 'sinon';
 
-import SliceAdder, { ChartList } from 'src/dashboard/components/SliceAdder';
+import SliceAdder, {
+  ChartList,
+  DEFAULT_SORT_KEY,
+} from 'src/dashboard/components/SliceAdder';
 import { sliceEntitiesForDashboard as mockSliceEntities } from 'spec/fixtures/mockSliceEntities';
 import { styledShallow } from 'spec/helpers/theming';
 
+jest.mock('lodash/debounce', () => fn => {
+  // eslint-disable-next-line no-param-reassign
+  fn.throttle = jest.fn();
+  return fn;
+});
+
 describe('SliceAdder', () => {
-  const mockEvent = {
-    key: 'Enter',
-    target: {
-      value: 'mock event target',
-    },
-    preventDefault: () => {},
-  };
   const props = {
     ...mockSliceEntities,
-    fetchAllSlices: () => {},
-    fetchSortedSlices: () => {},
+    fetchSlices: jest.fn(),
+    updateSlices: jest.fn(),
     selectedSliceIds: [127, 128],
-    userId: '1',
+    userId: 1,
   };
   const errorProps = {
     ...props,
@@ -84,16 +86,16 @@ describe('SliceAdder', () => {
 
   it('componentDidMount', () => {
     sinon.spy(SliceAdder.prototype, 'componentDidMount');
-    sinon.spy(props, 'fetchAllSlices');
+    sinon.spy(props, 'fetchSlices');
 
     shallow(<SliceAdder {...props} />, {
       lifecycleExperimental: true,
     });
     expect(SliceAdder.prototype.componentDidMount.calledOnce).toBe(true);
-    expect(props.fetchAllSlices.calledOnce).toBe(true);
+    expect(props.fetchSlices.calledOnce).toBe(true);
 
     SliceAdder.prototype.componentDidMount.restore();
-    props.fetchAllSlices.restore();
+    props.fetchSlices.restore();
   });
 
   describe('UNSAFE_componentWillReceiveProps', () => {
@@ -138,32 +140,30 @@ describe('SliceAdder', () => {
     let wrapper;
     let spy;
     beforeEach(() => {
-      wrapper = shallow(<SliceAdder {...props} />);
+      spy = props.fetchSlices;
+      wrapper = shallow(<SliceAdder {...props} fetchSlices={spy} />);
       wrapper.setState({ filteredSlices: Object.values(props.slices) });
-      spy = sinon.spy(wrapper.instance(), 'getFilteredSortedSlices');
     });
     afterEach(() => {
-      spy.restore();
+      spy.mockReset();
     });
 
     it('searchUpdated', () => {
       const newSearchTerm = 'new search term';
-      wrapper.instance().searchUpdated(newSearchTerm);
-      expect(spy.calledOnce).toBe(true);
-      expect(spy.lastCall.args[0]).toBe(newSearchTerm);
+      wrapper.instance().handleChange(newSearchTerm);
+      expect(spy).toHaveBeenCalled();
+      expect(spy).toHaveBeenCalledWith(
+        props.userId,
+        newSearchTerm,
+        DEFAULT_SORT_KEY,
+      );
     });
 
     it('handleSelect', () => {
       const newSortBy = 'viz_type';
       wrapper.instance().handleSelect(newSortBy);
-      expect(spy.calledOnce).toBe(true);
-      expect(spy.lastCall.args[1]).toBe(newSortBy);
-    });
-
-    it('handleKeyPress', () => {
-      wrapper.instance().handleKeyPress(mockEvent);
-      expect(spy.calledOnce).toBe(true);
-      expect(spy.lastCall.args[0]).toBe(mockEvent.target.value);
+      expect(spy).toHaveBeenCalled();
+      expect(spy).toHaveBeenCalledWith(props.userId, '', newSortBy);
     });
   });
 });
diff --git a/superset-frontend/src/dashboard/containers/SliceAdder.jsx b/superset-frontend/src/dashboard/containers/SliceAdder.jsx
index e3c8e1db87..992098a0da 100644
--- a/superset-frontend/src/dashboard/containers/SliceAdder.jsx
+++ b/superset-frontend/src/dashboard/containers/SliceAdder.jsx
@@ -18,12 +18,7 @@
  */
 import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
-
-import {
-  fetchAllSlices,
-  fetchSortedSlices,
-  fetchFilteredSlices,
-} from '../actions/sliceEntities';
+import { fetchSlices, updateSlices } from '../actions/sliceEntities';
 import SliceAdder from '../components/SliceAdder';
 
 function mapStateToProps(
@@ -32,7 +27,7 @@ function mapStateToProps(
 ) {
   return {
     height: ownProps.height,
-    userId: dashboardInfo.userId,
+    userId: +dashboardInfo.userId,
     dashboardId: dashboardInfo.id,
     selectedSliceIds: dashboardState.sliceIds,
     slices: sliceEntities.slices,
@@ -46,9 +41,8 @@ function mapStateToProps(
 function mapDispatchToProps(dispatch) {
   return bindActionCreators(
     {
-      fetchAllSlices,
-      fetchSortedSlices,
-      fetchFilteredSlices,
+      fetchSlices,
+      updateSlices,
     },
     dispatch,
   );
diff --git a/superset-frontend/src/dashboard/reducers/sliceEntities.js b/superset-frontend/src/dashboard/reducers/sliceEntities.js
index 70b66db724..1a065b11fa 100644
--- a/superset-frontend/src/dashboard/reducers/sliceEntities.js
+++ b/superset-frontend/src/dashboard/reducers/sliceEntities.js
@@ -21,7 +21,8 @@ import { t } from '@superset-ui/core';
 import {
   FETCH_ALL_SLICES_FAILED,
   FETCH_ALL_SLICES_STARTED,
-  SET_ALL_SLICES,
+  ADD_SLICES,
+  SET_SLICES,
 } from '../actions/sliceEntities';
 import { HYDRATE_DASHBOARD } from '../actions/hydrate';
 
@@ -48,7 +49,7 @@ export default function sliceEntitiesReducer(
         isLoading: true,
       };
     },
-    [SET_ALL_SLICES]() {
+    [ADD_SLICES]() {
       return {
         ...state,
         isLoading: false,
@@ -56,6 +57,13 @@ export default function sliceEntitiesReducer(
         lastUpdated: new Date().getTime(),
       };
     },
+    [SET_SLICES]() {
+      return {
+        isLoading: false,
+        slices: { ...action.payload.slices },
+        lastUpdated: new Date().getTime(),
+      };
+    },
     [FETCH_ALL_SLICES_FAILED]() {
       return {
         ...state,
diff --git a/superset-frontend/src/dashboard/reducers/sliceEntities.test.js b/superset-frontend/src/dashboard/reducers/sliceEntities.test.js
index 73b5aebee5..444535d276 100644
--- a/superset-frontend/src/dashboard/reducers/sliceEntities.test.js
+++ b/superset-frontend/src/dashboard/reducers/sliceEntities.test.js
@@ -19,7 +19,7 @@
 import {
   FETCH_ALL_SLICES_FAILED,
   FETCH_ALL_SLICES_STARTED,
-  SET_ALL_SLICES,
+  ADD_SLICES,
 } from 'src/dashboard/actions/sliceEntities';
 
 import sliceEntitiesReducer from 'src/dashboard/reducers/sliceEntities';
@@ -41,7 +41,7 @@ describe('sliceEntities reducer', () => {
   it('should set slices', () => {
     const result = sliceEntitiesReducer(
       { slices: { a: {} } },
-      { type: SET_ALL_SLICES, payload: { slices: { 1: {}, 2: {} } } },
+      { type: ADD_SLICES, payload: { slices: { 1: {}, 2: {} } } },
     );
 
     expect(result.slices).toEqual({
diff --git a/superset-frontend/src/dashboard/types.ts b/superset-frontend/src/dashboard/types.ts
index 56bccf900e..7345f0b1af 100644
--- a/superset-frontend/src/dashboard/types.ts
+++ b/superset-frontend/src/dashboard/types.ts
@@ -19,6 +19,7 @@
 import {
   ChartProps,
   DataMaskStateWithId,
+  DatasourceType,
   ExtraFormData,
   GenericDataType,
   JsonObject,
@@ -193,3 +194,23 @@ export type EmbeddedDashboard = {
   dashboard_id: string;
   allowed_domains: string[];
 };
+
+export type Slice = {
+  slice_id: number;
+  slice_name: string;
+  description: string;
+  description_markdown: string;
+  form_data: any;
+  slice_url: string;
+  viz_type: string;
+  thumbnail_url: string;
+  changed_on: number;
+  changed_on_humanized: string;
+  modified: string;
+  datasource_id: number;
+  datasource_type: DatasourceType;
+  datasource_url: string;
+  datasource_name: string;
+  owners: { id: number }[];
+  created_by: { id: number };
+};
diff --git a/superset-frontend/src/utils/localStorageHelpers.ts b/superset-frontend/src/utils/localStorageHelpers.ts
index 3dceaba86e..beb5ed1014 100644
--- a/superset-frontend/src/utils/localStorageHelpers.ts
+++ b/superset-frontend/src/utils/localStorageHelpers.ts
@@ -54,6 +54,7 @@ export enum LocalStorageKeys {
   explore__data_table_original_formatted_time_columns = 'explore__data_table_original_formatted_time_columns',
   dashboard__custom_filter_bar_widths = 'dashboard__custom_filter_bar_widths',
   dashboard__explore_context = 'dashboard__explore_context',
+  dashboard__editor_show_only_my_charts = 'dashboard__editor_show_only_my_charts',
   common__resizable_sidebar_widths = 'common__resizable_sidebar_widths',
 }
 
@@ -73,6 +74,7 @@ export type LocalStorageValues = {
   explore__data_table_original_formatted_time_columns: Record<string, string[]>;
   dashboard__custom_filter_bar_widths: Record<string, number>;
   dashboard__explore_context: Record<string, DashboardContextForExplore>;
+  dashboard__editor_show_only_my_charts: boolean;
   common__resizable_sidebar_widths: Record<string, number>;
 };