diff --git a/frontend/src/container/DashboardContainer/DashboardDescription/index.tsx b/frontend/src/container/DashboardContainer/DashboardDescription/index.tsx index 44f17c0c7a..cd7e125f5f 100644 --- a/frontend/src/container/DashboardContainer/DashboardDescription/index.tsx +++ b/frontend/src/container/DashboardContainer/DashboardDescription/index.tsx @@ -21,6 +21,7 @@ import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; import { DeleteButton } from 'container/ListOfDashboard/TableComponents/DeleteButton'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables'; import { useGetPublicDashboardMeta } from 'hooks/dashboard/useGetPublicDashboardMeta'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import useComponentPermission from 'hooks/useComponentPermission'; @@ -44,7 +45,7 @@ import { import { useAppContext } from 'providers/App/App'; import { useDashboard } from 'providers/Dashboard/Dashboard'; 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, - ); - - 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 {
{description}
)} - {!isEmpty(selectedData.variables) && ( + {!isEmpty(dashboardVariables) && (
diff --git a/frontend/src/container/DashboardContainer/DashboardDescription/utils.ts b/frontend/src/container/DashboardContainer/DashboardDescription/utils.ts index 80a2514632..54eee098fa 100644 --- a/frontend/src/container/DashboardContainer/DashboardDescription/utils.ts +++ b/frontend/src/container/DashboardContainer/DashboardDescription/utils.ts @@ -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, + ); + + return { + ...selectedData, + variables: updatedVariables, + }; +} + export function downloadObjectAsJson( exportObj: unknown, exportName: string, diff --git a/frontend/src/container/DashboardContainer/DashboardSettings/DashboardVariableSettings/VariableItem/VariableItem.tsx b/frontend/src/container/DashboardContainer/DashboardSettings/DashboardVariableSettings/VariableItem/VariableItem.tsx index 636e968223..496cff762f 100644 --- a/frontend/src/container/DashboardContainer/DashboardSettings/DashboardVariableSettings/VariableItem/VariableItem.tsx +++ b/frontend/src/container/DashboardContainer/DashboardSettings/DashboardVariableSettings/VariableItem/VariableItem.tsx @@ -14,10 +14,8 @@ 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 { useWidgetsByDynamicVariableId } from 'hooks/dashboard/useWidgetsByDynamicVariableId'; +import { getWidgetsHavingDynamicVariableAttribute } from 'hooks/dashboard/utils'; import { useGetFieldValues } from 'hooks/dynamicVariables/useGetFieldValues'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser'; @@ -243,23 +241,11 @@ function VariableItem({ const [selectedWidgets, setSelectedWidgets] = useState([]); 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(() => { diff --git a/frontend/src/container/DashboardContainer/DashboardSettings/DashboardVariableSettings/index.tsx b/frontend/src/container/DashboardContainer/DashboardSettings/DashboardVariableSettings/index.tsx index 74aebd7736..a8cb4e661c 100644 --- a/frontend/src/container/DashboardContainer/DashboardSettings/DashboardVariableSettings/index.tsx +++ b/frontend/src/container/DashboardContainer/DashboardSettings/DashboardVariableSettings/index.tsx @@ -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'; @@ -17,11 +17,13 @@ import { RowProps } from 'antd/lib'; import { VariablesSettingsTabHandle } from 'container/DashboardContainer/DashboardDescription/types'; import { convertVariablesToDbFormat } from 'container/DashboardContainer/DashboardVariablesSelection/util'; import { useAddDynamicVariableToPanels } from 'hooks/dashboard/useAddDynamicVariableToPanels'; +import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables'; 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 { 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([]); const [variblesOrderArr, setVariablesOrderArr] = useState([]); 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 ? ( ([]); @@ -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' ? ( 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, diff --git a/frontend/src/container/DashboardContainer/DashboardVariablesSelection/util.ts b/frontend/src/container/DashboardContainer/DashboardVariablesSelection/util.ts index 9a44f7ad44..8e6767d6ee 100644 --- a/frontend/src/container/DashboardContainer/DashboardVariablesSelection/util.ts +++ b/frontend/src/container/DashboardContainer/DashboardVariablesSelection/util.ts @@ -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; diff --git a/frontend/src/container/ListOfDashboard/DashboardsList.tsx b/frontend/src/container/ListOfDashboard/DashboardsList.tsx index d180c3cc43..9847e3e64e 100644 --- a/frontend/src/container/ListOfDashboard/DashboardsList.tsx +++ b/frontend/src/container/ListOfDashboard/DashboardsList.tsx @@ -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'; diff --git a/frontend/src/pages/DashboardsListPage/__tests__/DashboardListPage.test.tsx b/frontend/src/pages/DashboardsListPage/__tests__/DashboardListPage.test.tsx index 1d3dc1e7a2..01e171e0d6 100644 --- a/frontend/src/pages/DashboardsListPage/__tests__/DashboardListPage.test.tsx +++ b/frontend/src/pages/DashboardsListPage/__tests__/DashboardListPage.test.tsx @@ -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, @@ -12,8 +12,9 @@ import { rest } from 'msw'; import { DashboardProvider } from 'providers/Dashboard/Dashboard'; import { fireEvent, render, waitFor } from 'tests/test-utils'; -jest.mock('container/DashboardContainer/DashboardDescription', () => ({ - sanitizeDashboardData: jest.fn(), +jest.mock('container/DashboardContainer/DashboardDescription/utils', () => ({ + sanitizeDashboardData: jest.fn((data) => data), + downloadObjectAsJson: jest.fn(), })); jest.mock('react-router-dom', () => ({ @@ -232,7 +233,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,