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,
