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 06dfeaa939e07ed81cf400402a8dcc5d3ea4f4a7 Author: Evan Rusackas <[email protected]> AuthorDate: Thu Jan 8 16:17:04 2026 -0800 feat(dashboard): add inline chart title editing and fix description toggle - Add "Edit chart title" menu option for dashboard editors - Gate description toggle behind edit permission (was visible to all users) - Fix expanded_slices persistence bug (toggle state was not being saved) - Add controlled editing mode to EditableTitle component The title editing uses the existing sliceNameOverride infrastructure, storing dashboard-specific title overrides without modifying the underlying chart. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]> --- .../src/components/EditableTitle/index.tsx | 7 +++++++ .../src/dashboard/actions/dashboardState.js | 4 ++-- .../src/dashboard/components/SliceHeader/index.tsx | 14 +++++++++++--- .../components/SliceHeaderControls/index.tsx | 21 +++++++++++++++++---- superset-frontend/src/dashboard/types.ts | 1 + 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/components/EditableTitle/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/EditableTitle/index.tsx index 30b298f02dd..82ce1ccf7eb 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/EditableTitle/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/EditableTitle/index.tsx @@ -122,6 +122,13 @@ export function EditableTitle({ } }, [title]); + // Support controlled editing mode - sync isEditing when editing prop changes to true + useEffect(() => { + if (editing && !isEditing) { + setIsEditing(true); + } + }, [editing]); + useEffect(() => { if (isEditing && contentRef.current) { const textArea = contentRef.current.resizableTextArea?.textArea; diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js index 18b567e20f5..4728815a028 100644 --- a/superset-frontend/src/dashboard/actions/dashboardState.js +++ b/superset-frontend/src/dashboard/actions/dashboardState.js @@ -274,7 +274,7 @@ export function saveDashboardRequest(data, id, saveType) { dispatch({ type: UPDATE_COMPONENTS_PARENTS_LIST }); dispatch(saveDashboardStarted()); - const { dashboardFilters, dashboardLayout } = getState(); + const { dashboardFilters, dashboardLayout, dashboardState } = getState(); const layout = dashboardLayout.present; Object.values(dashboardFilters).forEach(filter => { const { chartId } = filter; @@ -327,7 +327,7 @@ export function saveDashboardRequest(data, id, saveType) { color_scheme_domain: colorScheme ? getColorSchemeDomain(colorScheme) : [], - expanded_slices: data.metadata?.expanded_slices || {}, + expanded_slices: dashboardState.expandedSlices || {}, label_colors: customLabelsColor, shared_label_colors: getFreshSharedLabels(sharedLabelsColor), map_label_colors: getFreshLabelsColorMapEntries(customLabelsColor), diff --git a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx index b9c0293534f..c890fcb4c67 100644 --- a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx @@ -174,6 +174,7 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>( !isEmbedded() || uiConfig.showRowLimitWarning; const dashboardPageId = useContext(DashboardPageIdContext); const [headerTooltip, setHeaderTooltip] = useState<ReactNode | null>(null); + const [isEditingTitle, setIsEditingTitle] = useState(false); const headerRef = useRef<HTMLDivElement>(null); // TODO: change to indicator field after it will be implemented const crossFilterValue = useSelector<RootState, any>( @@ -236,15 +237,21 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>( <EditableTitle title={ sliceName || - (editMode + (editMode || isEditingTitle ? '---' // this makes an empty title clickable : '') } - canEdit={editMode} + canEdit={editMode || isEditingTitle} + editing={isEditingTitle} onSaveTitle={updateSliceName} showTooltip={false} + onEditingChange={editing => { + if (!editing) setIsEditingTitle(false); + }} renderLink={ - canExplore && exploreUrl ? renderExploreLink : undefined + canExplore && exploreUrl && !isEditingTitle + ? renderExploreLink + : undefined } /> </div> @@ -347,6 +354,7 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>( exploreUrl={exploreUrl} crossFiltersEnabled={isCrossFiltersEnabled} exportPivotExcel={exportPivotExcel} + onEditTitle={() => setIsEditingTitle(true)} /> )} </> diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx index 564a4fb745a..bf456e5b62c 100644 --- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx @@ -138,6 +138,7 @@ export interface SliceHeaderControlsProps { supersetCanCSV?: boolean; crossFiltersEnabled?: boolean; + onEditTitle?: () => void; } type SliceHeaderControlsPropsWithRouter = SliceHeaderControlsProps & RouteComponentProps; @@ -168,10 +169,11 @@ const SliceHeaderControls = ( ); const theme = useTheme(); + const canEditDashboard = useSelector<RootState, boolean>( + ({ dashboardInfo }) => dashboardInfo.dash_edit_perm, + ); const canEditCrossFilters = - useSelector<RootState, boolean>( - ({ dashboardInfo }) => dashboardInfo.dash_edit_perm, - ) && + canEditDashboard && getChartMetadataRegistry() .get(props.slice.viz_type) ?.behaviors?.includes(Behavior.InteractiveChart); @@ -212,6 +214,10 @@ const SliceHeaderControls = ( // eslint-disable-next-line no-unused-expressions props.toggleExpandSlice?.(props.slice.slice_id); break; + case MenuKeys.EditChartTitle: + // eslint-disable-next-line no-unused-expressions + props.onEditTitle?.(); + break; case MenuKeys.ExploreChart: // eslint-disable-next-line no-unused-expressions props.logExploreChart?.(props.slice.slice_id); @@ -375,7 +381,7 @@ const SliceHeaderControls = ( }, ]; - if (slice.description) { + if (slice.description && canEditDashboard) { newMenuItems.push({ key: MenuKeys.ToggleChartDescription, label: props.isDescriptionExpanded @@ -384,6 +390,13 @@ const SliceHeaderControls = ( }); } + if (canEditDashboard) { + newMenuItems.push({ + key: MenuKeys.EditChartTitle, + label: t('Edit chart title'), + }); + } + if (canExplore) { newMenuItems.push({ key: MenuKeys.ExploreChart, diff --git a/superset-frontend/src/dashboard/types.ts b/superset-frontend/src/dashboard/types.ts index e1a589acdbf..8151bdeffd2 100644 --- a/superset-frontend/src/dashboard/types.ts +++ b/superset-frontend/src/dashboard/types.ts @@ -291,6 +291,7 @@ export enum MenuKeys { ForceRefresh = 'force_refresh', Fullscreen = 'fullscreen', ToggleChartDescription = 'toggle_chart_description', + EditChartTitle = 'edit_chart_title', ViewQuery = 'view_query', ViewResults = 'view_results', DrillToDetail = 'drill_to_detail',
