mirror of
https://github.com/apache/superset.git
synced 2026-05-09 18:05:52 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user