Compare commits

...

1 Commits

Author SHA1 Message Date
Ashwin Bhatkal
64d7719c90 chore: variable state + panel update 2026-02-09 23:38:12 +05:30
18 changed files with 684 additions and 294 deletions

View File

@@ -78,12 +78,10 @@ function TestWrapper({ children }: { children: React.ReactNode }): JSX.Element {
describe('VariableItem Integration Tests', () => {
let user: ReturnType<typeof userEvent.setup>;
let mockOnValueUpdate: jest.Mock;
let mockSetVariablesToGetUpdated: jest.Mock;
beforeEach(() => {
user = userEvent.setup();
mockOnValueUpdate = jest.fn();
mockSetVariablesToGetUpdated = jest.fn();
jest.clearAllMocks();
});
@@ -102,9 +100,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -150,9 +145,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -195,9 +187,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -247,9 +236,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -272,9 +258,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -308,9 +291,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -344,9 +324,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -369,9 +346,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -405,9 +379,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -461,9 +432,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -508,9 +476,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -548,9 +513,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);
@@ -582,9 +544,6 @@ describe('VariableItem Integration Tests', () => {
variableData={variable}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={null}
/>
</TestWrapper>,
);

View File

@@ -9,11 +9,15 @@ import {
import useVariablesFromUrl from 'hooks/dashboard/useVariablesFromUrl';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { initializeDefaultVariables } from 'providers/Dashboard/initializeDefaultVariables';
import { getVariableFetchContext } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStore';
import {
enqueueDescendants,
enqueueFetchAll,
} from 'providers/Dashboard/store/variableFetchStore';
import { AppState } from 'store/reducers';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
import { onUpdateVariableNode } from './util';
import VariableItem from './VariableItem';
import './DashboardVariableSelection.styles.scss';
@@ -22,8 +26,6 @@ function DashboardVariableSelection(): JSX.Element | null {
const {
setSelectedDashboard,
updateLocalStorageDashboardVariables,
variablesToGetUpdated,
setVariablesToGetUpdated,
} = useDashboard();
const { updateUrlVariable, getUrlVariables } = useVariablesFromUrl();
@@ -57,9 +59,7 @@ function DashboardVariableSelection(): JSX.Element | null {
// Trigger refetch when dependency order changes or global time changes
useEffect(() => {
if (dependencyData?.order && dependencyData.order.length > 0) {
setVariablesToGetUpdated(dependencyData?.order || []);
}
enqueueFetchAll(getVariableFetchContext());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dependencyOrderKey, minTime, maxTime]);
@@ -121,29 +121,14 @@ function DashboardVariableSelection(): JSX.Element | null {
return prev;
});
if (dependencyData) {
const updatedVariables: string[] = [];
onUpdateVariableNode(
name,
dependencyData.graph,
dependencyData.order,
(node) => updatedVariables.push(node),
);
setVariablesToGetUpdated((prev) => [
...new Set([...prev, ...updatedVariables.filter((v) => v !== name)]),
]);
} else {
setVariablesToGetUpdated((prev) => prev.filter((v) => v !== name));
}
// Cascade: enqueue query-type descendants for refetching
enqueueDescendants(name, getVariableFetchContext());
},
[
// This can be removed
dashboardVariables,
updateLocalStorageDashboardVariables,
dependencyData,
updateUrlVariable,
setSelectedDashboard,
setVariablesToGetUpdated,
],
);
@@ -158,9 +143,6 @@ function DashboardVariableSelection(): JSX.Element | null {
existingVariables={dashboardVariables}
variableData={variable}
onValueUpdate={onValueUpdate}
variablesToGetUpdated={variablesToGetUpdated}
setVariablesToGetUpdated={setVariablesToGetUpdated}
dependencyData={dependencyData}
/>
);
})}

View File

@@ -5,8 +5,15 @@ import { getFieldValues } from 'api/dynamicVariables/getFieldValues';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useVariableFetchState } from 'hooks/dashboard/useVariableFetchState';
import useDebounce from 'hooks/useDebounce';
import { isEmpty } from 'lodash-es';
import { getVariableFetchContext } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStore';
import {
completeFetch,
failFetch,
variableFetchStore,
} from 'providers/Dashboard/store/variableFetchStore';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { isRetryableError as checkIfRetryableError } from 'utils/errorUtils';
@@ -104,6 +111,12 @@ function DynamicVariableInput({
(state) => state.globalTime,
);
const {
isFetching: isVariableFetching,
hasLoaded,
state: fetchState,
} = useVariableFetchState(variableData.name || '');
// existing query is the query made from the other dynamic variables around this one with there current values
// for e.g. k8s.namespace.name IN ["zeus", "gene"] AND doc_op_type IN ["test"]
// eslint-disable-next-line sonarjs/cognitive-complexity
@@ -195,9 +208,9 @@ function DynamicVariableInput({
],
{
enabled:
variableData.type === 'DYNAMIC' &&
!!variableData.dynamicVariablesSource &&
!!variableData.dynamicVariablesAttribute,
!!variableData.dynamicVariablesAttribute &&
(isVariableFetching || (fetchState === 'idle' && hasLoaded)),
queryFn: () =>
getFieldValues(
variableData.dynamicVariablesSource?.toLowerCase() === 'all telemetry'
@@ -212,6 +225,7 @@ function DynamicVariableInput({
maxTime,
existingQuery,
),
// eslint-disable-next-line sonarjs/cognitive-complexity
onSuccess: (data) => {
const newNormalizedValues = data.data?.normalizedValues || [];
const newRelatedValues = data.data?.relatedValues || [];
@@ -258,6 +272,16 @@ function DynamicVariableInput({
];
applyDefaultIfNeeded(allNewOptions);
}
// Complete state machine fetch (skip for search/sibling-triggered refetches)
if (variableData.name) {
const currentState = variableFetchStore.getSnapshot().states[
variableData.name
];
if (currentState === 'loading' || currentState === 'revalidating') {
completeFetch(variableData.name, getVariableFetchContext());
}
}
},
onError: (error: any) => {
if (error) {
@@ -274,6 +298,16 @@ function DynamicVariableInput({
const isRetryable = checkIfRetryableError(error);
setIsRetryableError(isRetryable);
}
// Fail state machine fetch (skip for search/sibling-triggered refetches)
if (variableData.name) {
const currentState = variableFetchStore.getSnapshot().states[
variableData.name
];
if (currentState === 'loading' || currentState === 'revalidating') {
failFetch(variableData.name, getVariableFetchContext());
}
}
},
},
);
@@ -336,6 +370,7 @@ function DynamicVariableInput({
showRetryButton={isRetryableError}
showIncompleteDataMessage={!isComplete && filteredOptionsData.length > 0}
onSearch={handleSearch}
waiting={fetchState === 'waiting'}
/>
);
}

View File

@@ -3,8 +3,14 @@ import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useVariableFetchState } from 'hooks/dashboard/useVariableFetchState';
import sortValues from 'lib/dashboardVariables/sortVariableValues';
import { isArray, isString } from 'lodash-es';
import { getVariableFetchContext } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStore';
import {
completeFetch,
failFetch,
} from 'providers/Dashboard/store/variableFetchStore';
import { AppState } from 'store/reducers';
import { VariableResponseProps } from 'types/api/dashboard/variables/query';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -12,26 +18,18 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import { variablePropsToPayloadVariables } from '../utils';
import SelectVariableInput from './SelectVariableInput';
import { useDashboardVariableSelectHelper } from './useDashboardVariableSelectHelper';
import { areArraysEqual, checkAPIInvocation } from './util';
import { areArraysEqual } from './util';
import { VariableItemProps } from './VariableItem';
import { queryVariableSelectStrategy } from './variableSelectStrategy/queryVariableSelectStrategy';
type QueryVariableInputProps = Pick<
VariableItemProps,
| 'variableData'
| 'existingVariables'
| 'onValueUpdate'
| 'variablesToGetUpdated'
| 'setVariablesToGetUpdated'
| 'dependencyData'
'variableData' | 'existingVariables' | 'onValueUpdate'
>;
function QueryVariableInput({
variableData,
existingVariables,
variablesToGetUpdated,
setVariablesToGetUpdated,
dependencyData,
onValueUpdate,
}: QueryVariableInputProps): JSX.Element {
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
@@ -43,6 +41,10 @@ function QueryVariableInput({
(state) => state.globalTime,
);
const { isFetching, state: fetchState } = useVariableFetchState(
variableData.name || '',
);
const {
tempSelection,
setTempSelection,
@@ -60,16 +62,6 @@ function QueryVariableInput({
strategy: queryVariableSelectStrategy,
});
const validVariableUpdate = useCallback((): boolean => {
if (!variableData.name) {
return false;
}
return Boolean(
variablesToGetUpdated.length &&
variablesToGetUpdated[0] === variableData.name,
);
}, [variableData.name, variablesToGetUpdated]);
const getOptions = useCallback(
// eslint-disable-next-line sonarjs/cognitive-complexity
(variablesRes: VariableResponseProps | null): void => {
@@ -103,12 +95,7 @@ function QueryVariableInput({
valueNotInList = true;
}
// variablesData.allSelected is added for the case where on change of options we need to update the
// local storage
if (
variableData.name &&
(validVariableUpdate() || valueNotInList || variableData.allSelected)
) {
if (variableData.name && (valueNotInList || variableData.allSelected)) {
if (
variableData.allSelected &&
variableData.multiSelect &&
@@ -141,10 +128,6 @@ function QueryVariableInput({
setOptionsData(newOptionsData);
// Apply default if no value is selected (e.g., new variable, first load)
applyDefaultIfNeeded(newOptionsData);
} else {
setVariablesToGetUpdated((prev) =>
prev.filter((name) => name !== variableData.name),
);
}
}
} catch (e) {
@@ -157,8 +140,6 @@ function QueryVariableInput({
onValueUpdate,
tempSelection,
setTempSelection,
validVariableUpdate,
setVariablesToGetUpdated,
applyDefaultIfNeeded,
],
);
@@ -169,16 +150,9 @@ function QueryVariableInput({
variableData.name || '',
`${minTime}`,
`${maxTime}`,
JSON.stringify(dependencyData?.order),
],
{
enabled:
variableData &&
checkAPIInvocation(
variablesToGetUpdated,
variableData,
dependencyData?.parentDependencyGraph,
),
enabled: variableData && isFetching,
queryFn: () =>
dashboardVariablesQuery({
query: variableData.queryValue || '',
@@ -187,9 +161,9 @@ function QueryVariableInput({
refetchOnWindowFocus: false,
onSuccess: (response) => {
getOptions(response.payload);
setVariablesToGetUpdated((prev) =>
prev.filter((v) => v !== variableData.name),
);
if (variableData.name) {
completeFetch(variableData.name, getVariableFetchContext());
}
},
onError: (error: {
details: {
@@ -206,9 +180,9 @@ function QueryVariableInput({
}
setErrorMessage(message);
}
setVariablesToGetUpdated((prev) =>
prev.filter((v) => v !== variableData.name),
);
if (variableData.name) {
failFetch(variableData.name, getVariableFetchContext());
}
},
},
);
@@ -242,6 +216,7 @@ function QueryVariableInput({
loading={isLoading}
errorMessage={errorMessage}
onRetry={handleRetry}
waiting={fetchState === 'waiting'}
/>
);
}

View File

@@ -28,6 +28,7 @@ interface SelectVariableInputProps {
showRetryButton?: boolean;
showIncompleteDataMessage?: boolean;
onSearch?: (searchTerm: string) => void;
waiting?: boolean;
}
const MAX_TAG_DISPLAY_VALUES = 10;
@@ -65,6 +66,7 @@ function SelectVariableInput({
showRetryButton,
showIncompleteDataMessage,
onSearch,
waiting,
}: SelectVariableInputProps): JSX.Element {
const commonProps = useMemo(
() => ({
@@ -78,14 +80,17 @@ function SelectVariableInput({
className: 'variable-select',
popupClassName: 'dropdown-styles',
getPopupContainer: popupContainer,
style: SelectItemStyle,
showSearch: true,
bordered: false,
// changing props
'data-testid': 'variable-select',
onChange,
loading,
loading: loading || waiting,
style: {
...SelectItemStyle,
...(waiting && { opacity: 0.5, pointerEvents: 'none' as const }),
},
options,
errorMessage,
onRetry,
@@ -101,6 +106,7 @@ function SelectVariableInput({
defaultValue,
onChange,
loading,
waiting,
options,
value,
errorMessage,

View File

@@ -47,14 +47,6 @@ describe('VariableItem', () => {
variableData={mockVariableData}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
dependencyData={{
order: [],
graph: {},
parentDependencyGraph: {},
hasCycle: false,
}}
/>
</MockQueryClientProvider>,
);
@@ -69,14 +61,6 @@ describe('VariableItem', () => {
variableData={mockVariableData}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
dependencyData={{
order: [],
graph: {},
parentDependencyGraph: {},
hasCycle: false,
}}
/>
</MockQueryClientProvider>,
);
@@ -92,14 +76,6 @@ describe('VariableItem', () => {
variableData={mockVariableData}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
dependencyData={{
order: [],
graph: {},
parentDependencyGraph: {},
hasCycle: false,
}}
/>
</MockQueryClientProvider>,
);
@@ -133,14 +109,6 @@ describe('VariableItem', () => {
variableData={mockCustomVariableData}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
dependencyData={{
order: [],
graph: {},
parentDependencyGraph: {},
hasCycle: false,
}}
/>
</MockQueryClientProvider>,
);
@@ -163,14 +131,6 @@ describe('VariableItem', () => {
variableData={customVariableData}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
dependencyData={{
order: [],
graph: {},
parentDependencyGraph: {},
hasCycle: false,
}}
/>
</MockQueryClientProvider>,
);
@@ -185,14 +145,6 @@ describe('VariableItem', () => {
variableData={mockCustomVariableData}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
dependencyData={{
order: [],
graph: {},
parentDependencyGraph: {},
hasCycle: false,
}}
/>
</MockQueryClientProvider>,
);

View File

@@ -1,7 +1,6 @@
import { memo } from 'react';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Tooltip, Typography } from 'antd';
import { IDependencyData } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import CustomVariableInput from './CustomVariableInput';
@@ -21,18 +20,12 @@ export interface VariableItemProps {
allSelected: boolean,
haveCustomValuesSelected?: boolean,
) => void;
variablesToGetUpdated: string[];
setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>;
dependencyData: IDependencyData | null;
}
function VariableItem({
variableData,
onValueUpdate,
existingVariables,
variablesToGetUpdated,
setVariablesToGetUpdated,
dependencyData,
}: VariableItemProps): JSX.Element {
const { name, description, type: variableType } = variableData;
@@ -65,9 +58,6 @@ function VariableItem({
variableData={variableData}
onValueUpdate={onValueUpdate}
existingVariables={existingVariables}
variablesToGetUpdated={variablesToGetUpdated}
setVariablesToGetUpdated={setVariablesToGetUpdated}
dependencyData={dependencyData}
/>
)}
{variableType === 'DYNAMIC' && (

View File

@@ -8,14 +8,6 @@ import '@testing-library/jest-dom/extend-expect';
import VariableItem from '../VariableItem';
const mockOnValueUpdate = jest.fn();
const mockSetVariablesToGetUpdated = jest.fn();
const baseDependencyData = {
order: [],
graph: {},
parentDependencyGraph: {},
hasCycle: false,
};
const TEST_VARIABLE_ID = 'test_variable';
const VARIABLE_SELECT_TESTID = 'variable-select';
@@ -31,9 +23,6 @@ const renderVariableItem = (
variableData={variableData}
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
dependencyData={baseDependencyData}
/>
</MockQueryClientProvider>,
);

View File

@@ -1,5 +1,5 @@
import { OptionData } from 'components/NewSelect/types';
import { isEmpty, isNull } from 'lodash-es';
import { isNull } from 'lodash-es';
import {
IDashboardVariables,
IDependencyData,
@@ -284,33 +284,6 @@ export const onUpdateVariableNode = (
});
};
export const checkAPIInvocation = (
variablesToGetUpdated: string[],
variableData: IDashboardVariable,
parentDependencyGraph?: VariableGraph,
): boolean => {
if (isEmpty(variableData.name)) {
return false;
}
if (isEmpty(parentDependencyGraph)) {
return false;
}
// if no dependency then true
const haveDependency =
parentDependencyGraph?.[variableData.name || '']?.length > 0;
if (!haveDependency) {
return true;
}
// if variable is in the list and has dependency then check if its the top element in the queue then true else false
return (
variablesToGetUpdated.length > 0 &&
variablesToGetUpdated[0] === variableData.name
);
};
export const getOptionsForDynamicVariable = (
normalizedValues: (string | number | boolean)[],
relatedValues: string[],

View File

@@ -49,15 +49,11 @@ const mockDashboard = {
// Mock the dashboard provider with stable functions to prevent infinite loops
const mockSetSelectedDashboard = jest.fn();
const mockUpdateLocalStorageDashboardVariables = jest.fn();
const mockSetVariablesToGetUpdated = jest.fn();
jest.mock('providers/Dashboard/Dashboard', () => ({
useDashboard: (): any => ({
selectedDashboard: mockDashboard,
setSelectedDashboard: mockSetSelectedDashboard,
updateLocalStorageDashboardVariables: mockUpdateLocalStorageDashboardVariables,
variablesToGetUpdated: ['env'], // Stable initial value
setVariablesToGetUpdated: mockSetVariablesToGetUpdated,
}),
}));

View File

@@ -6,6 +6,7 @@ import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { populateMultipleResults } from 'container/NewWidget/LeftContainer/WidgetGraph/util';
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/types';
import { useDependentVariablesFetching } from 'hooks/dashboard/useVariableFetchState';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useIntersectionObserver } from 'hooks/useIntersectionObserver';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
@@ -64,8 +65,12 @@ function GridCardGraph({
toScrollWidgetId,
setToScrollWidgetId,
setDashboardQueryRangeCalled,
variablesToGetUpdated,
} = useDashboard();
const anyDependentVarFetching = useDependentVariablesFetching(
widget.query,
variables,
);
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
@@ -177,27 +182,6 @@ function GridCardGraph({
[requestData.query],
);
// Bring back dependency on variable chaining for panels to refetch,
// but only for non-dynamic variables. We derive a stable token from
// the head of the variablesToGetUpdated queue when it's non-dynamic.
const nonDynamicVariableChainToken = useMemo(() => {
if (!variablesToGetUpdated || variablesToGetUpdated.length === 0) {
return undefined;
}
if (!variables) {
return undefined;
}
const headName = variablesToGetUpdated[0];
const variableObj = Object.values(variables).find(
(variable) => variable?.name === headName,
);
if (variableObj && variableObj.type !== 'DYNAMIC') {
return headName;
}
return undefined;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [variablesToGetUpdated, variables]);
const queryResponse = useGetQueryRange(
{
...requestData,
@@ -237,9 +221,6 @@ function GridCardGraph({
...(customTimeRange && customTimeRange.startTime && customTimeRange.endTime
? [customTimeRange.startTime, customTimeRange.endTime]
: []),
// Include non-dynamic variable chaining token to drive refetches
// only when a non-dynamic variable is at the head of the queue
...(nonDynamicVariableChainToken ? [nonDynamicVariableChainToken] : []),
],
retry(failureCount, error): boolean {
if (
@@ -252,7 +233,7 @@ function GridCardGraph({
return failureCount < 2;
},
keepPreviousData: true,
enabled: queryEnabledCondition && !nonDynamicVariableChainToken,
enabled: queryEnabledCondition && !anyDependentVarFetching,
refetchOnMount: false,
onError: (error) => {
const errorMessage =
@@ -318,9 +299,7 @@ function GridCardGraph({
version={version}
threshold={threshold}
headerMenuList={menuList}
isFetchingResponse={
queryResponse.isFetching || variablesToGetUpdated.length > 0
}
isFetchingResponse={queryResponse.isFetching || anyDependentVarFetching}
setRequestData={setRequestData}
onClickHandler={onClickHandler}
onDragSelect={onDragSelect}

View File

@@ -0,0 +1,280 @@
import {
useCallback,
useEffect,
useMemo,
useRef,
useSyncExternalStore,
} from 'react';
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
import {
IVariableFetchStoreState,
VariableFetchState,
variableFetchStore,
} from 'providers/Dashboard/store/variableFetchStore';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { useDashboardVariablesSelector } from './useDashboardVariables';
/**
* Generic selector hook for the variable fetch store.
* Same pattern as useDashboardVariablesSelector.
*/
const useVariableFetchSelector = <T>(
selector: (state: IVariableFetchStoreState) => T,
): T => {
const selectorRef = useRef(selector);
selectorRef.current = selector;
const getSnapshot = useCallback(
() => selectorRef.current(variableFetchStore.getSnapshot()),
[],
);
return useSyncExternalStore(variableFetchStore.subscribe, getSnapshot);
};
interface UseVariableFetchStateReturn {
/** The current fetch state for this variable */
state: VariableFetchState;
/** True if any ancestor variable is not idle (still loading/waiting) */
isBlocked: boolean;
/** True if this variable is actively fetching (loading or revalidating) */
isFetching: boolean;
/** True if this variable has completed at least one fetch cycle */
hasLoaded: boolean;
}
/**
* Per-variable hook that exposes the fetch state of a single variable.
* Reusable by both variable input components and panel components.
*
* Subscribes to both variableFetchStore (for states) and
* dashboardVariablesStore (for parent graph) to compute derived values.
*/
export function useVariableFetchState(
variableName: string,
): UseVariableFetchStateReturn {
const state = useVariableFetchSelector(
(s) => s.states[variableName] || 'idle',
) as VariableFetchState;
const allStates = useVariableFetchSelector((s) => s.states);
const parentGraph = useDashboardVariablesSelector(
(s) => s.dependencyData?.parentDependencyGraph,
);
const lastUpdated = useVariableFetchSelector(
(s) => s.lastUpdated[variableName] || 0,
);
const isFetching = state === 'loading' || state === 'revalidating';
const hasLoaded = lastUpdated > 0;
const isBlocked = useMemo(() => {
const parents = parentGraph?.[variableName] || [];
return parents.some((p) => (allStates[p] || 'idle') !== 'idle');
}, [parentGraph, variableName, allStates]);
return { state, isBlocked, isFetching, hasLoaded };
}
// ---------------------------------------------------------------------------
// Query-text extraction utilities for finding variable references in widgets
// ---------------------------------------------------------------------------
function escapeRegExp(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Collects every text fragment from a Query that may contain variable
* references (filter values, filter expressions, raw SQL, PromQL).
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
function collectQueryTexts(query: Query): string[] {
const texts: string[] = [];
if (query.builder?.queryData) {
for (const qd of query.builder.queryData) {
if (qd.filters?.items) {
for (const item of qd.filters.items) {
if (typeof item.value === 'string') {
texts.push(item.value);
} else if (Array.isArray(item.value)) {
for (const v of item.value) {
if (typeof v === 'string') {
texts.push(v);
}
}
}
}
}
if (qd.filter?.expression) {
texts.push(qd.filter.expression);
}
}
}
if (query.clickhouse_sql) {
for (const cs of query.clickhouse_sql) {
if (cs.query) {
texts.push(cs.query);
}
}
}
if (query.promql) {
for (const pq of query.promql) {
if (pq.query) {
texts.push(pq.query);
}
}
}
return texts;
}
/**
* Returns the names of all dashboard variables referenced in a widget query.
* Supports {{.var}}, {{var}}, $var, and [[var]] syntaxes.
*/
export function getReferencedVariableNames(
query: Query,
variables: IDashboardVariables,
): string[] {
const texts = collectQueryTexts(query);
const allText = texts.join(' ');
if (!allText) {
return [];
}
const names: string[] = [];
Object.values(variables).forEach((v) => {
if (!v.name) {
return;
}
const escaped = escapeRegExp(v.name);
const pattern = new RegExp(
`\\{\\{\\s*\\.?\\s*${escaped}\\s*\\}\\}` +
`|\\$${escaped}(?![a-zA-Z0-9_.\\-])` +
`|\\[\\[\\s*${escaped}\\s*\\]\\]`,
);
if (pattern.test(allText)) {
names.push(v.name);
}
});
return names;
}
// ---------------------------------------------------------------------------
// Panel-targeted hook
// ---------------------------------------------------------------------------
/**
* Returns true when a widget's query should be blocked / show a loader
* because a dependent variable is not ready.
*
* Phase 1 Initial load:
* Checks the fetch-state of every variable referenced by `query`.
* If any dependent variable is non-idle → returns true (show loading).
* Once they all settle the hook transitions to Phase 2.
*
* Phase 2 Post initial load:
* Keeps a map of dependent variable name → serialised selectedValue.
* When a dependent variable's selectedValue changes:
* • allSelected = true → returns true until that variable is idle.
* • allSelected = false → returns false (react-query picks up the
* query-key change automatically).
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
export function useDependentVariablesFetching(
query: Query | undefined,
variables: IDashboardVariables | undefined,
): boolean {
const allStates = useVariableFetchSelector((s) => s.states);
// ---- 1. Derive dependent variable names from the widget query ----------
const dependentVarNames = useMemo(() => {
if (!query || !variables) {
return [] as string[];
}
return getReferencedVariableNames(query, variables);
}, [query, variables]);
// ---- 2. Are any dependent variables currently non-idle? ----------------
const anyDependentNonIdle = useMemo(
() =>
dependentVarNames.some(
(n) => allStates[n] !== undefined && allStates[n] !== 'idle',
),
[dependentVarNames, allStates],
);
// ---- 3. Track initial fetch cycle (non-idle → idle) --------------------
const didStartFetching = useRef(false);
const hasCompletedInitialFetch = useRef(false);
useEffect(() => {
if (anyDependentNonIdle) {
didStartFetching.current = true;
} else if (didStartFetching.current && !hasCompletedInitialFetch.current) {
hasCompletedInitialFetch.current = true;
}
}, [anyDependentNonIdle]);
// ---- 4. Track selectedValues snapshot ----------------------------------
const lastSettledValues = useRef<Record<string, string>>({});
const currentValues = useMemo(() => {
if (!variables) {
return {} as Record<string, string>;
}
const values: Record<string, string> = {};
Object.values(variables).forEach((v) => {
if (v.name && dependentVarNames.includes(v.name)) {
values[v.name] = JSON.stringify(v.selectedValue);
}
});
return values;
}, [variables, dependentVarNames]);
// Update snapshot whenever all dependent vars are idle
useEffect(() => {
if (!anyDependentNonIdle) {
lastSettledValues.current = currentValues;
}
}, [anyDependentNonIdle, currentValues]);
// ---- 5. Compute result ------------------------------------------------
// Phase 1 initial load: block while any dependent variable is loading
if (!hasCompletedInitialFetch.current) {
return anyDependentNonIdle;
}
// Phase 2 post initial load: block only for allSelected vars that
// have a changed value and are still fetching
if (!variables) {
return false;
}
return dependentVarNames.some((name) => {
const variable = Object.values(variables).find((v) => v.name === name);
if (!variable?.allSelected) {
return false;
}
// For allSelected variables: block if their fetch state is non-idle,
// meaning the value (= full option set) is stale or about to change.
const state = allStates[name];
if (!state || state === 'idle') {
return false;
}
// Only block if the value has actually drifted from the last settled
// snapshot — avoids blocking when an unrelated refetch is in progress.
return currentValues[name] !== lastSettledValues.current[name];
});
}

View File

@@ -84,8 +84,6 @@ const DashboardContext = createContext<IDashboardContext>({
toScrollWidgetId: '',
setToScrollWidgetId: () => {},
updateLocalStorageDashboardVariables: () => {},
variablesToGetUpdated: [],
setVariablesToGetUpdated: () => {},
dashboardQueryRangeCalled: false,
setDashboardQueryRangeCalled: () => {},
selectedRowWidgetId: '',
@@ -183,10 +181,6 @@ export function DashboardProvider({
exact: true,
});
const [variablesToGetUpdated, setVariablesToGetUpdated] = useState<string[]>(
[],
);
const [layouts, setLayouts] = useState<Layout[]>([]);
const [panelMap, setPanelMap] = useState<
@@ -517,8 +511,6 @@ export function DashboardProvider({
updatedTimeRef,
setToScrollWidgetId,
updateLocalStorageDashboardVariables,
variablesToGetUpdated,
setVariablesToGetUpdated,
dashboardQueryRangeCalled,
setDashboardQueryRangeCalled,
selectedRowWidgetId,
@@ -541,8 +533,6 @@ export function DashboardProvider({
toScrollWidgetId,
updateLocalStorageDashboardVariables,
currentDashboard,
variablesToGetUpdated,
setVariablesToGetUpdated,
dashboardQueryRangeCalled,
setDashboardQueryRangeCalled,
selectedRowWidgetId,

View File

@@ -1,4 +1,5 @@
import createStore from '../store';
import { VariableFetchContext } from '../variableFetchStore';
import { IDashboardVariablesStoreState } from './dashboardVariablesStoreTypes';
import {
computeDerivedValues,
@@ -10,6 +11,8 @@ const initialState: IDashboardVariablesStoreState = {
variables: {},
sortedVariablesArray: [],
dependencyData: null,
variableTypes: {},
dynamicVariableOrder: [],
};
export const dashboardVariablesStore = createStore<IDashboardVariablesStoreState>(
@@ -55,3 +58,17 @@ export function updateDashboardVariablesStore({
updateDerivedValues(draft);
});
}
/**
* Read current store snapshot as VariableFetchContext.
* Used by components to pass context to variableFetchStore actions
* without creating a circular import.
*/
export function getVariableFetchContext(): VariableFetchContext {
const state = dashboardVariablesStore.getSnapshot();
return {
variableTypes: state.variableTypes,
dynamicVariableOrder: state.dynamicVariableOrder,
dependencyData: state.dependencyData,
};
}

View File

@@ -1,4 +1,7 @@
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import {
IDashboardVariable,
TVariableQueryType,
} from 'types/api/dashboard/getAll';
export type VariableGraph = Record<string, string[]>;
@@ -24,6 +27,12 @@ export interface IDashboardVariablesStoreState {
// Derived: dependency data for QUERY variables
dependencyData: IDependencyData | null;
// Derived: variable name → type mapping
variableTypes: Record<string, TVariableQueryType>;
// Derived: display-ordered list of dynamic variable names
dynamicVariableOrder: string[];
}
export interface IUseDashboardVariablesReturn {

View File

@@ -2,7 +2,10 @@ import {
buildDependencies,
buildDependencyGraph,
} from 'container/DashboardContainer/DashboardVariablesSelection/util';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import {
IDashboardVariable,
TVariableQueryType,
} from 'types/api/dashboard/getAll';
import { initializeVariableFetchStore } from '../variableFetchStore';
import {
@@ -64,23 +67,42 @@ export function buildDependencyData(
}
/**
* Initialize the variable fetch store with the computed dependency data
* Build a variable name → type mapping from sorted variables array
*/
export function buildVariableTypesMap(
sortedVariablesArray: IDashboardVariable[],
): Record<string, TVariableQueryType> {
const types: Record<string, TVariableQueryType> = {};
sortedVariablesArray.forEach((v) => {
if (v.name) {
types[v.name] = v.type;
}
});
return types;
}
/**
* Build display-ordered list of dynamic variable names
*/
export function buildDynamicVariableOrder(
sortedVariablesArray: IDashboardVariable[],
): string[] {
return sortedVariablesArray
.filter((v) => v.type === 'DYNAMIC' && v.name)
.map((v) => v.name as string);
}
/**
* Initialize the variable fetch store with variable names
*/
function initializeFetchStore(
sortedVariablesArray: IDashboardVariable[],
dependencyData: IDependencyData | null,
): void {
if (dependencyData) {
const allVariableNames = sortedVariablesArray
.map((v) => v.name)
.filter((name): name is string => !!name);
const allVariableNames = sortedVariablesArray
.map((v) => v.name)
.filter((name): name is string => !!name);
initializeVariableFetchStore(
allVariableNames,
dependencyData.graph,
dependencyData.parentDependencyGraph,
);
}
initializeVariableFetchStore(allVariableNames);
}
/**
@@ -92,15 +114,25 @@ export function computeDerivedValues(
variables: IDashboardVariablesStoreState['variables'],
): Pick<
IDashboardVariablesStoreState,
'sortedVariablesArray' | 'dependencyData'
| 'sortedVariablesArray'
| 'dependencyData'
| 'variableTypes'
| 'dynamicVariableOrder'
> {
const sortedVariablesArray = buildSortedVariablesArray(variables);
const dependencyData = buildDependencyData(sortedVariablesArray);
const variableTypes = buildVariableTypesMap(sortedVariablesArray);
const dynamicVariableOrder = buildDynamicVariableOrder(sortedVariablesArray);
// Initialize the variable fetch store when dependency data is computed
initializeFetchStore(sortedVariablesArray, dependencyData);
initializeFetchStore(sortedVariablesArray);
return { sortedVariablesArray, dependencyData };
return {
sortedVariablesArray,
dependencyData,
variableTypes,
dynamicVariableOrder,
};
}
/**
@@ -112,7 +144,11 @@ export function updateDerivedValues(
): void {
draft.sortedVariablesArray = buildSortedVariablesArray(draft.variables);
draft.dependencyData = buildDependencyData(draft.sortedVariablesArray);
draft.variableTypes = buildVariableTypesMap(draft.sortedVariablesArray);
draft.dynamicVariableOrder = buildDynamicVariableOrder(
draft.sortedVariablesArray,
);
// Initialize the variable fetch store when dependency data is updated
initializeFetchStore(draft.sortedVariablesArray, draft.dependencyData);
initializeFetchStore(draft.sortedVariablesArray);
}

View File

@@ -1,5 +1,7 @@
import { VariableGraph } from 'container/DashboardContainer/DashboardVariablesSelection/util';
import { TVariableQueryType } from 'types/api/dashboard/getAll';
import { IDashboardVariablesStoreState } from './dashboardVariables/dashboardVariablesStoreTypes';
import createStore from './store';
// Fetch state for each variable
@@ -14,44 +16,266 @@ export interface IVariableFetchStoreState {
// Per-variable fetch state
states: Record<string, VariableFetchState>;
// Dependency graphs (set once when variables change)
dependencyGraph: VariableGraph; // variable -> children that depend on it
parentGraph: VariableGraph; // variable -> parents it depends on
// Track last update timestamp per variable to trigger re-fetches
// Track last update timestamp per variable
lastUpdated: Record<string, number>;
// JSON-serialized selectedValue per variable — used to detect value changes
lastValues: Record<string, string>;
}
/**
* Context from dashboardVariablesStore needed by fetch actions.
* Passed as parameter to avoid circular imports.
*/
export type VariableFetchContext = Pick<
IDashboardVariablesStoreState,
'variableTypes' | 'dynamicVariableOrder' | 'dependencyData'
>;
const initialState: IVariableFetchStoreState = {
states: {},
dependencyGraph: {},
parentGraph: {},
lastUpdated: {},
lastValues: {},
};
export const variableFetchStore = createStore<IVariableFetchStoreState>(
initialState,
);
// ============== Helpers ==============
/**
* BFS to collect all transitive descendants of a node in the dependency graph.
*/
function collectAllDescendants(node: string, graph: VariableGraph): string[] {
const descendants: string[] = [];
const visited = new Set<string>();
const queue = [...(graph[node] || [])];
while (queue.length > 0) {
const current = queue.shift();
if (!current || visited.has(current)) {
continue;
}
visited.add(current);
descendants.push(current);
(graph[current] || []).forEach((child) => {
if (!visited.has(child)) {
queue.push(child);
}
});
}
return descendants;
}
/**
* Check if all query variables are settled (idle or error).
*/
function areAllQueryVarsSettled(
states: Record<string, VariableFetchState>,
variableTypes: Record<string, TVariableQueryType>,
): boolean {
return Object.entries(variableTypes)
.filter(([, type]) => type === 'QUERY')
.every(([name]) => states[name] === 'idle' || states[name] === 'error');
}
/**
* Transition waiting dynamic variables to loading/revalidating.
*/
function unlockDynamicVariables(
draft: IVariableFetchStoreState,
dynamicVariableOrder: string[],
): void {
dynamicVariableOrder.forEach((dynName) => {
if (draft.states[dynName] === 'waiting') {
const hasData = (draft.lastUpdated[dynName] || 0) > 0;
draft.states[dynName] = hasData ? 'revalidating' : 'loading';
}
});
}
// ============== Actions ==============
/**
* Initialize the store with dependency graphs and set initial states
* Initialize the store with variable names.
* Called when dashboard variables change — sets up state entries.
*/
export function initializeVariableFetchStore(
variableNames: string[],
dependencyGraph: VariableGraph,
parentGraph: VariableGraph,
): void {
export function initializeVariableFetchStore(variableNames: string[]): void {
variableFetchStore.update((draft) => {
draft.dependencyGraph = dependencyGraph;
draft.parentGraph = parentGraph;
// Initialize all variables to idle, preserving existing ready states
// Initialize all variables to idle, preserving existing states
variableNames.forEach((name) => {
if (!draft.states[name]) {
draft.states[name] = 'idle';
}
if (!draft.lastValues[name]) {
draft.lastValues[name] = '';
}
});
// Clean up stale entries for variables that no longer exist
const nameSet = new Set(variableNames);
Object.keys(draft.states).forEach((name) => {
if (!nameSet.has(name)) {
delete draft.states[name];
delete draft.lastValues[name];
delete draft.lastUpdated[name];
}
});
});
}
/**
* Mark a variable as completed. Unblocks waiting query-type children.
* If all query variables are now settled, unlocks dynamic variables.
*/
export function completeFetch(name: string, ctx: VariableFetchContext): void {
const { dependencyData, variableTypes, dynamicVariableOrder } = ctx;
variableFetchStore.update((draft) => {
draft.states[name] = 'idle';
draft.lastUpdated[name] = Date.now();
if (!dependencyData) {
return;
}
const { graph } = dependencyData;
// Unblock waiting query-type children
const children = graph[name] || [];
children.forEach((child) => {
if (variableTypes[child] === 'QUERY' && draft.states[child] === 'waiting') {
const hasData = (draft.lastUpdated[child] || 0) > 0;
draft.states[child] = hasData ? 'revalidating' : 'loading';
}
});
// If all query vars are settled, unlock dynamic variables
if (
variableTypes[name] === 'QUERY' &&
areAllQueryVarsSettled(draft.states, variableTypes)
) {
unlockDynamicVariables(draft, dynamicVariableOrder);
}
});
}
/**
* Mark a variable as errored. Sets query-type descendants to idle
* (they can't proceed without this parent).
* If all query variables are now settled, unlocks dynamic variables.
*/
export function failFetch(name: string, ctx: VariableFetchContext): void {
const { dependencyData, variableTypes, dynamicVariableOrder } = ctx;
variableFetchStore.update((draft) => {
draft.states[name] = 'error';
if (!dependencyData) {
return;
}
const { graph } = dependencyData;
// Set query-type descendants to idle (can't fetch without parent)
const descendants = collectAllDescendants(name, graph);
descendants.forEach((desc) => {
if (variableTypes[desc] === 'QUERY') {
draft.states[desc] = 'idle';
}
});
// If all query vars are settled (error counts), unlock dynamic vars
if (
variableTypes[name] === 'QUERY' &&
areAllQueryVarsSettled(draft.states, variableTypes)
) {
unlockDynamicVariables(draft, dynamicVariableOrder);
}
});
}
/**
* Cascade a value change to query-type descendants.
* Called when a user changes a variable's value (not from a fetch cycle).
*
* Direct children whose parents are all settled start immediately.
* Deeper descendants wait until their parents complete (BFS order
* ensures parents are set before children within a single update).
*/
export function enqueueDescendants(
name: string,
ctx: VariableFetchContext,
): void {
const { dependencyData, variableTypes } = ctx;
if (!dependencyData) {
return;
}
const { graph, parentDependencyGraph } = dependencyData;
variableFetchStore.update((draft) => {
const descendants = collectAllDescendants(name, graph);
descendants.forEach((desc) => {
if (variableTypes[desc] !== 'QUERY') {
return;
}
const parents = parentDependencyGraph[desc] || [];
const allParentsSettled = parents.every(
(p) => draft.states[p] === 'idle' || draft.states[p] === 'error',
);
if (allParentsSettled) {
const hasData = (draft.lastUpdated[desc] || 0) > 0;
draft.states[desc] = hasData ? 'revalidating' : 'loading';
} else {
draft.states[desc] = 'waiting';
}
});
});
}
/**
* Start a full fetch cycle for all fetchable variables.
* Called on: initial load, time range change, or dependency graph change.
*
* Query variables with no query-type parents start immediately.
* Query variables with parents get 'waiting'.
* Dynamic variables get 'waiting' — unblocked once all query vars complete.
* If there are no query variables, dynamic vars start immediately.
*/
export function enqueueFetchAll(ctx: VariableFetchContext): void {
const { dependencyData, variableTypes, dynamicVariableOrder } = ctx;
if (!dependencyData) {
return;
}
const { order, parentDependencyGraph } = dependencyData;
variableFetchStore.update((draft) => {
// Query variables: root ones start immediately, dependent ones wait
order.forEach((name) => {
const parents = parentDependencyGraph[name] || [];
const hasQueryParents = parents.some((p) => variableTypes[p] === 'QUERY');
if (hasQueryParents) {
draft.states[name] = 'waiting';
} else {
const hasData = (draft.lastUpdated[name] || 0) > 0;
draft.states[name] = hasData ? 'revalidating' : 'loading';
}
});
// Dynamic variables: always start as 'waiting'
dynamicVariableOrder.forEach((name) => {
draft.states[name] = 'waiting';
});
// If no query variables exist, unlock dynamic vars immediately
if (order.length === 0 && dynamicVariableOrder.length > 0) {
unlockDynamicVariables(draft, dynamicVariableOrder);
}
});
}

View File

@@ -47,8 +47,6 @@ export interface IDashboardContext {
allSelected: boolean,
isDynamic?: boolean,
) => void;
variablesToGetUpdated: string[];
setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>;
dashboardQueryRangeCalled: boolean;
setDashboardQueryRangeCalled: (value: boolean) => void;
selectedRowWidgetId: string | null;