From fcac65a22efd4033a00f6a81b16d18570ee42ec1 Mon Sep 17 00:00:00 2001 From: nikhagra-akamai Date: Tue, 23 Sep 2025 19:13:30 +0530 Subject: [PATCH 01/10] upcoming: [DI-27110] - Added preference support for group by --- packages/api-v4/src/cloudpulse/types.ts | 1 + .../GroupBy/GlobalFilterGroupByRenderer.tsx | 25 +++++++++---- .../GroupBy/WidgetFilterGroupByRenderer.tsx | 35 ++++++++++++++++--- .../src/features/CloudPulse/GroupBy/utils.ts | 15 ++++---- .../CloudPulse/Overview/GlobalFilters.tsx | 16 ++++++++- .../features/CloudPulse/Utils/constants.ts | 2 ++ .../CloudPulse/Widget/CloudPulseWidget.tsx | 30 ++++++++++++---- .../Widget/CloudPulseWidgetRenderer.tsx | 5 ++- 8 files changed, 104 insertions(+), 25 deletions(-) diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index 05634b21d71..20c9f32e8b2 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -111,6 +111,7 @@ export interface AclpConfig { export interface AclpWidget { aggregateFunction: string; + groupBy?: string[]; label: string; size: number; timeGranularity: TimeGranularity; diff --git a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx index 836cb2a0dee..7db5f09c973 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx @@ -9,13 +9,21 @@ import { GLOBAL_GROUP_BY_MESSAGE } from './constants'; import { useGlobalDimensions } from './utils'; import type { GroupByOption } from './CloudPulseGroupByDrawer'; -import type { Dashboard } from '@linode/api-v4'; +import type { Dashboard, FilterValue } from '@linode/api-v4'; interface GlobalFilterGroupByRendererProps { /** * Callback to handle the selected values */ - handleChange: (selectedValue: string[]) => void; + handleChange: (selectedValue: string[], savePref?: boolean) => void; + /** + * User's saved group by preference + */ + preference?: FilterValue; + /** + * Indicates whether to save the selected group by options to user preferences + */ + savePreferences?: boolean; /** * Currently selected dashboard */ @@ -25,12 +33,14 @@ interface GlobalFilterGroupByRendererProps { export const GlobalFilterGroupByRenderer = ( props: GlobalFilterGroupByRendererProps ) => { - const { selectedDashboard, handleChange } = props; + const { selectedDashboard, handleChange, preference, savePreferences } = + props; const [isSelected, setIsSelected] = React.useState(false); const { options, defaultValue, isLoading } = useGlobalDimensions( selectedDashboard?.id, - selectedDashboard?.service_type + selectedDashboard?.service_type, + preference as string[] ); const [open, setOpen] = React.useState(false); @@ -42,10 +52,13 @@ export const GlobalFilterGroupByRenderer = ( } else { setIsSelected(true); } - handleChange(selectedValue.map(({ value }) => value)); + handleChange( + selectedValue.map(({ value }) => value), + savePreferences + ); setOpen(false); }, - [handleChange] + [handleChange, savePreferences] ); const onCancel = React.useCallback(() => { diff --git a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx index 8d892d5bec8..5d050cedff2 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx @@ -19,7 +19,7 @@ interface WidgetFilterGroupByRendererProps { /** * Callback function to handle the selected values */ - handleChange: (selectedValue: string[]) => void; + handleChange: (selectedValue: string[], savePreferences?: boolean) => void; /** * Label for the widget metric */ @@ -28,6 +28,14 @@ interface WidgetFilterGroupByRendererProps { * Name of the metric */ metric: string; + /** + * User's saved group by preference + */ + preference?: string[]; + /** + * Indicates whether to save the selected group by options to user preferences + */ + savePreferences?: boolean; /** * Service type of the selected dashboard */ @@ -37,7 +45,15 @@ interface WidgetFilterGroupByRendererProps { export const WidgetFilterGroupByRenderer = ( props: WidgetFilterGroupByRendererProps ) => { - const { metric, dashboardId, serviceType, label, handleChange } = props; + const { + metric, + dashboardId, + serviceType, + label, + handleChange, + savePreferences, + preference, + } = props; const [isSelected, setIsSelected] = React.useState(false); const { isLoading: globalDimensionLoading, options: globalDimensions } = @@ -46,7 +62,13 @@ export const WidgetFilterGroupByRenderer = ( isLoading: widgetDimensionLoading, options: widgetDimensions, defaultValue, - } = useWidgetDimension(dashboardId, serviceType, globalDimensions, metric); + } = useWidgetDimension( + dashboardId, + serviceType, + globalDimensions, + metric, + preference + ); const [open, setOpen] = React.useState(false); const onCancel = React.useCallback(() => { setOpen(false); @@ -58,10 +80,13 @@ export const WidgetFilterGroupByRenderer = ( } else { setIsSelected(true); } - handleChange(selectedValue.map(({ value }) => value)); + handleChange( + selectedValue.map(({ value }) => value), + savePreferences + ); setOpen(false); }, - [handleChange] + [handleChange, savePreferences] ); const isDisabled = diff --git a/packages/manager/src/features/CloudPulse/GroupBy/utils.ts b/packages/manager/src/features/CloudPulse/GroupBy/utils.ts index 4b642e1a5ef..dd5d4d7ff89 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/utils.ts +++ b/packages/manager/src/features/CloudPulse/GroupBy/utils.ts @@ -40,7 +40,8 @@ interface MetricDimension { */ export const useGlobalDimensions = ( dashboardId: number | undefined, - serviceType: CloudPulseServiceType | undefined + serviceType: CloudPulseServiceType | undefined, + preference?: string[] ): GroupByDimension => { const { data: dashboard, isLoading: dashboardLoading } = useCloudPulseDashboardByIdQuery(dashboardId); @@ -60,7 +61,7 @@ export const useGlobalDimensions = ( ]; const commonGroups = getCommonGroups( - dashboard?.group_by ?? [], + preference ? preference : (dashboard?.group_by ?? []), commonDimensions ); return { @@ -99,7 +100,8 @@ export const useWidgetDimension = ( dashboardId: number | undefined, serviceType: CloudPulseServiceType | undefined, globalDimensions: GroupByOption[], - metric: string | undefined + metric: string | undefined, + preference?: string[] ): GroupByDimension => { const { data: dashboard, isLoading: dashboardLoading } = useCloudPulseDashboardByIdQuery(dashboardId); @@ -120,9 +122,10 @@ export const useWidgetDimension = ( label, value: dimension_label, })) ?? []; - const defaultGroupBy = - dashboard?.widgets.find((widget) => widget.metric === metric)?.group_by ?? - []; + const defaultGroupBy = preference + ? preference + : (dashboard?.widgets.find((widget) => widget.metric === metric) + ?.group_by ?? []); const options = metricDimensions.filter( (metricDimension) => !globalDimensions.some( diff --git a/packages/manager/src/features/CloudPulse/Overview/GlobalFilters.tsx b/packages/manager/src/features/CloudPulse/Overview/GlobalFilters.tsx index 6d66ec243ec..13a8e778901 100644 --- a/packages/manager/src/features/CloudPulse/Overview/GlobalFilters.tsx +++ b/packages/manager/src/features/CloudPulse/Overview/GlobalFilters.tsx @@ -14,6 +14,7 @@ import { CloudPulseTooltip } from '../shared/CloudPulseTooltip'; import { convertToGmt } from '../Utils/CloudPulseDateTimePickerUtils'; import { DASHBOARD_ID, + GROUP_BY, REFRESH, RESOURCE_FILTER_MAP, TIME_DURATION, @@ -105,6 +106,17 @@ export const GlobalFilters = React.memo((props: GlobalFilterProperties) => { RESOURCE_FILTER_MAP[selectedDashboard?.service_type ?? ''] ?? {} ); + const onGroupByChange = React.useCallback( + (selectedValues: string[], savePref: boolean = false) => { + if (savePref) { + updatePreferences({ [GROUP_BY]: selectedValues }); + } + + handleGroupByChange(selectedValues); + }, + [] + ); + return ( @@ -150,7 +162,9 @@ export const GlobalFilters = React.memo((props: GlobalFilterProperties) => { diff --git a/packages/manager/src/features/CloudPulse/Utils/constants.ts b/packages/manager/src/features/CloudPulse/Utils/constants.ts index a09a3fa4b48..68679030dd0 100644 --- a/packages/manager/src/features/CloudPulse/Utils/constants.ts +++ b/packages/manager/src/features/CloudPulse/Utils/constants.ts @@ -22,6 +22,8 @@ export const TIME_DURATION = 'dateTimeDuration'; export const AGGREGATE_FUNCTION = 'aggregateFunction'; +export const GROUP_BY = 'groupBy'; + export const SIZE = 'size'; export const LABEL = 'label'; diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx index c717a0b44d9..887087f842b 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx @@ -12,7 +12,12 @@ import { generateGraphData, getCloudPulseMetricRequest, } from '../Utils/CloudPulseWidgetUtils'; -import { AGGREGATE_FUNCTION, SIZE, TIME_GRANULARITY } from '../Utils/constants'; +import { + AGGREGATE_FUNCTION, + GROUP_BY, + SIZE, + TIME_GRANULARITY, +} from '../Utils/constants'; import { constructAdditionalRequestFilters } from '../Utils/FilterBuilder'; import { generateCurrentUnit } from '../Utils/unitConversion'; import { useAclpPreference } from '../Utils/UserPreference'; @@ -81,6 +86,11 @@ export interface CloudPulseWidgetProperties { */ errorLabel?: string; + /** + * Group by selected on global filter + */ + globalFilterGroupBy: string[]; + /** * Jwe token fetching status check */ @@ -120,11 +130,11 @@ export interface CloudPulseWidgetProperties { * this should come from dashboard, which maintains map for service types in a separate API call */ unit: string; - /** * color index to be selected from available them if not theme is provided by user */ useColorIndex?: number; + /** * this comes from dashboard, has inbuilt metrics, agg_func,group_by,filters,gridsize etc , also helpful in publishing any changes */ @@ -148,11 +158,13 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { const { data: profile } = useProfile(); const [widget, setWidget] = React.useState({ ...props.widget }); - const [groupBy, setGroupBy] = React.useState([]); - + const [groupBy, setGroupBy] = React.useState( + props.widget.group_by + ); const theme = useTheme(); const { + globalFilterGroupBy, additionalFilters, ariaLabel, authToken, @@ -251,6 +263,11 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { [] ); const handleGroupByChange = React.useCallback((selectedGroupBy: string[]) => { + if (savePref) { + updatePreferences(widget.label, { + [GROUP_BY]: selectedGroupBy, + }); + } setGroupBy(selectedGroupBy); }, []); const { @@ -266,7 +283,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { entityIds, resources, widget, - groupBy: [...(widgetProp.group_by ?? []), ...groupBy], + groupBy: [...globalFilterGroupBy, ...(groupBy ?? [])], linodeRegion, region, serviceType, @@ -295,7 +312,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { status, unit, serviceType, - groupBy: [...(widgetProp.group_by ?? []), ...groupBy], + groupBy: [...globalFilterGroupBy, ...(groupBy ?? [])], metricLabel: availableMetrics?.label, }); @@ -377,6 +394,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { handleChange={handleGroupByChange} label={widget.label} metric={widget.metric} + preference={groupBy} serviceType={serviceType} /> Date: Wed, 24 Sep 2025 12:00:14 +0530 Subject: [PATCH 02/10] upcoming: [DI-27110] - Updated typecheck --- .../src/features/CloudPulse/Widget/CloudPulseWidget.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.test.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.test.tsx index 0a0309b07e0..1f4613c4abe 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.test.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.test.tsx @@ -51,6 +51,7 @@ const props: CloudPulseWidgetProperties = { widget: widgetFactory.build({ label: 'CPU Utilization', }), + globalFilterGroupBy: [], }; const queryMocks = vi.hoisted(() => ({ From 753f61e1f1e9ca83f7f68864a5826818c7aecb26 Mon Sep 17 00:00:00 2001 From: nikhagra-akamai Date: Wed, 24 Sep 2025 12:05:01 +0530 Subject: [PATCH 03/10] upcoming: [DI-27110] - Renamed props --- .../GroupBy/GlobalFilterGroupByRenderer.tsx | 12 ++++++++---- .../GroupBy/WidgetFilterGroupByRenderer.tsx | 6 +++--- .../features/CloudPulse/Overview/GlobalFilters.tsx | 2 +- .../features/CloudPulse/Widget/CloudPulseWidget.tsx | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx index 7db5f09c973..ff908c165cd 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx @@ -19,7 +19,7 @@ interface GlobalFilterGroupByRendererProps { /** * User's saved group by preference */ - preference?: FilterValue; + preferenceGroupBy?: FilterValue; /** * Indicates whether to save the selected group by options to user preferences */ @@ -33,14 +33,18 @@ interface GlobalFilterGroupByRendererProps { export const GlobalFilterGroupByRenderer = ( props: GlobalFilterGroupByRendererProps ) => { - const { selectedDashboard, handleChange, preference, savePreferences } = - props; + const { + selectedDashboard, + handleChange, + preferenceGroupBy, + savePreferences, + } = props; const [isSelected, setIsSelected] = React.useState(false); const { options, defaultValue, isLoading } = useGlobalDimensions( selectedDashboard?.id, selectedDashboard?.service_type, - preference as string[] + preferenceGroupBy as string[] ); const [open, setOpen] = React.useState(false); diff --git a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx index 5d050cedff2..dc70a1c7394 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx @@ -31,7 +31,7 @@ interface WidgetFilterGroupByRendererProps { /** * User's saved group by preference */ - preference?: string[]; + preferenceGroupBy?: string[]; /** * Indicates whether to save the selected group by options to user preferences */ @@ -52,7 +52,7 @@ export const WidgetFilterGroupByRenderer = ( label, handleChange, savePreferences, - preference, + preferenceGroupBy, } = props; const [isSelected, setIsSelected] = React.useState(false); @@ -67,7 +67,7 @@ export const WidgetFilterGroupByRenderer = ( serviceType, globalDimensions, metric, - preference + preferenceGroupBy ); const [open, setOpen] = React.useState(false); const onCancel = React.useCallback(() => { diff --git a/packages/manager/src/features/CloudPulse/Overview/GlobalFilters.tsx b/packages/manager/src/features/CloudPulse/Overview/GlobalFilters.tsx index 13a8e778901..5f7dcbf7488 100644 --- a/packages/manager/src/features/CloudPulse/Overview/GlobalFilters.tsx +++ b/packages/manager/src/features/CloudPulse/Overview/GlobalFilters.tsx @@ -163,7 +163,7 @@ export const GlobalFilters = React.memo((props: GlobalFilterProperties) => { diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx index 887087f842b..c1bcd2e8345 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx @@ -394,7 +394,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { handleChange={handleGroupByChange} label={widget.label} metric={widget.metric} - preference={groupBy} + preferenceGroupBy={groupBy} serviceType={serviceType} /> Date: Wed, 24 Sep 2025 17:21:36 +0530 Subject: [PATCH 04/10] upcoming: [DI-27110] - Updated failing test cases --- .../GroupBy/GlobalFilterGroupByRenderer.test.tsx | 7 +++++-- .../GroupBy/WidgetFilterGroupByRenderer.test.tsx | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.test.tsx b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.test.tsx index cac5537d38a..c3e63a710a8 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.test.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.test.tsx @@ -86,7 +86,7 @@ describe('Global Group By Renderer Component', () => { const drawer = screen.getByTestId('drawer'); expect(drawer).toBeInTheDocument(); - expect(handleChange).toHaveBeenCalledWith([]); + expect(handleChange).toHaveBeenCalledWith([], undefined); }); it('Should not open drawer but group by icon should be enabled', async () => { @@ -131,7 +131,10 @@ describe('Global Group By Renderer Component', () => { const drawer = screen.getByTestId('drawer'); expect(drawer).toBeInTheDocument(); - expect(handleChange).toHaveBeenCalledWith([defaultValue[0].value]); + expect(handleChange).toHaveBeenCalledWith( + [defaultValue[0].value], + undefined + ); defaultValue.forEach((value) => { const option = screen.getByRole('button', { name: value.label }); diff --git a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.test.tsx b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.test.tsx index 1b820c0aa08..a3e335fe012 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.test.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.test.tsx @@ -85,7 +85,7 @@ describe('Widget Group By Renderer', () => { const title = screen.getByText('Group By'); expect(title).toBeInTheDocument(); - expect(handleChange).toHaveBeenCalledWith([]); + expect(handleChange).toHaveBeenCalledWith([], undefined); }); it('Should not open drawer but group by icon should be enabled', async () => { @@ -120,7 +120,10 @@ describe('Widget Group By Renderer', () => { const drawer = screen.getByTestId('drawer'); expect(drawer).toBeInTheDocument(); - expect(handleChange).toHaveBeenCalledWith([defaultValue[0].value]); + expect(handleChange).toHaveBeenCalledWith( + [defaultValue[0].value], + undefined + ); defaultValue.forEach((value) => { const option = screen.getByRole('button', { name: value.label }); From acc3c643736dae43381d64eaaf5add2ce09807be Mon Sep 17 00:00:00 2001 From: nikhagra-akamai Date: Wed, 24 Sep 2025 17:30:02 +0530 Subject: [PATCH 05/10] upcoming: [DI-27110] - Added test cases --- .../features/CloudPulse/GroupBy/utils.test.ts | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/manager/src/features/CloudPulse/GroupBy/utils.test.ts b/packages/manager/src/features/CloudPulse/GroupBy/utils.test.ts index 9d648715557..ca1459276e7 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/utils.test.ts +++ b/packages/manager/src/features/CloudPulse/GroupBy/utils.test.ts @@ -85,7 +85,7 @@ describe('useGlobalDimensions method test', () => { expect(result).toEqual({ options: [], defaultValue: [], isLoading: true }); }); - it('should return empty options and defaultValue if no common dimensions', () => { + it('should return non-empty options and defaultValue if no common dimensions', () => { queryMocks.useCloudPulseDashboardByIdQuery.mockReturnValue({ data: dashboardFactory.build(), isLoading: false, @@ -103,6 +103,26 @@ describe('useGlobalDimensions method test', () => { isLoading: false, }); }); + + it('should return non-empty options and defaultValue from preferences', () => { + queryMocks.useCloudPulseDashboardByIdQuery.mockReturnValue({ + data: dashboardFactory.build(), + isLoading: false, + }); + queryMocks.useGetCloudPulseMetricDefinitionsByServiceType.mockReturnValue({ + data: { + data: metricDefinitions, + }, + isLoading: false, + }); + const preference = ['Dim 2']; + const result = useGlobalDimensions(1, 'linode', preference); + expect(result).toEqual({ + options: [defaultOption, { label: 'Dim 2', value: 'Dim 2' }], + defaultValue: [{ label: 'Dim 2', value: 'Dim 2' }], + isLoading: false, + }); + }); }); describe('useWidgetDimension method test', () => { @@ -158,6 +178,32 @@ describe('useWidgetDimension method test', () => { expect(result.defaultValue).toHaveLength(0); expect(result.isLoading).toBe(false); }); + + it('should return non-empty options and non-empty default value from preferences', () => { + queryMocks.useCloudPulseDashboardByIdQuery.mockReturnValue({ + data: dashboardFactory.build(), + isLoading: false, + }); + + queryMocks.useGetCloudPulseMetricDefinitionsByServiceType.mockReturnValue({ + data: { + data: metricDefinitions, + }, + isLoading: false, + }); + const preferences = ['Dim 2']; + const result = useWidgetDimension( + 1, + 'linode', + [{ label: 'Dim 1', value: 'Dim 1' }], + 'Metric 1', + preferences + ); + + expect(result.options).toHaveLength(1); + expect(result.defaultValue).toHaveLength(1); + expect(result.isLoading).toBe(false); + }); }); describe('getCommonGroups method test', () => { it('should return empty list if groups or commonDimensions are empty', () => { From 0b4709c54a18b6b7daefe64a7b1632077d05d4f4 Mon Sep 17 00:00:00 2001 From: nikhagra-akamai Date: Fri, 26 Sep 2025 19:45:57 +0530 Subject: [PATCH 06/10] upcoming: [DI-27110] - Updated logic to maintain the order of default value selection as per configuration or preferences --- .../src/features/CloudPulse/GroupBy/utils.ts | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/GroupBy/utils.ts b/packages/manager/src/features/CloudPulse/GroupBy/utils.ts index dd5d4d7ff89..df02fdcf110 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/utils.ts +++ b/packages/manager/src/features/CloudPulse/GroupBy/utils.ts @@ -82,10 +82,18 @@ export const getCommonGroups = ( commonDimensions: GroupByOption[] ): GroupByOption[] => { if (groupBy.length === 0 || commonDimensions.length === 0) return []; - - return commonDimensions.filter((group) => { - return groupBy.includes(group.value); - }); + const commonGroups: GroupByOption[] = []; + // To maintain the order of groupBy from dashboard config or preferences + for (let index = 0; index < groupBy.length; index++) { + const group = groupBy[index]; + const commonGroup = commonDimensions.find( + (dimension) => dimension.value === group + ); + if (commonGroup) { + commonGroups.push(commonGroup); + } + } + return commonGroups; }; /** @@ -126,6 +134,7 @@ export const useWidgetDimension = ( ? preference : (dashboard?.widgets.find((widget) => widget.metric === metric) ?.group_by ?? []); + const options = metricDimensions.filter( (metricDimension) => !globalDimensions.some( @@ -133,9 +142,17 @@ export const useWidgetDimension = ( ) ); - const defaultValue = options.filter((options) => - defaultGroupBy.includes(options.value) - ); + // To maintain the order of groupBy from dashboard config or preferences + const defaultValue: GroupByOption[] = []; + + for (let index = 0; index < defaultGroupBy.length; index++) { + const groupBy = defaultGroupBy[index]; + + const defaultOption = options.find((option) => option.value === groupBy); + if (defaultOption) { + defaultValue.push(defaultOption); + } + } return { options, From c19b6a0b4e698a6ed280cd9d77490d76011cff18 Mon Sep 17 00:00:00 2001 From: nikhagra-akamai Date: Fri, 26 Sep 2025 19:49:45 +0530 Subject: [PATCH 07/10] upcomign: [DI-27110] - Updated logic to avoid preference call on initial group by selection --- .../features/CloudPulse/GroupBy/CloudPulseGroupByDrawer.tsx | 4 ++-- .../CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx | 4 ++-- .../CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/GroupBy/CloudPulseGroupByDrawer.tsx b/packages/manager/src/features/CloudPulse/GroupBy/CloudPulseGroupByDrawer.tsx index c87195aa07d..400da9e137c 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/CloudPulseGroupByDrawer.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/CloudPulseGroupByDrawer.tsx @@ -28,7 +28,7 @@ export interface GroupByDrawerProps { /** * Callback function triggered when apply button is clicked */ - onApply: (value: GroupByOption[]) => void; + onApply: (value: GroupByOption[], savePref?: boolean) => void; /** * Callback function triggered when cancel button is clicked */ @@ -96,7 +96,7 @@ export const CloudPulseGroupByDrawer = React.memo( 0, Math.min(defaultValue.length, GROUP_BY_SELECTION_LIMIT) ); - onApply(value); + onApply(value, false); setSelectedValue(value); }, [serviceType]); const handleClose = () => { diff --git a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx index ff908c165cd..e2beda55ea3 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.tsx @@ -50,7 +50,7 @@ export const GlobalFilterGroupByRenderer = ( const [open, setOpen] = React.useState(false); const onApply = React.useCallback( - (selectedValue: GroupByOption[]) => { + (selectedValue: GroupByOption[], savePref?: boolean) => { if (selectedValue.length === 0) { setIsSelected(false); } else { @@ -58,7 +58,7 @@ export const GlobalFilterGroupByRenderer = ( } handleChange( selectedValue.map(({ value }) => value), - savePreferences + savePref ?? savePreferences ); setOpen(false); }, diff --git a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx index dc70a1c7394..9eeeee7b1b3 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.tsx @@ -74,7 +74,7 @@ export const WidgetFilterGroupByRenderer = ( setOpen(false); }, []); const onApply = React.useCallback( - (selectedValue: GroupByOption[]) => { + (selectedValue: GroupByOption[], savePref?: boolean) => { if (selectedValue.length === 0) { setIsSelected(false); } else { @@ -82,7 +82,7 @@ export const WidgetFilterGroupByRenderer = ( } handleChange( selectedValue.map(({ value }) => value), - savePreferences + savePref ?? savePreferences ); setOpen(false); }, From 14b2ba9181c05778b2eeef16cb90c92b739a6177 Mon Sep 17 00:00:00 2001 From: nikhagra-akamai Date: Wed, 8 Oct 2025 13:32:40 +0530 Subject: [PATCH 08/10] upcoming: [DI-27110] - Updated save preferences logic --- .../CloudPulse/Widget/CloudPulseWidget.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx index c1bcd2e8345..f3b72ac2f7e 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx @@ -262,14 +262,17 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { }, [] ); - const handleGroupByChange = React.useCallback((selectedGroupBy: string[]) => { - if (savePref) { - updatePreferences(widget.label, { - [GROUP_BY]: selectedGroupBy, - }); - } - setGroupBy(selectedGroupBy); - }, []); + const handleGroupByChange = React.useCallback( + (selectedGroupBy: string[], savePreferences?: boolean) => { + if (savePreferences) { + updatePreferences(widget.label, { + [GROUP_BY]: selectedGroupBy, + }); + } + setGroupBy(selectedGroupBy); + }, + [] + ); const { data: metricsList, error, @@ -395,6 +398,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { label={widget.label} metric={widget.metric} preferenceGroupBy={groupBy} + savePreferences={savePref} serviceType={serviceType} /> Date: Wed, 8 Oct 2025 16:40:36 +0530 Subject: [PATCH 09/10] upcoming: [DI-27110] - Updated test cases --- .../GroupBy/GlobalFilterGroupByRenderer.test.tsx | 7 ++----- .../GroupBy/WidgetFilterGroupByRenderer.test.tsx | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.test.tsx b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.test.tsx index c3e63a710a8..a4d9274edc1 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.test.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/GlobalFilterGroupByRenderer.test.tsx @@ -86,7 +86,7 @@ describe('Global Group By Renderer Component', () => { const drawer = screen.getByTestId('drawer'); expect(drawer).toBeInTheDocument(); - expect(handleChange).toHaveBeenCalledWith([], undefined); + expect(handleChange).toHaveBeenCalledWith([], false); }); it('Should not open drawer but group by icon should be enabled', async () => { @@ -131,10 +131,7 @@ describe('Global Group By Renderer Component', () => { const drawer = screen.getByTestId('drawer'); expect(drawer).toBeInTheDocument(); - expect(handleChange).toHaveBeenCalledWith( - [defaultValue[0].value], - undefined - ); + expect(handleChange).toHaveBeenCalledWith([defaultValue[0].value], false); defaultValue.forEach((value) => { const option = screen.getByRole('button', { name: value.label }); diff --git a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.test.tsx b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.test.tsx index a3e335fe012..0c326e38996 100644 --- a/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.test.tsx +++ b/packages/manager/src/features/CloudPulse/GroupBy/WidgetFilterGroupByRenderer.test.tsx @@ -85,7 +85,7 @@ describe('Widget Group By Renderer', () => { const title = screen.getByText('Group By'); expect(title).toBeInTheDocument(); - expect(handleChange).toHaveBeenCalledWith([], undefined); + expect(handleChange).toHaveBeenCalledWith([], false); }); it('Should not open drawer but group by icon should be enabled', async () => { @@ -120,10 +120,7 @@ describe('Widget Group By Renderer', () => { const drawer = screen.getByTestId('drawer'); expect(drawer).toBeInTheDocument(); - expect(handleChange).toHaveBeenCalledWith( - [defaultValue[0].value], - undefined - ); + expect(handleChange).toHaveBeenCalledWith([defaultValue[0].value], false); defaultValue.forEach((value) => { const option = screen.getByRole('button', { name: value.label }); From 49e3eede06697700c188a05bd8fb691442609074 Mon Sep 17 00:00:00 2001 From: nikhagra-akamai Date: Wed, 8 Oct 2025 17:49:25 +0530 Subject: [PATCH 10/10] added changeset --- .../.changeset/pr-12969-upcoming-features-1759925909029.md | 5 +++++ .../.changeset/pr-12969-upcoming-features-1759925950294.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 packages/api-v4/.changeset/pr-12969-upcoming-features-1759925909029.md create mode 100644 packages/manager/.changeset/pr-12969-upcoming-features-1759925950294.md diff --git a/packages/api-v4/.changeset/pr-12969-upcoming-features-1759925909029.md b/packages/api-v4/.changeset/pr-12969-upcoming-features-1759925909029.md new file mode 100644 index 00000000000..b3c39fccbb0 --- /dev/null +++ b/packages/api-v4/.changeset/pr-12969-upcoming-features-1759925909029.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Upcoming Features +--- + +ACLP: add `groupBy` in `AclpWidget` interface of cloudpulse types ([#12969](https://github.com/linode/manager/pull/12969)) diff --git a/packages/manager/.changeset/pr-12969-upcoming-features-1759925950294.md b/packages/manager/.changeset/pr-12969-upcoming-features-1759925950294.md new file mode 100644 index 00000000000..49f3bcd842b --- /dev/null +++ b/packages/manager/.changeset/pr-12969-upcoming-features-1759925950294.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +ACLP: add `group by preference` support for group-by feature ([#12969](https://github.com/linode/manager/pull/12969))