Compare commits

...

1 Commits

Author SHA1 Message Date
Ashwin Bhatkal
5859cb9485 chore: variable store isolation 2026-01-29 18:58:31 +05:30
36 changed files with 352 additions and 249 deletions

2
.github/CODEOWNERS vendored
View File

@@ -110,6 +110,7 @@
# Dashboard Owners
/frontend/src/hooks/dashboard/ @SigNoz/pulse-frontend
/frontend/src/providers/Dashboard/ @SigNoz/pulse-frontend
## Dashboard Types
@@ -131,3 +132,4 @@
/frontend/src/pages/PublicDashboard/ @SigNoz/pulse-frontend
/frontend/src/container/PublicDashboardContainer/ @SigNoz/pulse-frontend

View File

@@ -105,6 +105,7 @@
"i18next": "^21.6.12",
"i18next-browser-languagedetector": "^6.1.3",
"i18next-http-backend": "^1.3.2",
"immer": "11.1.3",
"jest": "^27.5.1",
"js-base64": "^3.7.2",
"less": "^4.1.2",

View File

@@ -32,13 +32,12 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
import useDebounce from 'hooks/useDebounce';
import { debounce, isNull } from 'lodash-es';
import { Info, TriangleAlert } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariablesByType } from 'providers/Dashboard/store/useDashboardVariablesByType';
import {
IDetailedError,
IQueryContext,
IValidationResult,
} from 'types/antlrQueryTypes';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
import { DataSource } from 'types/common/queryBuilder';
@@ -207,14 +206,9 @@ function QuerySearch({
const lastValueRef = useRef<string>('');
const isMountedRef = useRef<boolean>(true);
const { selectedDashboard } = useDashboard();
const dynamicVariables = useMemo(
() =>
Object.values(selectedDashboard?.data?.variables || {})?.filter(
(variable: IDashboardVariable) => variable.type === 'DYNAMIC',
),
[selectedDashboard],
const dashboardDynamicVariables = useDashboardVariablesByType(
'DYNAMIC',
'values',
);
// Add back the generateOptions function and useEffect
@@ -1069,7 +1063,7 @@ function QuerySearch({
);
// Add dynamic variables suggestions for the current key
const variableName = dynamicVariables?.find(
const variableName = dashboardDynamicVariables?.find(
(variable) => variable?.dynamicVariablesAttribute === keyName,
)?.name;

View File

@@ -43,8 +43,9 @@ import {
} from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import { sortLayout } from 'providers/Dashboard/util';
import { DashboardData, IDashboardVariable } from 'types/api/dashboard/getAll';
import { DashboardData } from 'types/api/dashboard/getAll';
import { Props } from 'types/api/dashboard/update';
import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission';
@@ -56,7 +57,11 @@ import { Base64Icons } from '../DashboardSettings/General/utils';
import DashboardVariableSelection from '../DashboardVariablesSelection';
import SettingsDrawer from './SettingsDrawer';
import { VariablesSettingsTab } from './types';
import { DEFAULT_ROW_NAME, downloadObjectAsJson } from './utils';
import {
DEFAULT_ROW_NAME,
downloadObjectAsJson,
sanitizeDashboardData,
} from './utils';
import './Description.styles.scss';
@@ -64,28 +69,6 @@ interface DashboardDescriptionProps {
handle: FullScreenHandle;
}
export function sanitizeDashboardData(
selectedData: DashboardData,
): DashboardData {
if (!selectedData?.variables) {
return selectedData;
}
const updatedVariables = Object.entries(selectedData.variables).reduce(
(acc, [key, value]) => {
const { selectedValue: _selectedValue, ...rest } = value;
acc[key] = rest;
return acc;
},
{} as Record<string, IDashboardVariable>,
);
return {
...selectedData,
variables: updatedVariables,
};
}
// eslint-disable-next-line sonarjs/cognitive-complexity
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
@@ -119,6 +102,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
uuid: selectedDashboard.id,
}
: ({} as DashboardData);
const { dashboardVariables } = useDashboardVariables();
const { title = '', description, tags, image = Base64Icons[0] } =
selectedData || {};
@@ -576,7 +560,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
<section className="dashboard-description-section">{description}</section>
)}
{!isEmpty(selectedData.variables) && (
{!isEmpty(dashboardVariables) && (
<section className="dashboard-variables">
<DashboardVariableSelection />
</section>

View File

@@ -1,3 +1,27 @@
import { DashboardData, IDashboardVariable } from 'types/api/dashboard/getAll';
export function sanitizeDashboardData(
selectedData: DashboardData,
): DashboardData {
if (!selectedData?.variables) {
return selectedData;
}
const updatedVariables = Object.entries(selectedData.variables).reduce(
(acc, [key, value]) => {
const { selectedValue: _selectedValue, ...rest } = value;
acc[key] = rest;
return acc;
},
{} as Record<string, IDashboardVariable>,
);
return {
...selectedData,
variables: updatedVariables,
};
}
export function downloadObjectAsJson(
exportObj: unknown,
exportName: string,

View File

@@ -14,10 +14,7 @@ import { CustomSelect } from 'components/NewSelect';
import TextToolTip from 'components/TextToolTip';
import { PANEL_GROUP_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import {
createDynamicVariableToWidgetsMap,
getWidgetsHavingDynamicVariableAttribute,
} from 'hooks/dashboard/utils';
import { getWidgetsHavingDynamicVariableAttribute } from 'hooks/dashboard/utils';
import { useGetFieldValues } from 'hooks/dynamicVariables/useGetFieldValues';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
@@ -34,6 +31,7 @@ import {
X,
} from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useWidgetsByDynamicVariableId } from 'providers/Dashboard/store/useWidgetsByDynamicVariableId';
import { AppState } from 'store/reducers';
import {
IDashboardVariable,
@@ -243,23 +241,11 @@ function VariableItem({
const [selectedWidgets, setSelectedWidgets] = useState<string[]>([]);
const { selectedDashboard } = useDashboard();
const widgetsByDynamicVariableId = useWidgetsByDynamicVariableId();
useEffect(() => {
const dynamicVariables = Object.values(
selectedDashboard?.data?.variables || {},
)?.filter((variable: IDashboardVariable) => variable.type === 'DYNAMIC');
const widgets =
selectedDashboard?.data?.widgets?.filter(
(widget) => widget.panelTypes !== PANEL_GROUP_TYPES.ROW,
) || [];
const widgetsHavingDynamicVariables = createDynamicVariableToWidgetsMap(
dynamicVariables,
widgets as Widgets[],
);
if (variableData?.id && variableData.id in widgetsHavingDynamicVariables) {
setSelectedWidgets(widgetsHavingDynamicVariables[variableData.id] || []);
if (variableData?.id && variableData.id in widgetsByDynamicVariableId) {
setSelectedWidgets(widgetsByDynamicVariableId[variableData.id] || []);
} else if (dynamicVariablesSelectedValue?.name) {
const widgets = getWidgetsHavingDynamicVariableAttribute(
dynamicVariablesSelectedValue?.name,
@@ -275,6 +261,7 @@ function VariableItem({
selectedDashboard,
variableData.id,
variableData.name,
widgetsByDynamicVariableId,
]);
useEffect(() => {

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { HolderOutlined, PlusOutlined } from '@ant-design/icons';
import type { DragEndEvent, UniqueIdentifier } from '@dnd-kit/core';
@@ -21,7 +21,9 @@ import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications';
import { PenLine, Trash2 } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { TVariableMode } from './types';
import VariableItem from './VariableItem/VariableItem';
@@ -91,13 +93,10 @@ function VariablesSettings({
const { t } = useTranslation(['dashboard']);
const { selectedDashboard, setSelectedDashboard } = useDashboard();
const { dashboardVariables } = useDashboardVariables();
const { notifications } = useNotifications();
const variables = useMemo(() => selectedDashboard?.data?.variables || {}, [
selectedDashboard?.data?.variables,
]);
const [variablesTableData, setVariablesTableData] = useState<any>([]);
const [variblesOrderArr, setVariablesOrderArr] = useState<number[]>([]);
const [existingVariableNamesMap, setExistingVariableNamesMap] = useState<
@@ -147,13 +146,13 @@ function VariablesSettings({
const variableNamesMap = {};
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(variables)) {
for (const [key, value] of Object.entries(dashboardVariables)) {
const { order, id, name } = value;
tableRowData.push({
key,
name: key,
...variables[key],
...dashboardVariables[key],
id,
});
@@ -174,10 +173,10 @@ function VariablesSettings({
setVariablesTableData(tableRowData);
setVariablesOrderArr(variableOrderArr);
setExistingVariableNamesMap(variableNamesMap);
}, [variables]);
}, [dashboardVariables]);
const updateVariables = (
updatedVariablesData: Dashboard['data']['variables'],
updatedVariablesData: IDashboardVariables,
currentRequestedId?: string,
widgetIds?: string[],
applyToAll?: boolean,
@@ -312,7 +311,7 @@ function VariablesSettings({
currentVariableId?: string,
): boolean => {
// Check if any other dynamic variable already uses this attribute key
const isDuplicateAttributeKey = Object.values(variables).some(
const isDuplicateAttributeKey = Object.values(dashboardVariables).some(
(variable: IDashboardVariable) =>
variable.type === 'DYNAMIC' &&
variable.dynamicVariablesAttribute === attributeKey &&
@@ -422,7 +421,7 @@ function VariablesSettings({
{variableViewMode ? (
<VariableItem
variableData={{ ...variableEditData } as IDashboardVariable}
existingVariables={variables}
existingVariables={dashboardVariables}
onSave={onVariableSaveHandler}
onCancel={onDoneVariableViewMode}
validateName={validateVariableName}

View File

@@ -6,6 +6,7 @@ import useVariablesFromUrl from 'hooks/dashboard/useVariablesFromUrl';
import { isEmpty } from 'lodash-es';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { initializeDefaultVariables } from 'providers/Dashboard/initializeDefaultVariables';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import { AppState } from 'store/reducers';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -33,9 +34,7 @@ function DashboardVariableSelection(): JSX.Element | null {
const { updateUrlVariable, getUrlVariables } = useVariablesFromUrl();
const { data } = selectedDashboard || {};
const { variables } = data || {};
const { dashboardVariables } = useDashboardVariables();
const [variablesTableData, setVariablesTableData] = useState<any>([]);
@@ -48,29 +47,31 @@ function DashboardVariableSelection(): JSX.Element | null {
);
useEffect(() => {
if (variables) {
const tableRowData = [];
const tableRowData = [];
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(variables)) {
const { id } = value;
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(dashboardVariables)) {
const { id } = value;
tableRowData.push({
key,
name: key,
...variables[key],
id,
});
}
tableRowData.sort((a, b) => a.order - b.order);
setVariablesTableData(tableRowData);
// Initialize variables with default values if not in URL
initializeDefaultVariables(variables, getUrlVariables, updateUrlVariable);
tableRowData.push({
key,
name: key,
...dashboardVariables[key],
id,
});
}
}, [getUrlVariables, updateUrlVariable, variables]);
tableRowData.sort((a, b) => a.order - b.order);
setVariablesTableData(tableRowData);
// Initialize variables with default values if not in URL
initializeDefaultVariables(
dashboardVariables,
getUrlVariables,
updateUrlVariable,
);
}, [getUrlVariables, updateUrlVariable, dashboardVariables]);
useEffect(() => {
if (variablesTableData.length > 0) {
@@ -94,7 +95,7 @@ function DashboardVariableSelection(): JSX.Element | null {
cycleNodes,
});
}
}, [variables, variablesTableData]);
}, [dashboardVariables, variablesTableData]);
// this handles the case where the dependency order changes i.e. variable list updated via creation or deletion etc. and we need to refetch the variables
// also trigger when the global time changes
@@ -122,7 +123,7 @@ function DashboardVariableSelection(): JSX.Element | null {
if (id) {
// For dynamic variables, only store in localStorage when NOT allSelected
// This makes localStorage much lighter by avoiding storing all individual values
const variable = variables?.[id] || variables?.[name];
const variable = dashboardVariables?.[id] || dashboardVariables?.[name];
const isDynamic = variable?.type === 'DYNAMIC';
updateLocalStorageDashboardVariables(name, value, allSelected, isDynamic);
@@ -185,7 +186,7 @@ function DashboardVariableSelection(): JSX.Element | null {
}
};
if (!variables) {
if (!dashboardVariables) {
return null;
}
@@ -202,7 +203,7 @@ function DashboardVariableSelection(): JSX.Element | null {
variable.type === 'DYNAMIC' ? (
<DynamicVariableSelection
key={`${variable.name}${variable.id}${variable.order}`}
existingVariables={variables}
existingVariables={dashboardVariables}
variableData={{
name: variable.name,
...variable,
@@ -212,7 +213,7 @@ function DashboardVariableSelection(): JSX.Element | null {
) : (
<VariableItem
key={`${variable.name}${variable.id}}${variable.order}`}
existingVariables={variables}
existingVariables={dashboardVariables}
variableData={{
name: variable.name,
...variable,

View File

@@ -3,7 +3,8 @@ import { useCallback } from 'react';
import { useAddDynamicVariableToPanels } from 'hooks/dashboard/useAddDynamicVariableToPanels';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { v4 as uuidv4 } from 'uuid';
import { convertVariablesToDbFormat } from './util';
@@ -27,7 +28,7 @@ interface UseDashboardVariableUpdateReturn {
widgetId?: string,
) => void;
updateVariables: (
updatedVariablesData: Dashboard['data']['variables'],
updatedVariablesData: IDashboardVariables,
currentRequestedId?: string,
widgetIds?: string[],
applyToAll?: boolean,
@@ -106,7 +107,7 @@ export const useDashboardVariableUpdate = (): UseDashboardVariableUpdateReturn =
const updateVariables = useCallback(
(
updatedVariablesData: Dashboard['data']['variables'],
updatedVariablesData: IDashboardVariables,
currentRequestedId?: string,
widgetIds?: string[],
applyToAll?: boolean,

View File

@@ -1,6 +1,7 @@
import { OptionData } from 'components/NewSelect/types';
import { isEmpty, isNull } from 'lodash-es';
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
export function areArraysEqual(
a: (string | number | boolean)[],
@@ -21,7 +22,7 @@ export function areArraysEqual(
export const convertVariablesToDbFormat = (
variblesArr: IDashboardVariable[],
): Dashboard['data']['variables'] =>
): IDashboardVariables =>
variblesArr.reduce((result, obj: IDashboardVariable) => {
const { id } = obj;

View File

@@ -37,6 +37,7 @@ import GetMinMax from 'lib/getMinMax';
import { isEmpty } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import { AppState } from 'store/reducers';
import { Warning } from 'types/api';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -79,6 +80,7 @@ function FullView({
}, [setCurrentGraphRef]);
const { selectedDashboard, isDashboardLocked } = useDashboard();
const { dashboardVariables } = useDashboardVariables();
const { user } = useAppContext();
const [editWidget] = useComponentPermission(['edit_widget'], user.role);
@@ -114,7 +116,7 @@ function FullView({
graphType: getGraphType(selectedPanelType),
query: updatedQuery,
globalSelectedInterval: globalSelectedTime,
variables: getDashboardVariables(selectedDashboard?.data.variables),
variables: getDashboardVariables(dashboardVariables),
fillGaps: widget.fillSpans,
formatForWeb: selectedPanelType === PANEL_TYPES.TABLE,
originalGraphType: selectedPanelType,
@@ -125,7 +127,7 @@ function FullView({
graphType: PANEL_TYPES.LIST,
selectedTime: widget?.timePreferance || 'GLOBAL_TIME',
globalSelectedInterval: globalSelectedTime,
variables: getDashboardVariables(selectedDashboard?.data.variables),
variables: getDashboardVariables(dashboardVariables),
tableParams: {
pagination: {
offset: 0,

View File

@@ -53,7 +53,7 @@ function GridCardGraph({
customOnRowClick,
customTimeRangeWindowForCoRelation,
enableDrillDown,
widgetsHavingDynamicVariables,
widgetsByDynamicVariableId,
}: GridCardGraphProps): JSX.Element {
const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>();
@@ -226,8 +226,8 @@ function GridCardGraph({
? Object.entries(variables).reduce((acc, [id, variable]) => {
if (
variable.type !== 'DYNAMIC' ||
(widgetsHavingDynamicVariables?.[variable.id] &&
widgetsHavingDynamicVariables?.[variable.id].includes(widget.id))
(widgetsByDynamicVariableId?.[variable.id] &&
widgetsByDynamicVariableId?.[variable.id].includes(widget.id))
) {
return { ...acc, [id]: variable.selectedValue };
}

View File

@@ -4,8 +4,9 @@ import { ToggleGraphProps } from 'components/Graph/types';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
import { SuccessResponse } from 'types/api';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { QueryData } from 'types/api/widgets/getQuery';
import uPlot from 'uplot';
@@ -50,7 +51,7 @@ export interface GridCardGraphProps {
headerMenuList?: WidgetGraphComponentProps['headerMenuList'];
onClickHandler?: OnClickPluginOpts['onClick'];
isQueryEnabled: boolean;
variables?: Dashboard['data']['variables'];
variables?: IDashboardVariables;
version?: string;
onDragSelect: (start: number, end: number) => void;
customOnDragSelect?: (start: number, end: number) => void;
@@ -71,7 +72,7 @@ export interface GridCardGraphProps {
customOnRowClick?: (record: RowData) => void;
customTimeRangeWindowForCoRelation?: string | undefined;
enableDrillDown?: boolean;
widgetsHavingDynamicVariables?: Record<string, string[]>;
widgetsByDynamicVariableId?: Record<string, string[]>;
}
export interface GetGraphVisibilityStateOnLegendClickProps {

View File

@@ -15,7 +15,6 @@ import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import { DEFAULT_ROW_NAME } from 'container/DashboardContainer/DashboardDescription/utils';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { createDynamicVariableToWidgetsMap } from 'hooks/dashboard/utils';
import useComponentPermission from 'hooks/useComponentPermission';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
@@ -32,9 +31,11 @@ import {
} from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import { useWidgetsByDynamicVariableId } from 'providers/Dashboard/store/useWidgetsByDynamicVariableId';
import { sortLayout } from 'providers/Dashboard/util';
import { UpdateTimeInterval } from 'store/actions';
import { IDashboardVariable, Widgets } from 'types/api/dashboard/getAll';
import { Widgets } from 'types/api/dashboard/getAll';
import { Props } from 'types/api/dashboard/update';
import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission';
@@ -79,7 +80,9 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
const { pathname } = useLocation();
const dispatch = useDispatch();
const { widgets, variables } = data || {};
const { widgets } = data || {};
const { dashboardVariables } = useDashboardVariables();
const { user } = useAppContext();
@@ -99,21 +102,7 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
Record<string, { widgets: Layout[]; collapsed: boolean }>
>({});
const widgetsHavingDynamicVariables = useMemo(() => {
const dynamicVariables = Object.values(
selectedDashboard?.data?.variables || {},
)?.filter((variable: IDashboardVariable) => variable.type === 'DYNAMIC');
const widgets =
selectedDashboard?.data?.widgets?.filter(
(widget) => widget.panelTypes !== PANEL_GROUP_TYPES.ROW,
) || [];
return createDynamicVariableToWidgetsMap(
dynamicVariables,
widgets as Widgets[],
);
}, [selectedDashboard]);
const widgetsByDynamicVariableId = useWidgetsByDynamicVariableId();
useEffect(() => {
setCurrentPanelMap(panelMap);
@@ -178,11 +167,11 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
dashboardId: selectedDashboard?.id,
dashboardName: data.title,
numberOfPanels: data.widgets?.length,
numberOfVariables: Object.keys(data?.variables || {}).length || 0,
numberOfVariables: Object.keys(dashboardVariables).length || 0,
});
logEventCalledRef.current = true;
}
}, [data, selectedDashboard?.id]);
}, [dashboardVariables, data, selectedDashboard?.id]);
const onSaveHandler = (): void => {
if (!selectedDashboard) {
@@ -622,13 +611,13 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
<GridCard
widget={(currentWidget as Widgets) || ({ id, query: {} } as Widgets)}
headerMenuList={widgetActions}
variables={variables}
variables={dashboardVariables}
// version={selectedDashboard?.data?.version}
version={ENTITY_VERSION_V5}
onDragSelect={onDragSelect}
dataAvailable={checkIfDataExists}
enableDrillDown={enableDrillDown}
widgetsHavingDynamicVariables={widgetsHavingDynamicVariables}
widgetsByDynamicVariableId={widgetsByDynamicVariableId}
/>
</Card>
</CardContainer>

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import { getSubstituteVars } from 'api/dashboard/substitute_vars';
@@ -7,9 +7,8 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariablesByType } from 'providers/Dashboard/store/useDashboardVariablesByType';
import { AppState } from 'store/reducers';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getGraphType } from 'utils/getGraphType';
@@ -36,14 +35,9 @@ function useUpdatedQuery(): UseUpdatedQueryResult {
const queryRangeMutation = useMutation(getSubstituteVars);
const { selectedDashboard } = useDashboard();
const dynamicVariables = useMemo(
() =>
Object.values(selectedDashboard?.data?.variables || {})?.filter(
(variable: IDashboardVariable) => variable.type === 'DYNAMIC',
),
[selectedDashboard],
const dashboardDynamicVariables = useDashboardVariablesByType(
'DYNAMIC',
'values',
);
const getUpdatedQuery = useCallback(
@@ -59,7 +53,7 @@ function useUpdatedQuery(): UseUpdatedQueryResult {
globalSelectedInterval,
variables: getDashboardVariables(selectedDashboard?.data?.variables),
originalGraphType: widgetConfig.panelTypes,
dynamicVariables,
dynamicVariables: dashboardDynamicVariables,
});
// Execute query and process results
@@ -68,7 +62,7 @@ function useUpdatedQuery(): UseUpdatedQueryResult {
// Map query data from API response
return mapQueryDataFromApi(queryResult.data.compositeQuery);
},
[dynamicVariables, globalSelectedInterval, queryRangeMutation],
[dashboardDynamicVariables, globalSelectedInterval, queryRangeMutation],
);
return {

View File

@@ -39,8 +39,10 @@ import cx from 'classnames';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import ROUTES from 'constants/routes';
import { sanitizeDashboardData } from 'container/DashboardContainer/DashboardDescription';
import { downloadObjectAsJson } from 'container/DashboardContainer/DashboardDescription/utils';
import {
downloadObjectAsJson,
sanitizeDashboardData,
} from 'container/DashboardContainer/DashboardDescription/utils';
import { Base64Icons } from 'container/DashboardContainer/DashboardSettings/General/utils';
import dayjs from 'dayjs';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';

View File

@@ -35,7 +35,7 @@ import {
Spline,
SquareArrowOutUpRight,
} from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import { SuccessResponse } from 'types/api';
import {
ColumnUnit,
@@ -131,7 +131,7 @@ function RightContainer({
enableDrillDown = false,
isNewDashboard,
}: RightContainerProps): JSX.Element {
const { selectedDashboard } = useDashboard();
const { dashboardVariables } = useDashboardVariables();
const [inputValue, setInputValue] = useState(title);
const [autoCompleteOpen, setAutoCompleteOpen] = useState(false);
const [cursorPos, setCursorPos] = useState(0);
@@ -173,16 +173,12 @@ function RightContainer({
const [graphTypes, setGraphTypes] = useState<ItemsProps[]>(GraphTypes);
// Get dashboard variables
const dashboardVariables = useMemo<VariableOption[]>(() => {
if (!selectedDashboard?.data?.variables) {
return [];
}
return Object.entries(selectedDashboard.data.variables).map(([, value]) => ({
const dashboardVariableOptions = useMemo<VariableOption[]>(() => {
return Object.entries(dashboardVariables).map(([, value]) => ({
value: value.name || '',
label: value.name || '',
}));
}, [selectedDashboard?.data?.variables]);
}, [dashboardVariables]);
const updateCursorAndDropdown = (value: string, pos: number): void => {
setCursorPos(pos);
@@ -274,7 +270,7 @@ function RightContainer({
<section className="name-description">
<Typography.Text className="typography">Name</Typography.Text>
<AutoComplete
options={dashboardVariables}
options={dashboardVariableOptions}
value={inputValue}
onChange={onInputChange}
onSelect={onSelect}

View File

@@ -72,6 +72,7 @@ import {
placeWidgetAtBottom,
placeWidgetBetweenRows,
} from './utils';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import './NewWidget.styles.scss';
@@ -89,6 +90,8 @@ function NewWidget({
columnWidths,
} = useDashboard();
const { dashboardVariables } = useDashboardVariables();
const { t } = useTranslation(['dashboard']);
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
@@ -377,7 +380,7 @@ function NewWidget({
graphType: PANEL_TYPES.LIST,
selectedTime: selectedTime.enum || 'GLOBAL_TIME',
globalSelectedInterval: customGlobalSelectedInterval,
variables: getDashboardVariables(selectedDashboard?.data.variables),
variables: getDashboardVariables(dashboardVariables),
tableParams: {
pagination: {
offset: 0,
@@ -394,7 +397,7 @@ function NewWidget({
formatForWeb:
getGraphTypeForFormat(selectedGraph || selectedWidget.panelTypes) ===
PANEL_TYPES.TABLE,
variables: getDashboardVariables(selectedDashboard?.data.variables),
variables: getDashboardVariables(dashboardVariables),
originalGraphType: selectedGraph || selectedWidget?.panelTypes,
};
}
@@ -408,7 +411,7 @@ function NewWidget({
graphType: selectedGraph,
selectedTime: selectedTime.enum || 'GLOBAL_TIME',
globalSelectedInterval: customGlobalSelectedInterval,
variables: getDashboardVariables(selectedDashboard?.data.variables),
variables: getDashboardVariables(dashboardVariables),
};
});

View File

@@ -38,9 +38,8 @@ import {
unset,
} from 'lodash-es';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariablesByType } from 'providers/Dashboard/store/useDashboardVariablesByType';
import type { BaseSelectRef } from 'rc-select';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import {
BaseAutocompleteData,
DataTypes,
@@ -248,14 +247,9 @@ function QueryBuilderSearchV2(
return false;
}, [currentState, query.aggregateAttribute?.dataType, query.dataSource]);
const { selectedDashboard } = useDashboard();
const dynamicVariables = useMemo(
() =>
Object.values(selectedDashboard?.data?.variables || {})?.filter(
(variable: IDashboardVariable) => variable.type === 'DYNAMIC',
),
[selectedDashboard],
const dashboardDynamicVariables = useDashboardVariablesByType(
'DYNAMIC',
'values',
);
const { data, isFetching } = useGetAggregateKeys(
@@ -806,7 +800,7 @@ function QueryBuilderSearchV2(
values.push(...(attributeValues?.payload?.[key] || []));
// here we want to suggest the variable name matching with the key here, we will go over the dynamic variables for the keys
const variableName = dynamicVariables?.find(
const variableName = dashboardDynamicVariables?.find(
(variable) =>
variable?.dynamicVariablesAttribute === currentFilterItem?.key?.key,
)?.name;
@@ -837,7 +831,7 @@ function QueryBuilderSearchV2(
suggestionsData?.payload?.attributes,
operatorConfigKey,
currentFilterItem?.key?.key,
dynamicVariables,
dashboardDynamicVariables,
]);
// keep the query in sync with the selected tags in logs explorer page

View File

@@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { ArrowLeft, Plus, Settings, X } from 'lucide-react';
import ContextMenu from 'periscope/components/ContextMenu';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariablesByType } from 'providers/Dashboard/store/useDashboardVariablesByType';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
// import { PANEL_TYPES } from 'constants/queryBuilder';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
@@ -33,17 +33,9 @@ const useDashboardVarConfig = ({
};
// contextItems: React.ReactNode;
} => {
const { selectedDashboard } = useDashboard();
const dashboardDynamicVariables = useDashboardVariablesByType('DYNAMIC');
const { onValueUpdate, createVariable } = useDashboardVariableUpdate();
const dynamicDashboardVariables = useMemo(
(): [string, IDashboardVariable][] =>
Object.entries(selectedDashboard?.data?.variables || {}).filter(
([, value]) => value.name && value.type === 'DYNAMIC',
),
[selectedDashboard],
);
// Function to determine the source from query data
const getSourceFromQuery = useCallback(():
| 'logs'
@@ -116,7 +108,7 @@ const useDashboardVarConfig = ({
<>
{' '}
{Object.entries(fieldVariables).map(([fieldName, value]) => {
const dashboardVar = dynamicDashboardVariables.find(
const dashboardVar = dashboardDynamicVariables.find(
([, dynamicValue]) =>
dynamicValue.dynamicVariablesAttribute === fieldName,
);
@@ -178,7 +170,7 @@ const useDashboardVarConfig = ({
),
[
fieldVariables,
dynamicDashboardVariables,
dashboardDynamicVariables,
handleSetVariable,
handleUnsetVariable,
handleCreateVariable,

View File

@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -38,20 +38,17 @@ interface ResolvedTextUtilsResult {
function useContextVariables({
maxValues = 2,
// ! To be noted: This customVariables is not Dashboard Custom Variables
customVariables,
}: UseContextVariablesProps): UseContextVariablesResult {
const { selectedDashboard } = useDashboard();
const { dashboardVariables } = useDashboardVariables();
const globalTime = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
// Extract dashboard variables
const dashboardVariables = useMemo(() => {
if (!selectedDashboard?.data?.variables) {
return [];
}
return Object.entries(selectedDashboard.data.variables)
const processedDashboardVariables = useMemo(() => {
return Object.entries(dashboardVariables)
.filter(([, value]) => value.name)
.map(([, value]) => {
let processedValue: string | number | boolean;
@@ -74,7 +71,7 @@ function useContextVariables({
originalValue: value.selectedValue,
};
});
}, [selectedDashboard]);
}, [dashboardVariables]);
// Extract global variables
const globalVariables = useMemo(
@@ -111,8 +108,12 @@ function useContextVariables({
// Combine all variables
const allVariables = useMemo(
() => [...dashboardVariables, ...globalVariables, ...customVariablesList],
[dashboardVariables, globalVariables, customVariablesList],
() => [
...processedDashboardVariables,
...globalVariables,
...customVariablesList,
],
[processedDashboardVariables, globalVariables, customVariablesList],
);
// Create processed variables with truncation logic

View File

@@ -5,7 +5,7 @@
// return value should be a full text string, and a truncated text string (if max length is provided)
import { ReactNode, useCallback, useMemo } from 'react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
interface UseGetResolvedTextProps {
text: string | ReactNode;
@@ -23,23 +23,15 @@ interface ResolvedTextResult {
// eslint-disable-next-line sonarjs/cognitive-complexity
function useGetResolvedText({
text,
variables,
maxLength,
matcher = '$',
maxValues = 2, // Default to showing 2 values before +n more
}: UseGetResolvedTextProps): ResolvedTextResult {
const { selectedDashboard } = useDashboard();
const { dashboardVariables } = useDashboardVariables();
const isString = typeof text === 'string';
const processedDashboardVariables = useMemo(() => {
if (variables) {
return variables;
}
if (!selectedDashboard?.data.variables) {
return {};
}
return Object.entries(selectedDashboard.data.variables).reduce<
return Object.entries(dashboardVariables).reduce<
Record<string, string | number | boolean>
>((acc, [, value]) => {
if (!value.name) {
@@ -54,7 +46,7 @@ function useGetResolvedText({
}
return acc;
}, {});
}, [variables, selectedDashboard?.data.variables]);
}, [dashboardVariables]);
// Process array values to add +n more notation for truncated text
const processedVariables = useMemo(() => {

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import logEvent from 'api/common/logEvent';
@@ -15,8 +15,10 @@ import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariab
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { isEmpty } from 'lodash-es';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import { useDashboardVariablesByType } from 'providers/Dashboard/store/useDashboardVariablesByType';
import { AppState } from 'store/reducers';
import { IDashboardVariable, Widgets } from 'types/api/dashboard/getAll';
import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getGraphType } from 'utils/getGraphType';
@@ -32,12 +34,10 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
const { selectedDashboard } = useDashboard();
const dynamicVariables = useMemo(
() =>
Object.values(selectedDashboard?.data?.variables || {})?.filter(
(variable: IDashboardVariable) => variable.type === 'DYNAMIC',
),
[selectedDashboard],
const { dashboardVariables } = useDashboardVariables();
const dashboardDynamicVariables = useDashboardVariablesByType(
'DYNAMIC',
'values',
);
return useCallback(() => {
@@ -68,9 +68,9 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
globalSelectedInterval,
graphType: getGraphType(widget.panelTypes),
selectedTime: widget.timePreferance,
variables: getDashboardVariables(selectedDashboard?.data.variables),
variables: getDashboardVariables(dashboardVariables),
originalGraphType: widget.panelTypes,
dynamicVariables,
dynamicVariables: dashboardDynamicVariables,
});
queryRangeMutation.mutate(queryPayload, {
onSuccess: (data) => {
@@ -104,10 +104,10 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
globalSelectedInterval,
notifications,
queryRangeMutation,
selectedDashboard?.data.variables,
dashboardVariables,
dashboardDynamicVariables,
selectedDashboard?.data.version,
widget,
dynamicVariables,
]);
};

View File

@@ -9,9 +9,8 @@ import {
GetQueryResultsProps,
} from 'lib/dashboard/getQueryResults';
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariablesByType } from 'providers/Dashboard/store/useDashboardVariablesByType';
import { SuccessResponse, Warning } from 'types/api';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import APIError from 'types/api/error';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { DataSource } from 'types/common/queryBuilder';
@@ -43,14 +42,9 @@ export const useGetQueryRange: UseGetQueryRange = (
headers,
publicQueryMeta,
) => {
const { selectedDashboard } = useDashboard();
const dynamicVariables = useMemo(
() =>
Object.values(selectedDashboard?.data?.variables || {})?.filter(
(variable: IDashboardVariable) => variable.type === 'DYNAMIC',
),
[selectedDashboard],
const dashboardDynamicVariables = useDashboardVariablesByType(
'DYNAMIC',
'values',
);
const newRequestData: GetQueryResultsProps = useMemo(() => {
@@ -159,7 +153,7 @@ export const useGetQueryRange: UseGetQueryRange = (
GetMetricQueryRange(
modifiedRequestData,
version,
dynamicVariables,
dashboardDynamicVariables,
signal,
headers,
undefined,

View File

@@ -4,7 +4,7 @@ import { initialQueriesMap } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useDashboardVariables } from 'providers/Dashboard/store/useDashboardVariables';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
@@ -28,7 +28,7 @@ export const useGetWidgetQueryRange = (
const { stagedQuery } = useQueryBuilder();
const { selectedDashboard } = useDashboard();
const { dashboardVariables } = useDashboardVariables();
return useGetQueryRange(
{
@@ -36,7 +36,7 @@ export const useGetWidgetQueryRange = (
selectedTime,
globalSelectedInterval,
query: stagedQuery || initialQueriesMap.metrics,
variables: getDashboardVariables(selectedDashboard?.data.variables),
variables: getDashboardVariables(dashboardVariables),
},
version,
{

View File

@@ -5,8 +5,7 @@ import {
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
import { Option } from 'container/QueryBuilder/type';
import { isEmpty } from 'lodash-es';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { useDashboardVariablesByType } from 'providers/Dashboard/store/useDashboardVariablesByType';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { WhereClauseConfig } from './useAutoComplete';
@@ -32,16 +31,12 @@ export const useOptions = (
const operators = useOperators(key, keys);
// get matching dynamic variables to suggest
const { selectedDashboard } = useDashboard();
const dynamicVariables = useMemo(
() =>
Object.values(selectedDashboard?.data?.variables || {})?.filter(
(variable: IDashboardVariable) => variable.type === 'DYNAMIC',
),
[selectedDashboard],
const dashboardDynamicVariables = useDashboardVariablesByType(
'DYNAMIC',
'values',
);
const variableName = dynamicVariables?.find(
const variableName = dashboardDynamicVariables?.find(
(variable) => variable?.dynamicVariablesAttribute === key,
)?.name;

View File

@@ -1,9 +1,9 @@
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariablesStore';
import store from 'store';
import { Dashboard } from 'types/api/dashboard/getAll';
export const getDashboardVariables = (
variables?: Dashboard['data']['variables'],
variables?: IDashboardVariables,
): Record<string, unknown> => {
if (!variables) {
return {};

View File

@@ -1,7 +1,7 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { MemoryRouter, useLocation } from 'react-router-dom';
import ROUTES from 'constants/routes';
import * as dashboardUtils from 'container/DashboardContainer/DashboardDescription';
import { sanitizeDashboardData } from 'container/DashboardContainer/DashboardDescription/utils';
import DashboardsList from 'container/ListOfDashboard';
import {
dashboardEmptyState,
@@ -232,7 +232,7 @@ describe('dashboard list page', () => {
expect(exportJsonBtn).toBeInTheDocument();
fireEvent.click(exportJsonBtn);
const firstDashboardData = dashboardSuccessResponse.data[0];
expect(dashboardUtils.sanitizeDashboardData).toHaveBeenCalledWith(
expect(sanitizeDashboardData).toHaveBeenCalledWith(
expect.objectContaining({
title: firstDashboardData.data.title,
createdAt: firstDashboardData.createdAt,

View File

@@ -45,6 +45,8 @@ import APIError from 'types/api/error';
import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 as generateUUID } from 'uuid';
import { updateDashboardVariablesStore } from './store/dashboardVariablesStore';
import { useDashboardVariables } from './store/useDashboardVariables';
import {
DashboardSortOrder,
IDashboardContext,
@@ -196,6 +198,16 @@ export function DashboardProvider({
: isDashboardWidgetPage?.params.dashboardId) || '';
const [selectedDashboard, setSelectedDashboard] = useState<Dashboard>();
const dashboardVariables = useDashboardVariables();
useEffect(() => {
const existingVariables = dashboardVariables;
const updatedVariables = selectedDashboard?.data.variables || {};
if (!isEqual(existingVariables, updatedVariables)) {
updateDashboardVariablesStore(updatedVariables);
}
}, [selectedDashboard]);
const {
currentDashboard,

View File

@@ -10,6 +10,7 @@ import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { initializeDefaultVariables } from '../initializeDefaultVariables';
import { normalizeUrlValueForVariable } from '../normalizeUrlValue';
import { useDashboardVariables } from '../store/useDashboardVariables';
// Mock the dashboard API
jest.mock('api/v1/dashboards/id/get');
@@ -55,6 +56,7 @@ jest.mock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') }));
function TestComponent(): JSX.Element {
const { dashboardResponse, dashboardId, selectedDashboard } = useDashboard();
const { dashboardVariables } = useDashboardVariables();
return (
<div>
@@ -65,9 +67,7 @@ function TestComponent(): JSX.Element {
{dashboardResponse.isFetching.toString()}
</div>
<div data-testid="dashboard-variables">
{selectedDashboard?.data?.variables
? JSON.stringify(selectedDashboard.data.variables)
: 'null'}
{dashboardVariables ? JSON.stringify(dashboardVariables) : 'null'}
</div>
<div data-testid="dashboard-data">
{selectedDashboard?.data?.title || 'No Title'}

View File

@@ -0,0 +1,17 @@
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import createStore from './store';
// export type IDashboardVariables = DashboardData['variables'];
export type IDashboardVariables = Record<string, IDashboardVariable>;
export const dashboardVariablesStore = createStore<IDashboardVariables>({});
export function updateDashboardVariablesStore(
variables: Partial<IDashboardVariables>,
): void {
dashboardVariablesStore.update((currentVariables) => ({
...currentVariables,
...variables,
}));
}

View File

@@ -0,0 +1,44 @@
import { produce } from 'immer';
type ListenerFn = () => void;
export default function createStore<T>(
init: T,
): {
set: (setter: any) => void;
update: (updater: (draft: T) => void) => void;
subscribe: (listener: ListenerFn) => () => void;
getSnapshot: () => T;
} {
let listeners: ListenerFn[] = [];
let state = init;
function emitChange(): void {
for (const listener of listeners) {
listener();
}
}
function set(setter: any): void {
state = produce(state, setter);
emitChange();
}
function update(updater: (draft: T) => void): void {
state = produce(state, updater);
emitChange();
}
return {
set,
update,
subscribe(listener: ListenerFn): () => void {
listeners = [...listeners, listener];
return (): void => {
listeners = listeners.filter((l) => l !== listener);
};
},
getSnapshot(): T {
return state;
},
};
}

View File

@@ -0,0 +1,19 @@
import { useSyncExternalStore } from 'react';
import {
dashboardVariablesStore,
IDashboardVariables,
} from './dashboardVariablesStore';
export const useDashboardVariables = (): {
dashboardVariables: IDashboardVariables;
} => {
const dashboardVariables = useSyncExternalStore(
dashboardVariablesStore.subscribe,
dashboardVariablesStore.getSnapshot,
);
return {
dashboardVariables,
};
};

View File

@@ -0,0 +1,29 @@
import {
IDashboardVariable,
TVariableQueryType,
} from 'types/api/dashboard/getAll';
import { useDashboardVariables } from './useDashboardVariables';
import { useMemo } from 'react';
export function useDashboardVariablesByType(
variableType: TVariableQueryType,
returnType: 'values',
): IDashboardVariable[];
export function useDashboardVariablesByType(
variableType: TVariableQueryType,
returnType?: 'entries',
): [string, IDashboardVariable][];
export function useDashboardVariablesByType(
variableType: TVariableQueryType,
returnType?: 'values' | 'entries',
): IDashboardVariable[] | [string, IDashboardVariable][] {
const { dashboardVariables } = useDashboardVariables();
return useMemo(() => {
const entries = Object.entries(dashboardVariables || {}).filter(
(entry): entry is [string, IDashboardVariable] =>
Boolean(entry[1].name) && entry[1].type === variableType,
);
return returnType === 'values' ? entries.map(([, value]) => value) : entries;
}, [dashboardVariables, variableType, returnType]);
}

View File

@@ -0,0 +1,28 @@
import { useMemo } from 'react';
import { PANEL_GROUP_TYPES } from 'constants/queryBuilder';
import { createDynamicVariableToWidgetsMap } from 'hooks/dashboard/utils';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { Widgets } from 'types/api/dashboard/getAll';
import { useDashboardVariablesByType } from './useDashboardVariablesByType';
/**
* Hook to get a map of dynamic variable IDs to widget IDs that use them.
* This is useful for determining which widgets need to be refreshed when a dynamic variable changes.
*/
export function useWidgetsByDynamicVariableId(): Record<string, string[]> {
const dynamicVariables = useDashboardVariablesByType('DYNAMIC', 'values');
const { selectedDashboard } = useDashboard();
return useMemo(() => {
const widgets =
selectedDashboard?.data?.widgets?.filter(
(widget) => widget.panelTypes !== PANEL_GROUP_TYPES.ROW,
) || [];
return createDynamicVariableToWidgetsMap(
dynamicVariables,
widgets as Widgets[],
);
}, [selectedDashboard, dynamicVariables]);
}

View File

@@ -12030,6 +12030,11 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
immer@11.1.3:
version "11.1.3"
resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.3.tgz#78681e1deb6cec39753acf04eb16d7576c04f4d6"
integrity sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==
immer@^9.0.6:
version "9.0.21"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"