Chore: Refactor scene hooks outside of class definition (#110181)

* refactor scene hooks outside of class definition

* remove unused import

* restore useMemo

* prune suppressions
This commit is contained in:
Ashley Harrison 2025-09-09 10:31:06 +01:00 committed by GitHub
parent ac1cd43fac
commit 7651b7b77e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 247 additions and 275 deletions

View File

@ -2050,21 +2050,11 @@
"count": 4
}
},
"public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx": {
"react-hooks/rules-of-hooks": {
"count": 4
}
},
"public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx": {
"react-hooks/rules-of-hooks": {
"count": 6
}
},
"public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.tsx": {
"no-restricted-syntax": {
"count": 3
@ -2164,21 +2154,11 @@
"count": 1
}
},
"public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx": {
"react-hooks/rules-of-hooks": {
"count": 2
}
},
"public/app/features/dashboard-scene/scene/layout-default/row-actions/RowOptionsForm.tsx": {
"no-restricted-syntax": {
"count": 2
}
},
"public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx": {
"react-hooks/rules-of-hooks": {
"count": 1
}
},
"public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx": {
"no-restricted-syntax": {
"count": 1
@ -2187,11 +2167,6 @@
"count": 1
}
},
"public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx": {
"react-hooks/rules-of-hooks": {
"count": 1
}
},
"public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx": {
"no-restricted-syntax": {
"count": 1
@ -2243,25 +2218,17 @@
"count": 10
}
},
"public/app/features/dashboard-scene/settings/variables/LocalVariableEditableElement.tsx": {
"react-hooks/rules-of-hooks": {
"count": 3
}
},
"public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx": {
"no-restricted-syntax": {
"count": 1
},
"react-hooks/rules-of-hooks": {
"count": 9
"count": 4
}
},
"public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx": {
"no-restricted-syntax": {
"count": 1
},
"react-hooks/rules-of-hooks": {
"count": 2
}
},
"public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx": {

View File

@ -13,6 +13,40 @@ import { EditableDashboardElement, EditableDashboardElementInfo } from '../scene
import { dashboardEditActions, undoRedoWasClicked } from './shared';
function useEditPaneOptions(
this: DashboardEditableElement,
dashboard: DashboardScene
): OptionsPaneCategoryDescriptor[] {
// When layout changes we need to update options list
const { body } = dashboard.useState();
const dashboardTitleInputId = useId();
const dashboardDescriptionInputId = useId();
const dashboardOptions = useMemo(() => {
const editPaneHeaderOptions = new OptionsPaneCategoryDescriptor({ title: '', id: 'dashboard-options' })
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.options.title-option', 'Title'),
id: dashboardTitleInputId,
render: () => <DashboardTitleInput id={dashboardTitleInputId} dashboard={dashboard} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.options.description', 'Description'),
id: dashboardDescriptionInputId,
render: () => <DashboardDescriptionInput id={dashboardDescriptionInputId} dashboard={dashboard} />,
})
);
return editPaneHeaderOptions;
}, [dashboard, dashboardDescriptionInputId, dashboardTitleInputId]);
const layoutCategory = useLayoutCategory(body);
return [dashboardOptions, ...layoutCategory];
}
export class DashboardEditableElement implements EditableDashboardElement {
public readonly isEditableDashboardElement = true;
@ -31,38 +65,7 @@ export class DashboardEditableElement implements EditableDashboardElement {
return [$variables!, ...body.getOutlineChildren()];
}
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
const dashboard = this.dashboard;
// When layout changes we need to update options list
const { body } = dashboard.useState();
const dashboardTitleInputId = useId();
const dashboardDescriptionInputId = useId();
const dashboardOptions = useMemo(() => {
const editPaneHeaderOptions = new OptionsPaneCategoryDescriptor({ title: '', id: 'dashboard-options' })
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.options.title-option', 'Title'),
id: dashboardTitleInputId,
render: () => <DashboardTitleInput id={dashboardTitleInputId} dashboard={dashboard} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.options.description', 'Description'),
id: dashboardDescriptionInputId,
render: () => <DashboardDescriptionInput id={dashboardDescriptionInputId} dashboard={dashboard} />,
})
);
return editPaneHeaderOptions;
}, [dashboard, dashboardDescriptionInputId, dashboardTitleInputId]);
const layoutCategory = useLayoutCategory(body);
return [dashboardOptions, ...layoutCategory];
}
public useEditPaneOptions = useEditPaneOptions.bind(this, this.dashboard);
public renderActions(): ReactNode {
return (

View File

@ -25,6 +25,59 @@ import { getDashboardSceneFor, getPanelIdForVizPanel } from '../utils/utils';
import { MultiSelectedVizPanelsEditableElement } from './MultiSelectedVizPanelsEditableElement';
function useEditPaneOptions(this: VizPanelEditableElement, isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
const panel = this.panel;
const layoutElement = panel.parent!;
const rootId = useId();
const titleId = useId();
const descriptionId = useId();
const backgroundId = useId();
const panelOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({ title: '', id: 'panel-options' })
.addItem(
new OptionsPaneItemDescriptor({
title: '',
id: rootId,
render: () => <OpenPanelEditViz panel={this.panel} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.viz-panel.options.title-option', 'Title'),
id: titleId,
value: panel.state.title,
popularRank: 1,
render: (descriptor) => (
<PanelFrameTitleInput id={descriptor.props.id} panel={panel} isNewElement={isNewElement} />
),
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.viz-panel.options.description', 'Description'),
id: descriptionId,
value: panel.state.description,
render: (descriptor) => <PanelDescriptionTextArea id={descriptor.props.id} panel={panel} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.viz-panel.options.transparent-background', 'Transparent background'),
id: backgroundId,
render: (descriptor) => <PanelBackgroundSwitch id={descriptor.props.id} panel={panel} />,
})
);
}, [rootId, titleId, panel, descriptionId, backgroundId, isNewElement]);
const layoutCategories = useMemo(
() => (isDashboardLayoutItem(layoutElement) && layoutElement.getOptions ? layoutElement.getOptions() : []),
[layoutElement]
);
return [panelOptions, ...layoutCategories];
}
export class VizPanelEditableElement implements EditableDashboardElement, BulkActionElement {
public readonly isEditableDashboardElement = true;
public readonly typeName = 'Panel';
@ -39,58 +92,7 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
};
}
public useEditPaneOptions(isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
const panel = this.panel;
const layoutElement = panel.parent!;
const rootId = useId();
const titleId = useId();
const descriptionId = useId();
const backgroundId = useId();
const panelOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({ title: '', id: 'panel-options' })
.addItem(
new OptionsPaneItemDescriptor({
title: '',
id: rootId,
render: () => <OpenPanelEditViz panel={this.panel} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.viz-panel.options.title-option', 'Title'),
id: titleId,
value: panel.state.title,
popularRank: 1,
render: (descriptor) => (
<PanelFrameTitleInput id={descriptor.props.id} panel={panel} isNewElement={isNewElement} />
),
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.viz-panel.options.description', 'Description'),
id: descriptionId,
value: panel.state.description,
render: (descriptor) => <PanelDescriptionTextArea id={descriptor.props.id} panel={panel} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.viz-panel.options.transparent-background', 'Transparent background'),
id: backgroundId,
render: (descriptor) => <PanelBackgroundSwitch id={descriptor.props.id} panel={panel} />,
})
);
}, [rootId, titleId, panel, descriptionId, backgroundId, isNewElement]);
const layoutCategories = useMemo(
() => (isDashboardLayoutItem(layoutElement) && layoutElement.getOptions ? layoutElement.getOptions() : []),
[layoutElement]
);
return [panelOptions, ...layoutCategories];
}
public useEditPaneOptions = useEditPaneOptions.bind(this);
public onDelete() {
const layout = dashboardSceneGraph.getLayoutManagerFor(this.panel);

View File

@ -18,6 +18,40 @@ import { EditableDashboardElement, EditableDashboardElementInfo } from '../types
import { DefaultGridLayoutManager } from './DefaultGridLayoutManager';
import { RowRepeaterBehavior } from './RowRepeaterBehavior';
function useEditPaneOptions(this: SceneGridRowEditableElement, row: SceneGridRow): OptionsPaneCategoryDescriptor[] {
const rowOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({
title: t('dashboard.default-layout.row-options.title', 'Row options'),
id: 'row-options',
isOpenDefault: true,
}).addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.default-layout.row-options.form.title', 'Title'),
id: 'row-options-title',
render: () => <RowTitleInput row={row} />,
})
);
}, [row]);
const rowRepeatOptions = useMemo(() => {
const dashboard = getDashboardSceneFor(row);
return new OptionsPaneCategoryDescriptor({
title: t('dashboard.default-layout.row-options.repeat.title', 'Repeat options'),
id: 'row-repeat-options',
isOpenDefault: true,
}).addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.default-layout.row-options.repeat.variable.title', 'Variable'),
id: 'row-options-repeat-variable',
render: () => <RowRepeatSelect row={row} dashboard={dashboard} />,
})
);
}, [row]);
return [rowOptions, rowRepeatOptions];
}
export class SceneGridRowEditableElement implements EditableDashboardElement, BulkActionElement {
public readonly isEditableDashboardElement = true;
@ -35,41 +69,7 @@ export class SceneGridRowEditableElement implements EditableDashboardElement, Bu
return this._row.state.children;
}
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
const row = this._row;
const rowOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({
title: t('dashboard.default-layout.row-options.title', 'Row options'),
id: 'row-options',
isOpenDefault: true,
}).addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.default-layout.row-options.form.title', 'Title'),
id: 'row-options-title',
render: (descriptor) => <RowTitleInput id={descriptor.props.id} row={row} />,
})
);
}, [row]);
const rowRepeatOptions = useMemo(() => {
const dashboard = getDashboardSceneFor(row);
return new OptionsPaneCategoryDescriptor({
title: t('dashboard.default-layout.row-options.repeat.title', 'Repeat options'),
id: 'row-repeat-options',
isOpenDefault: true,
}).addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.default-layout.row-options.repeat.variable.title', 'Variable'),
id: 'row-options-repeat-variable',
render: (descriptor) => <RowRepeatSelect id={descriptor.props.id} row={row} dashboard={dashboard} />,
})
);
}, [row]);
return [rowOptions, rowRepeatOptions];
}
public useEditPaneOptions = useEditPaneOptions.bind(this, this._row);
public onDelete() {
const layoutManager = getLayoutManagerFor(this._row);

View File

@ -14,7 +14,6 @@ import appEvents from 'app/core/app_events';
import { LS_ROW_COPY_KEY } from 'app/core/constants';
import store from 'app/core/store';
import kbn from 'app/core/utils/kbn';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { ShowConfirmModalEvent } from 'app/types/events';
import { ConditionalRenderingGroup } from '../../conditional-rendering/group/ConditionalRenderingGroup';
@ -108,9 +107,7 @@ export class RowItem
this.setState({ layout });
}
public useEditPaneOptions(isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
return useEditOptions(this, isNewElement);
}
public useEditPaneOptions = useEditOptions.bind(this);
public onDelete() {
this.getParentLayout().removeRow(this);

View File

@ -17,7 +17,8 @@ import { useEditPaneInputAutoFocus } from '../layouts-shared/utils';
import { RowItem } from './RowItem';
export function useEditOptions(model: RowItem, isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
export function useEditOptions(this: RowItem, isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
const model = this;
const { layout } = model.useState();
const rowCategory = useMemo(
@ -45,7 +46,7 @@ export function useEditOptions(model: RowItem, isNewElement: boolean): OptionsPa
render: (descriptor) => <RowHeaderSwitch id={descriptor.props.id} row={model} />,
})
),
[model, isNewElement]
[isNewElement, model]
);
const repeatCategory = useMemo(
@ -57,7 +58,7 @@ export function useEditOptions(model: RowItem, isNewElement: boolean): OptionsPa
}).addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.rows-layout.row-options.repeat.variable.title', 'Repeat by variable'),
id: `dash-row-repeat-by-variable`,
id: 'dash-row-repeat-by-variable',
description: t(
'dashboard.rows-layout.row-options.repeat.variable.description',
'Repeat this row for each value in the selected variable.'

View File

@ -14,7 +14,6 @@ import { LS_TAB_COPY_KEY } from 'app/core/constants';
import { appEvents } from 'app/core/core';
import store from 'app/core/store';
import kbn from 'app/core/utils/kbn';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { ShowConfirmModalEvent } from 'app/types/events';
import { ConditionalRenderingGroup } from '../../conditional-rendering/group/ConditionalRenderingGroup';
@ -106,9 +105,7 @@ export class TabItem
this.setState({ layout });
}
public useEditPaneOptions(isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
return useEditOptions(this, isNewElement);
}
public useEditPaneOptions = useEditOptions.bind(this);
public onDelete() {
const layout = this.getParentLayout();

View File

@ -17,7 +17,8 @@ import { useEditPaneInputAutoFocus } from '../layouts-shared/utils';
import { TabItem } from './TabItem';
export function useEditOptions(model: TabItem, isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
export function useEditOptions(this: TabItem, isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
const model = this;
const { layout } = model.useState();
const tabCategory = useMemo(
@ -29,7 +30,7 @@ export function useEditOptions(model: TabItem, isNewElement: boolean): OptionsPa
render: (descriptor) => <TabTitleInput id={descriptor.props.id} tab={model} isNewElement={isNewElement} />,
})
),
[model, isNewElement]
[isNewElement, model]
);
const repeatCategory = useMemo(

View File

@ -8,6 +8,42 @@ import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/Pan
import { EditableDashboardElement, EditableDashboardElementInfo } from '../../scene/types/EditableDashboardElement';
function useEditPaneOptions(this: LocalVariableEditableElement): OptionsPaneCategoryDescriptor[] {
const variable = this.variable;
const localVariableCategoryId = useId();
const localVariableId = useId();
return useMemo(() => {
const category = new OptionsPaneCategoryDescriptor({
title: '',
id: localVariableCategoryId,
});
category.addItem(
new OptionsPaneItemDescriptor({
title: '',
id: localVariableId,
skipField: true,
render: () => {
return (
<Box paddingBottom={1}>
<Stack>
<Stack>
<span>${variable.state.name}</span>
<span>=</span>
<span>{variable.getValueText()}</span>
</Stack>
</Stack>
</Box>
);
},
})
);
return [category];
}, [localVariableCategoryId, localVariableId, variable]);
}
export class LocalVariableEditableElement implements EditableDashboardElement {
public readonly isEditableDashboardElement = true;
@ -22,39 +58,5 @@ export class LocalVariableEditableElement implements EditableDashboardElement {
};
}
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
const variable = this.variable;
const localVariableCategoryId = useId();
const localVariableId = useId();
return useMemo(() => {
const category = new OptionsPaneCategoryDescriptor({
title: '',
id: localVariableCategoryId,
});
category.addItem(
new OptionsPaneItemDescriptor({
title: '',
id: localVariableId,
skipField: true,
render: () => {
return (
<Box paddingBottom={1}>
<Stack>
<Stack>
<span>${variable.state.name}</span>
<span>=</span>
<span>{variable.getValueText()}</span>
</Stack>
</Stack>
</Box>
);
},
})
);
return [category];
}, [localVariableCategoryId, localVariableId, variable]);
}
public useEditPaneOptions = useEditPaneOptions.bind(this);
}

View File

@ -18,6 +18,65 @@ import { getEditableVariableDefinition, validateVariableName } from '../../setti
import { useVariableSelectionOptionsCategory } from './useVariableSelectionOptionsCategory';
// TODO fix conditional hook usage here...
function useEditPaneOptions(this: VariableEditableElement, isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
const variable = this.variable;
const variableOptionsCategoryId = useId();
const variableNameId = useId();
const labelId = useId();
const descriptionId = useId();
const variableHideId = useId();
if (variable instanceof LocalValueVariable) {
return useLocalVariableOptions(variable);
}
const basicOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({ title: '', id: variableOptionsCategoryId })
.addItem(
new OptionsPaneItemDescriptor({
title: '',
id: variableNameId,
skipField: true,
render: () => <VariableNameInput variable={variable} isNewElement={isNewElement} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.edit-pane.variable.label', 'Label'),
id: labelId,
description: t('dashboard.edit-pane.variable.label-description', 'Optional display name'),
render: () => <VariableLabelInput variable={variable} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.edit-pane.variable.description', 'Description'),
id: descriptionId,
render: () => <VariableDescriptionTextArea variable={variable} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: '',
id: variableHideId,
skipField: true,
render: () => <VariableHideInput variable={variable} />,
})
);
}, [variableOptionsCategoryId, variableNameId, labelId, descriptionId, variableHideId, variable, isNewElement]);
const categories = [basicOptions];
const typeCategory = useVariableTypeCategory(variable);
categories.push(typeCategory);
if (variable instanceof MultiValueVariable) {
categories.push(useVariableSelectionOptionsCategory(variable));
}
return categories;
}
export class VariableEditableElement implements EditableDashboardElement, BulkActionElement {
public readonly isEditableDashboardElement = true;
public readonly typeName = 'Variable';
@ -44,63 +103,7 @@ export class VariableEditableElement implements EditableDashboardElement, BulkAc
};
}
public useEditPaneOptions(isNewElement: boolean): OptionsPaneCategoryDescriptor[] {
const variable = this.variable;
const variableOptionsCategoryId = useId();
const variableNameId = useId();
const labelId = useId();
const descriptionId = useId();
const variableHideId = useId();
if (variable instanceof LocalValueVariable) {
return useLocalVariableOptions(variable);
}
const basicOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({ title: '', id: variableOptionsCategoryId, isOpenDefault: true })
.addItem(
new OptionsPaneItemDescriptor({
title: '',
id: variableNameId,
skipField: true,
render: () => <VariableNameInput variable={variable} isNewElement={isNewElement} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.edit-pane.variable.label', 'Label'),
id: labelId,
description: t('dashboard.edit-pane.variable.label-description', 'Optional display name'),
render: (descriptor) => <VariableLabelInput id={descriptor.props.id} variable={variable} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.edit-pane.variable.description', 'Description'),
id: descriptionId,
render: (descriptor) => <VariableDescriptionTextArea id={descriptor.props.id} variable={variable} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: '',
id: variableHideId,
skipField: true,
render: () => <VariableHideInput variable={variable} />,
})
);
}, [variableOptionsCategoryId, variableNameId, labelId, descriptionId, variableHideId, variable, isNewElement]);
const categories = [basicOptions];
const typeCategory = useVariableTypeCategory(variable);
categories.push(typeCategory);
if (variable instanceof MultiValueVariable) {
categories.push(useVariableSelectionOptionsCategory(variable));
}
return categories;
}
public useEditPaneOptions = useEditPaneOptions.bind(this);
public onDelete() {
const set = this.variable.parent!;

View File

@ -17,6 +17,21 @@ import { getDashboardSceneFor } from '../../utils/utils';
import { EditableVariableType, getNextAvailableId, getVariableScene, getVariableTypeSelectOptions } from './utils';
function useEditPaneOptions(this: VariableSetEditableElement, set: SceneVariableSet): OptionsPaneCategoryDescriptor[] {
const variableListId = useId();
const options = useMemo(() => {
return new OptionsPaneCategoryDescriptor({ title: '', id: 'variables' }).addItem(
new OptionsPaneItemDescriptor({
title: '',
id: variableListId,
skipField: true,
render: () => <VariableList set={set} />,
})
);
}, [set, variableListId]);
return [options];
}
export class VariableSetEditableElement implements EditableDashboardElement {
public readonly isEditableDashboardElement = true;
public readonly typeName = 'Variable';
@ -35,23 +50,7 @@ export class VariableSetEditableElement implements EditableDashboardElement {
return this.set.state.variables;
}
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
const variableListId = useId();
const set = this.set;
const options = useMemo(() => {
return new OptionsPaneCategoryDescriptor({ title: '', id: 'variables' }).addItem(
new OptionsPaneItemDescriptor({
title: '',
id: variableListId,
skipField: true,
render: () => <VariableList set={set} />,
})
);
}, [set, variableListId]);
return [options];
}
public useEditPaneOptions = useEditPaneOptions.bind(this, this.set);
}
function VariableList({ set }: { set: SceneVariableSet }) {