You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by kg...@apache.org on 2023/01/10 15:49:09 UTC

[superset] branch master updated: feat(dashboard): Display a loading spinner while dashboard is being saved (#22588)

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

kgabryje 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 399f6e3ddc feat(dashboard): Display a loading spinner while dashboard is being saved (#22588)
399f6e3ddc is described below

commit 399f6e3ddc8bb21fd7b39cdf850510b2692fbe12
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Tue Jan 10 16:48:51 2023 +0100

    feat(dashboard): Display a loading spinner while dashboard is being saved (#22588)
---
 superset-frontend/src/dashboard/actions/dashboardState.js  | 14 ++++++++++++++
 .../src/dashboard/actions/dashboardState.test.js           |  8 +++++---
 superset-frontend/src/dashboard/actions/hydrate.js         |  1 +
 .../components/DashboardBuilder/DashboardBuilder.test.tsx  | 14 ++++++++++++++
 .../components/DashboardBuilder/DashboardBuilder.tsx       | 12 ++++++++++++
 superset-frontend/src/dashboard/reducers/dashboardState.js | 14 ++++++++++++++
 superset-frontend/src/dashboard/types.ts                   |  1 +
 7 files changed, 61 insertions(+), 3 deletions(-)

diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js
index 9bbe618c88..518dd7b5dc 100644
--- a/superset-frontend/src/dashboard/actions/dashboardState.js
+++ b/superset-frontend/src/dashboard/actions/dashboardState.js
@@ -203,9 +203,20 @@ export function setOverrideConfirm(overwriteConfirmMetadata) {
   };
 }
 
+export const SAVE_DASHBOARD_STARTED = 'SAVE_DASHBOARD_STARTED';
+export function saveDashboardStarted() {
+  return { type: SAVE_DASHBOARD_STARTED };
+}
+
+export const SAVE_DASHBOARD_FINISHED = 'SAVE_DASHBOARD_FINISHED';
+export function saveDashboardFinished() {
+  return { type: SAVE_DASHBOARD_FINISHED };
+}
+
 export function saveDashboardRequest(data, id, saveType) {
   return (dispatch, getState) => {
     dispatch({ type: UPDATE_COMPONENTS_PARENTS_LIST });
+    dispatch(saveDashboardStarted());
 
     const { dashboardFilters, dashboardLayout } = getState();
     const layout = dashboardLayout.present;
@@ -291,6 +302,7 @@ export function saveDashboardRequest(data, id, saveType) {
         const chartConfiguration = handleChartConfiguration();
         dispatch(setChartConfiguration(chartConfiguration));
       }
+      dispatch(saveDashboardFinished());
       dispatch(addSuccessToast(t('This dashboard was saved successfully.')));
       return response;
     };
@@ -322,6 +334,7 @@ export function saveDashboardRequest(data, id, saveType) {
       if (lastModifiedTime) {
         dispatch(saveDashboardRequestSuccess(lastModifiedTime));
       }
+      dispatch(saveDashboardFinished());
       // redirect to the new slug or id
       window.history.pushState(
         { event: 'dashboard_properties_changed' },
@@ -347,6 +360,7 @@ export function saveDashboardRequest(data, id, saveType) {
       if (typeof message === 'string' && message === 'Forbidden') {
         errorText = t('You do not have permission to edit this dashboard');
       }
+      dispatch(saveDashboardFinished());
       dispatch(addDangerToast(errorText));
     };
 
diff --git a/superset-frontend/src/dashboard/actions/dashboardState.test.js b/superset-frontend/src/dashboard/actions/dashboardState.test.js
index 25aa54ed66..00b358bc43 100644
--- a/superset-frontend/src/dashboard/actions/dashboardState.test.js
+++ b/superset-frontend/src/dashboard/actions/dashboardState.test.js
@@ -22,6 +22,7 @@ import { waitFor } from '@testing-library/react';
 
 import {
   removeSliceFromDashboard,
+  SAVE_DASHBOARD_STARTED,
   saveDashboardRequest,
   SET_OVERRIDE_CONFIRM,
 } from 'src/dashboard/actions/dashboardState';
@@ -104,10 +105,11 @@ describe('dashboardState actions', () => {
       });
       const thunk = saveDashboardRequest(newDashboardData, 1, 'save_dash');
       thunk(dispatch, getState);
-      expect(dispatch.callCount).toBe(1);
+      expect(dispatch.callCount).toBe(2);
       expect(dispatch.getCall(0).args[0].type).toBe(
         UPDATE_COMPONENTS_PARENTS_LIST,
       );
+      expect(dispatch.getCall(1).args[0].type).toBe(SAVE_DASHBOARD_STARTED);
     });
 
     it('should post dashboard data with updated redux state', () => {
@@ -162,10 +164,10 @@ describe('dashboardState actions', () => {
         expect(getStub.callCount).toBe(1);
         expect(postStub.callCount).toBe(0);
         await waitFor(() =>
-          expect(dispatch.getCall(1).args[0].type).toBe(SET_OVERRIDE_CONFIRM),
+          expect(dispatch.getCall(2).args[0].type).toBe(SET_OVERRIDE_CONFIRM),
         );
         expect(
-          dispatch.getCall(1).args[0].overwriteConfirmMetadata.dashboardId,
+          dispatch.getCall(2).args[0].overwriteConfirmMetadata.dashboardId,
         ).toBe(id);
       });
 
diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js
index 917492bd7b..ed359a8cee 100644
--- a/superset-frontend/src/dashboard/actions/hydrate.js
+++ b/superset-frontend/src/dashboard/actions/hydrate.js
@@ -454,6 +454,7 @@ export const hydrateDashboard =
           editMode: canEdit && editMode,
           isPublished: dashboard.published,
           hasUnsavedChanges: false,
+          dashboardIsSaving: false,
           maxUndoHistoryExceeded: false,
           lastModifiedTime: dashboard.changed_on,
           isRefreshing: false,
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
index 65969e2ca6..b5f249b896 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
@@ -249,6 +249,20 @@ describe('DashboardBuilder', () => {
     (setDirectPathToChild as jest.Mock).mockReset();
   });
 
+  it('should not display a loading spinner when saving is not in progress', () => {
+    const { queryByAltText } = setup();
+
+    expect(queryByAltText('Loading...')).not.toBeInTheDocument();
+  });
+
+  it('should display a loading spinner when saving is in progress', async () => {
+    const { findByAltText } = setup({
+      dashboardState: { dashboardIsSaving: true },
+    });
+
+    expect(await findByAltText('Loading...')).toBeVisible();
+  });
+
   describe('when nativeFiltersEnabled', () => {
     beforeEach(() => {
       (isFeatureEnabled as jest.Mock).mockImplementation(
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index d2ce6a2f1a..ab14c4d932 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -246,6 +246,9 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
   const canEdit = useSelector<RootState, boolean>(
     ({ dashboardInfo }) => dashboardInfo.dash_edit_perm,
   );
+  const dashboardIsSaving = useSelector<RootState, boolean>(
+    ({ dashboardState }) => dashboardState.dashboardIsSaving,
+  );
   const nativeFilters = useSelector((state: RootState) => state.nativeFilters);
   const focusedFilterId = nativeFilters?.focusedFilterId;
   const fullSizeChartId = useSelector<RootState, number | null>(
@@ -533,6 +536,15 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
           </StyledDashboardContent>
         </div>
       </StyledContent>
+      {dashboardIsSaving && (
+        <Loading
+          css={css`
+            && {
+              position: fixed;
+            }
+          `}
+        />
+      )}
     </StyledDiv>
   );
 };
diff --git a/superset-frontend/src/dashboard/reducers/dashboardState.js b/superset-frontend/src/dashboard/reducers/dashboardState.js
index 1c339001d1..5d81cd8ac1 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardState.js
+++ b/superset-frontend/src/dashboard/reducers/dashboardState.js
@@ -43,6 +43,8 @@ import {
   ON_FILTERS_REFRESH_SUCCESS,
   SET_DATASETS_STATUS,
   SET_OVERRIDE_CONFIRM,
+  SAVE_DASHBOARD_STARTED,
+  SAVE_DASHBOARD_FINISHED,
 } from '../actions/dashboardState';
 import { HYDRATE_DASHBOARD } from '../actions/hydrate';
 
@@ -111,6 +113,18 @@ export default function dashboardStateReducer(state = {}, action) {
     [ON_CHANGE]() {
       return { ...state, hasUnsavedChanges: true };
     },
+    [SAVE_DASHBOARD_STARTED]() {
+      return {
+        ...state,
+        dashboardIsSaving: true,
+      };
+    },
+    [SAVE_DASHBOARD_FINISHED]() {
+      return {
+        ...state,
+        dashboardIsSaving: false,
+      };
+    },
     [ON_SAVE]() {
       return {
         ...state,
diff --git a/superset-frontend/src/dashboard/types.ts b/superset-frontend/src/dashboard/types.ts
index 1bdd1c14a1..e24356be61 100644
--- a/superset-frontend/src/dashboard/types.ts
+++ b/superset-frontend/src/dashboard/types.ts
@@ -70,6 +70,7 @@ export type DashboardState = {
   isRefreshing: boolean;
   isFiltersRefreshing: boolean;
   hasUnsavedChanges: boolean;
+  dashboardIsSaving: boolean;
   colorScheme: string;
   sliceIds: number[];
   directPathLastUpdated: number;