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

rusackas pushed a commit to branch live-edits
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 73379b1aff6fb82076d95435bc09dc7331473e4b
Author: Evan Rusackas <[email protected]>
AuthorDate: Thu Jan 8 16:57:03 2026 -0800

    feat(dashboard): auto-save chart title and description toggle changes
    
    - toggleExpandSlice now auto-saves expanded_slices to json_metadata
    - Add updateSliceNameWithSave action that saves position_json after title 
edit
    - Both features now persist without entering edit mode
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-Authored-By: Claude Opus 4.5 <[email protected]>
---
 .../src/dashboard/actions/dashboardLayout.js       | 71 +++++++++++++++++++++-
 .../src/dashboard/actions/dashboardState.js        | 36 ++++++++++-
 .../gridComponents/ChartHolder/ChartHolder.tsx     | 18 +++---
 .../dashboard/containers/DashboardComponent.jsx    |  2 +
 4 files changed, 114 insertions(+), 13 deletions(-)

diff --git a/superset-frontend/src/dashboard/actions/dashboardLayout.js 
b/superset-frontend/src/dashboard/actions/dashboardLayout.js
index 42759a8e9c4..a9bc94d3ee9 100644
--- a/superset-frontend/src/dashboard/actions/dashboardLayout.js
+++ b/superset-frontend/src/dashboard/actions/dashboardLayout.js
@@ -17,8 +17,16 @@
  * under the License.
  */
 import { ActionCreators as UndoActionCreators } from 'redux-undo';
-import { t } from '@superset-ui/core';
-import { addWarningToast } from 'src/components/MessageToasts/actions';
+import {
+  t,
+  SupersetClient,
+  logging,
+  getClientErrorObject,
+} from '@superset-ui/core';
+import {
+  addWarningToast,
+  addDangerToast,
+} from 'src/components/MessageToasts/actions';
 import { TABS_TYPE, ROW_TYPE } from 'src/dashboard/util/componentTypes';
 import {
   DASHBOARD_ROOT_ID,
@@ -28,6 +36,9 @@ import {
 import dropOverflowsParent from 'src/dashboard/util/dropOverflowsParent';
 import findParentId from 'src/dashboard/util/findParentId';
 import isInDifferentFilterScopes from 
'src/dashboard/util/isInDifferentFilterScopes';
+import serializeActiveFilterValues from 
'src/dashboard/util/serializeActiveFilterValues';
+import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
+import { safeStringify } from 'src/utils/safeStringify';
 import { updateLayoutComponents } from './dashboardFilters';
 import { setUnsavedChanges } from './dashboardState';
 
@@ -74,6 +85,62 @@ export const updateComponents = setUnsavedChangesAfterAction(
   }),
 );
 
+// Update a slice name override and auto-save to persist the change
+export function updateSliceNameWithSave(componentId, component, nextName) {
+  return (dispatch, getState) => {
+    const { dashboardInfo, dashboardLayout } = getState();
+    const dashboardId = dashboardInfo.id;
+
+    // Update the component with the new name override
+    const updatedComponent = {
+      ...component,
+      meta: {
+        ...component.meta,
+        sliceNameOverride: nextName,
+      },
+    };
+
+    // Dispatch the update for immediate UI feedback
+    dispatch(
+      updateComponents({
+        [componentId]: updatedComponent,
+      }),
+    );
+
+    // Get the updated layout after the dispatch
+    const { dashboardLayout: updatedLayout, dashboardFilters } = getState();
+    const layout = updatedLayout.present;
+
+    // Serialize the layout for saving
+    const serializedFilters = serializeActiveFilterValues(getActiveFilters());
+
+    // Auto-save the position_json to persist the change
+    SupersetClient.put({
+      endpoint: `/api/v1/dashboard/${dashboardId}`,
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        position_json: safeStringify(layout),
+        json_metadata: safeStringify({
+          ...dashboardInfo.metadata,
+          default_filters: safeStringify(serializedFilters),
+        }),
+      }),
+    })
+      .then(() => {
+        dispatch(setUnsavedChanges(false));
+      })
+      .catch(async response => {
+        const { error } = await getClientErrorObject(response);
+        logging.error('Error saving slice name:', error);
+        dispatch(
+          addDangerToast(
+            t('Could not save your changes. Please try again.'),
+          ),
+        );
+      });
+  };
+}
+
 export function updateDashboardTitle(text) {
   return (dispatch, getState) => {
     const { dashboardLayout } = getState();
diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js 
b/superset-frontend/src/dashboard/actions/dashboardState.js
index 4728815a028..5a56fce584b 100644
--- a/superset-frontend/src/dashboard/actions/dashboardState.js
+++ b/superset-frontend/src/dashboard/actions/dashboardState.js
@@ -182,7 +182,41 @@ export function savePublished(id, isPublished) {
 
 export const TOGGLE_EXPAND_SLICE = 'TOGGLE_EXPAND_SLICE';
 export function toggleExpandSlice(sliceId) {
-  return { type: TOGGLE_EXPAND_SLICE, sliceId };
+  return (dispatch, getState) => {
+    const { dashboardInfo, dashboardState } = getState();
+    const dashboardId = dashboardInfo.id;
+
+    // Toggle the expanded state
+    dispatch({ type: TOGGLE_EXPAND_SLICE, sliceId });
+
+    // Get updated state after toggle
+    const { dashboardState: updatedState } = getState();
+    const expandedSlices = updatedState.expandedSlices || {};
+
+    // Auto-save to persist the change
+    SupersetClient.put({
+      endpoint: `/api/v1/dashboard/${dashboardId}`,
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        json_metadata: safeStringify({
+          ...dashboardInfo.metadata,
+          expanded_slices: expandedSlices,
+        }),
+      }),
+    })
+      .then(() => {
+        dispatch(setUnsavedChanges(false));
+      })
+      .catch(async response => {
+        const { error } = await getClientErrorObject(response);
+        logging.error('Error saving expanded slices:', error);
+        dispatch(
+          addDangerToast(
+            t('Could not save your preferences. Please try again.'),
+          ),
+        );
+      });
+  };
 }
 
 export const SET_EDIT_MODE = 'SET_EDIT_MODE';
diff --git 
a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder/ChartHolder.tsx
 
b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder/ChartHolder.tsx
index 7d68c8ceaec..bfc1cb78864 100644
--- 
a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder/ChartHolder.tsx
+++ 
b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder/ChartHolder.tsx
@@ -65,6 +65,11 @@ interface ChartHolderProps {
   // dnd
   deleteComponent: (id: string, parentId: string) => void;
   updateComponents: Function;
+  updateSliceNameWithSave: (
+    componentId: string,
+    component: LayoutItem,
+    nextName: string,
+  ) => void;
   handleComponentDrop: (...args: unknown[]) => unknown;
   setFullSizeChartId: (chartId: number | null) => void;
   isInView: boolean;
@@ -89,6 +94,7 @@ const ChartHolder = ({
   getComponentById = () => undefined,
   deleteComponent,
   updateComponents,
+  updateSliceNameWithSave,
   handleComponentDrop,
   setFullSizeChartId,
   isInView,
@@ -214,17 +220,9 @@ const ChartHolder = ({
 
   const handleUpdateSliceName = useCallback(
     (nextName: string) => {
-      updateComponents({
-        [component.id]: {
-          ...component,
-          meta: {
-            ...component.meta,
-            sliceNameOverride: nextName,
-          },
-        },
-      });
+      updateSliceNameWithSave(component.id, component, nextName);
     },
-    [component, updateComponents],
+    [component, updateSliceNameWithSave],
   );
 
   const handleToggleFullSize = useCallback(() => {
diff --git a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx 
b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
index 2e35ffbc0d4..e68ff9e8cbd 100644
--- a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
@@ -30,6 +30,7 @@ import {
   createComponent,
   deleteComponent,
   updateComponents,
+  updateSliceNameWithSave,
   handleComponentDrop,
 } from 'src/dashboard/actions/dashboardLayout';
 import {
@@ -84,6 +85,7 @@ const DashboardComponent = props => {
           createComponent,
           deleteComponent,
           updateComponents,
+          updateSliceNameWithSave,
           handleComponentDrop,
           setDirectPathToChild,
           setFullSizeChartId,

Reply via email to