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 <noreply@anthropic.com>
This commit is contained in:
Evan Rusackas
2026-01-08 16:17:04 -08:00
parent cedc35e39f
commit 06dfeaa939
5 changed files with 38 additions and 9 deletions

View File

@@ -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;

View File

@@ -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),

View File

@@ -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)}
/>
)}
</>

View File

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

View File

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