You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/03/06 17:07:09 UTC
(superset) 03/09: feat(dashboard): auto-save chart title and description toggle changes
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 <ev...@rusackas.com>
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 <no...@anthropic.com>
---
.../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,