From c4de2dfff88c02c5aef61d1cbee070b5f6e5ccbc Mon Sep 17 00:00:00 2001 From: Sergej-Vlasov Date: Fri, 14 Nov 2025 11:28:16 +0000 Subject: [PATCH] optimise action logic to avoid unnecessary triggers --- .../scene/PanelGroupByAction.tsx | 90 ++++++++++++------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/PanelGroupByAction.tsx b/public/app/features/dashboard-scene/scene/PanelGroupByAction.tsx index 0ab566c39a0..f78a7cd842c 100644 --- a/public/app/features/dashboard-scene/scene/PanelGroupByAction.tsx +++ b/public/app/features/dashboard-scene/scene/PanelGroupByAction.tsx @@ -2,13 +2,14 @@ import { css, cx } from '@emotion/css'; import { useState, useCallback, useEffect, useMemo } from 'react'; import { lastValueFrom } from 'rxjs'; -import { GrafanaTheme2, fuzzySearch } from '@grafana/data'; +import { GrafanaTheme2, LoadingState, fuzzySearch } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; import { GroupByVariable, SceneComponentProps, sceneGraph, SceneObjectBase, + SceneObjectState, VariableValueOption, VariableValueSingle, VizPanel, @@ -18,13 +19,19 @@ import { Button, Icon, Input, useStyles2, Checkbox, Dropdown, Stack } from '@gra interface OptionWithChecked extends VariableValueOption { checked: boolean; } -export class PanelGroupByAction extends SceneObjectBase { + +export interface PanelGroupByActionState extends SceneObjectState { + hasGroupBy?: boolean; +} +export class PanelGroupByAction extends SceneObjectBase { static Component = PanelGroupByActionRenderer; - private _groupByVariable: GroupByVariable | undefined; + private _groupByVariable: GroupByVariable | undefined = undefined; constructor() { - super({}); + super({ + hasGroupBy: false, + }); this.addActivationHandler(this.onActivate); } @@ -34,9 +41,12 @@ export class PanelGroupByAction extends SceneObjectBase { throw new Error('PanelGroupByAction can be used only for VizPanel'); } - this._groupByVariable = sceneGraph + const groupByVar = sceneGraph .getVariables(this) .state.variables.find((variable) => variable instanceof GroupByVariable); + + this._groupByVariable = groupByVar; + this.setState({ hasGroupBy: !!groupByVar }); }; public getGroupByVariable() { @@ -49,12 +59,12 @@ export class PanelGroupByAction extends SceneObjectBase { throw new Error('PanelGroupByAction can be used only for VizPanel'); } - const dsUid = sceneGraph.getData(panel).state.data?.request?.targets?.[0]?.datasource?.uid; - if (!this._groupByVariable) { return false; } + const dsUid = sceneGraph.getData(panel).state.data?.request?.targets?.[0]?.datasource?.uid; + return sceneGraph.interpolate(this._groupByVariable, this._groupByVariable.state.datasource?.uid) === dsUid; } @@ -90,11 +100,22 @@ export class PanelGroupByAction extends SceneObjectBase { } } +// additional wrapper to avoid triggering useEffects in PanelGroupByHeader when no groupBy function PanelGroupByActionRenderer({ model }: SceneComponentProps) { + const { hasGroupBy } = model.useState(); + + if (!hasGroupBy) { + return null; + } + + return ; +} + +function PanelGroupByHeader({ model }: SceneComponentProps) { const groupByState = model.getGroupByVariable()?.useState(); const dataState = sceneGraph.getData(model).useState(); const styles = useStyles2(getStyles); - const panelHasGroupBy = model.doesPanelSupportGroupByVariable(); + const [panelSupportsGroupBy, setPanelSupportsGroupBy] = useState(undefined); const [options, setOptions] = useState([]); const [searchValue, setSearchValue] = useState(''); @@ -136,31 +157,38 @@ function PanelGroupByActionRenderer({ model }: SceneComponentProps { - const fetchOptions = async () => { - setIsLoading(true); - try { - const applicableOptions = await model.getGroupByOptions(); - const currentValue = groupByState?.value || []; - const currentValues = Array.isArray(currentValue) ? currentValue : [currentValue]; + const handleFetchOptions = useCallback(async () => { + setIsLoading(true); + try { + const applicableOptions = await model.getGroupByOptions(); + const currentValue = groupByState?.value || []; + const currentValues = Array.isArray(currentValue) ? currentValue : [currentValue]; - const optionsWithChecked: OptionWithChecked[] = applicableOptions.map((opt) => ({ - ...opt, - checked: currentValues.includes(opt.value), - })); + const optionsWithChecked: OptionWithChecked[] = applicableOptions.map((opt) => ({ + ...opt, + checked: currentValues.includes(opt.value), + })); - setOptions(optionsWithChecked); - } catch (error) { - setOptions([]); - } finally { - setIsLoading(false); - } - }; - - if (isOpen) { - fetchOptions(); + setOptions(optionsWithChecked); + } catch (error) { + setOptions([]); + } finally { + setIsLoading(false); } - }, [model, groupByState?.value, dataState.data?.request?.targets, isOpen]); + }, [groupByState?.value, model]); + + useEffect(() => { + if (panelSupportsGroupBy === undefined && dataState.data?.state === LoadingState.Done) { + const hasGroupByDs = model.doesPanelSupportGroupByVariable(); + setPanelSupportsGroupBy(hasGroupByDs); + } + }, [dataState.data?.state, model, panelSupportsGroupBy]); + + useEffect(() => { + if (isOpen) { + handleFetchOptions(); + } + }, [isOpen, handleFetchOptions]); const filteredOptions = useMemo(() => { if (!searchValue) { @@ -176,7 +204,7 @@ function PanelGroupByActionRenderer({ model }: SceneComponentProps opt.checked); }, [options]); - if (!groupByState || !panelHasGroupBy) { + if (!panelSupportsGroupBy) { return null; }