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',

Reply via email to