Skip to content

Commit 40219ce

Browse files
upcoming: [DI-27803] - Add new optional NodeBalancers filter in Nodebalancer-Firewall dashboard (linode#13029)
* [DI-27803] - Add new optional nodebalancers filter in nodebalancer firewall dashboard * [DI-27803] - update as latest aclp dev * upcoming: [DI-27803] - cleanup * upcoming: [DI-27803] - more cleanup * upcoming: [DI-27803] - Pr comments * upcoming: [DI-27803] - Pass order by * upcoming: [DI-27803] - Minor cleanup * upcoming: [DI-27803] - Add temporary integration in firewalls page for reviewer * upcoming: [DI-27803] - Add changeset * upcoming: [DI-27803] - use existing style const * upcoming: [DI-27803] - Remove temporary integration in service page
1 parent 3de19a3 commit 40219ce

15 files changed

Lines changed: 922 additions & 13 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Upcoming Features
3+
---
4+
5+
CloudPulse-Metrics: Add optional-filter component at `CloudPulseFirewallNodebalancersSelect.tsx` and integrate it with existing firewall-nodebalancer filters ([#13029](https://github.com/linode/manager/pull/13029))

packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import {
1111

1212
import { RESOURCE_FILTER_MAP } from '../Utils/constants';
1313
import { useAclpPreference } from '../Utils/UserPreference';
14-
import { getResourcesFilterConfig } from '../Utils/utils';
14+
import {
15+
getAssociatedEntityType,
16+
getResourcesFilterConfig,
17+
} from '../Utils/utils';
1518
import {
1619
renderPlaceHolder,
1720
RenderWidgets,
@@ -114,9 +117,9 @@ export const CloudPulseDashboard = (props: DashboardProperties) => {
114117

115118
// Get the resources filter configuration for the dashboard
116119
const resourcesFilterConfig = getResourcesFilterConfig(dashboardId);
117-
const associatedEntityType =
118-
resourcesFilterConfig?.associatedEntityType ?? 'both';
119120
const filterFn = resourcesFilterConfig?.filterFn;
121+
// Get the associated entity type for the dashboard
122+
const associatedEntityType = getAssociatedEntityType(dashboardId);
120123

121124
const {
122125
data: resourceList,

packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { databaseQueries } from '@linode/queries';
2+
import { nodeBalancerFactory } from '@linode/utilities';
23
import { DateTime } from 'luxon';
34

45
import {
@@ -12,9 +13,11 @@ import {
1213
deepEqual,
1314
filterBasedOnConfig,
1415
filterEndpointsUsingRegion,
16+
filterFirewallNodebalancers,
1517
filterUsingDependentFilters,
1618
getEndpointsProperties,
1719
getFilters,
20+
getFirewallNodebalancersProperties,
1821
getTextFilterProperties,
1922
} from './FilterBuilder';
2023
import {
@@ -43,7 +46,9 @@ const dbaasConfig = FILTER_CONFIG.get(1);
4346

4447
const nodeBalancerConfig = FILTER_CONFIG.get(3);
4548

46-
const firewallConfig = FILTER_CONFIG.get(4);
49+
const linodeFirewallConfig = FILTER_CONFIG.get(4);
50+
51+
const nodebalancerFirewallConfig = FILTER_CONFIG.get(8);
4752

4853
const dbaasDashboard = dashboardFactory.build({ service_type: 'dbaas', id: 1 });
4954

@@ -135,7 +140,7 @@ it('test getResourceSelectionProperties method', () => {
135140
});
136141

137142
it('test getResourceSelectionProperties method for linode-firewall', () => {
138-
const resourceSelectionConfig = firewallConfig?.filters.find(
143+
const resourceSelectionConfig = linodeFirewallConfig?.filters.find(
139144
(filterObj) => filterObj.name === 'Firewalls'
140145
);
141146

@@ -426,7 +431,7 @@ it('test getTextFilterProperties method for port', () => {
426431
});
427432

428433
it('test getTextFilterProperties method for interface_id', () => {
429-
const interfaceIdFilterConfig = firewallConfig?.filters.find(
434+
const interfaceIdFilterConfig = linodeFirewallConfig?.filters.find(
430435
(filterObj) => filterObj.name === 'Interface IDs'
431436
);
432437

@@ -488,6 +493,49 @@ it('test getEndpointsProperties method', () => {
488493
expect(xFilter).toEqual({ region: 'us-east' });
489494
}
490495
});
496+
it('test getFirewallNodebalancersProperties', () => {
497+
const nodebalancersConfig = nodebalancerFirewallConfig?.filters.find(
498+
(filterObj) => filterObj.name === 'NodeBalancers'
499+
);
500+
501+
expect(nodebalancersConfig).toBeDefined();
502+
503+
if (nodebalancersConfig) {
504+
const nodebalancersProperties = getFirewallNodebalancersProperties(
505+
{
506+
config: nodebalancersConfig,
507+
dashboard: dashboardFactory.build({ service_type: 'firewall', id: 8 }),
508+
dependentFilters: {
509+
resource_id: '1',
510+
associated_entity_region: 'us-east',
511+
},
512+
isServiceAnalyticsIntegration: false,
513+
},
514+
vi.fn()
515+
);
516+
const {
517+
label,
518+
disabled,
519+
selectedDashboard,
520+
savePreferences,
521+
handleNodebalancersSelection,
522+
defaultValue,
523+
xFilter,
524+
} = nodebalancersProperties;
525+
526+
expect(nodebalancersProperties).toBeDefined();
527+
expect(label).toEqual(nodebalancersConfig.configuration.name);
528+
expect(selectedDashboard.service_type).toEqual('firewall');
529+
expect(savePreferences).toEqual(true);
530+
expect(disabled).toEqual(false);
531+
expect(handleNodebalancersSelection).toBeDefined();
532+
expect(defaultValue).toEqual(undefined);
533+
expect(xFilter).toEqual({
534+
resource_id: '1',
535+
associated_entity_region: 'us-east',
536+
});
537+
}
538+
});
491539

492540
it('test getFiltersForMetricsCallFromCustomSelect method', () => {
493541
const result = getMetricsCallCustomFilters(
@@ -669,6 +717,76 @@ describe('filterEndpointsUsingRegion', () => {
669717
});
670718
});
671719

720+
describe('filterFirewallNodebalancers', () => {
721+
const mockData = [
722+
nodeBalancerFactory.build({
723+
id: 1,
724+
label: 'nodebalancer-1',
725+
region: 'us-east',
726+
}),
727+
nodeBalancerFactory.build({
728+
id: 2,
729+
label: 'nodebalancer-2',
730+
region: 'us-west',
731+
}),
732+
];
733+
const mockFirewalls: CloudPulseResources[] = [
734+
{
735+
id: '1',
736+
label: 'firewall-1',
737+
entities: { '1': 'nodebalancer-1' },
738+
},
739+
];
740+
741+
it('should return undefined if data is undefined', () => {
742+
expect(
743+
filterFirewallNodebalancers(
744+
undefined,
745+
{ associated_entity_region: 'us-east', resource_id: '1' },
746+
mockFirewalls
747+
)
748+
).toEqual(undefined);
749+
});
750+
751+
it('should return undefined if xFilter/firewalls is empty or undefined', () => {
752+
const result = filterFirewallNodebalancers(
753+
mockData,
754+
undefined,
755+
mockFirewalls
756+
);
757+
const result2 = filterFirewallNodebalancers(mockData, {}, mockFirewalls);
758+
const result3 = filterFirewallNodebalancers(
759+
mockData,
760+
{ associated_entity_region: 'us-east', resource_id: '1' },
761+
[]
762+
);
763+
const result4 = filterFirewallNodebalancers(
764+
mockData,
765+
{ associated_entity_region: 'us-east', resource_id: '1' },
766+
undefined
767+
);
768+
expect(result).toEqual(undefined);
769+
expect(result2).toEqual(undefined);
770+
expect(result3).toEqual(undefined);
771+
expect(result4).toEqual(undefined);
772+
});
773+
774+
it('should filter nodebalancers based on xFilter', () => {
775+
const result = filterFirewallNodebalancers(
776+
mockData,
777+
{ associated_entity_region: 'us-east', resource_id: '1' },
778+
mockFirewalls
779+
);
780+
expect(result).toEqual([
781+
{
782+
id: '1',
783+
label: 'nodebalancer-1',
784+
associated_entity_region: 'us-east',
785+
},
786+
]);
787+
});
788+
});
789+
672790
describe('filterBasedOnConfig', () => {
673791
const config: CloudPulseServiceTypeFilters = {
674792
configuration: {

packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from './constants';
99
import { FILTER_CONFIG } from './FilterConfig';
1010
import { CloudPulseAvailableViews, CloudPulseSelectTypes } from './models';
11+
import { getAssociatedEntityType } from './utils';
1112

1213
import type {
1314
CloudPulseMetricsFilter,
@@ -16,6 +17,10 @@ import type {
1617
import type { CloudPulseCustomSelectProps } from '../shared/CloudPulseCustomSelect';
1718
import type { CloudPulseEndpointsSelectProps } from '../shared/CloudPulseEndpointsSelect';
1819
import type { CloudPulseEndpoints } from '../shared/CloudPulseEndpointsSelect';
20+
import type {
21+
CloudPulseFirewallNodebalancersSelectProps,
22+
CloudPulseNodebalancers,
23+
} from '../shared/CloudPulseFirewallNodebalancersSelect';
1924
import type { CloudPulseNodeTypeFilterProps } from '../shared/CloudPulseNodeTypeFilter';
2025
import type { CloudPulseRegionSelectProps } from '../shared/CloudPulseRegionSelect';
2126
import type {
@@ -36,6 +41,7 @@ import type {
3641
Dashboard,
3742
DateTimeWithPreset,
3843
Filters,
44+
NodeBalancer,
3945
TimeDuration,
4046
} from '@linode/api-v4';
4147

@@ -183,7 +189,7 @@ export const getResourcesProperties = (
183189
resourceType: dashboard.service_type,
184190
savePreferences: !isServiceAnalyticsIntegration,
185191
xFilter: filterBasedOnConfig(config, dependentFilters ?? {}),
186-
associatedEntityType: config.configuration.associatedEntityType ?? 'both',
192+
associatedEntityType: getAssociatedEntityType(dashboard.id),
187193
filterFn: config.configuration.filterFn,
188194
};
189195
};
@@ -408,6 +414,47 @@ export const getEndpointsProperties = (
408414
};
409415
};
410416

417+
/**
418+
*
419+
* @param props The cloudpulse filter properties selected so far
420+
* @param handleFirewallNodebalancersChange The callback function when selection of nodebalancers changes
421+
* @returns CloudPulseFirewallNodebalancersSelectProps
422+
*/
423+
export const getFirewallNodebalancersProperties = (
424+
props: CloudPulseFilterProperties,
425+
handleFirewallNodebalancersChange: (
426+
nodebalancers: CloudPulseNodebalancers[],
427+
savePref?: boolean
428+
) => void
429+
): CloudPulseFirewallNodebalancersSelectProps => {
430+
const { filterKey, name: label, placeholder } = props.config.configuration;
431+
const {
432+
config,
433+
dashboard,
434+
dependentFilters,
435+
isServiceAnalyticsIntegration,
436+
preferences,
437+
shouldDisable,
438+
} = props;
439+
return {
440+
defaultValue: preferences?.[config.configuration.filterKey],
441+
selectedDashboard: dashboard,
442+
disabled:
443+
shouldDisable ||
444+
shouldDisableFilterByFilterKey(
445+
filterKey,
446+
dependentFilters ?? {},
447+
dashboard,
448+
preferences
449+
),
450+
handleNodebalancersSelection: handleFirewallNodebalancersChange,
451+
label,
452+
placeholder,
453+
savePreferences: !isServiceAnalyticsIntegration,
454+
xFilter: filterBasedOnConfig(config, dependentFilters ?? {}),
455+
isOptional: config.configuration.isOptional,
456+
};
457+
};
411458
/**
412459
* This function helps in builder the xFilter needed to passed in a apiV4 call
413460
*
@@ -769,3 +816,45 @@ export const filterEndpointsUsingRegion = (
769816

770817
return data.filter(({ region }) => region === regionFromFilter);
771818
};
819+
820+
/**
821+
*
822+
* @param data The nodebalancers for which the filter needs to be applied
823+
* @param xFilter The selected filters that will be used to filter the nodebalancers
824+
* @param firewalls The firewalls for which the filter needs to be applied
825+
* @returns The filtered nodebalancers
826+
*/
827+
828+
export const filterFirewallNodebalancers = (
829+
data?: NodeBalancer[],
830+
xFilter?: CloudPulseMetricsFilter,
831+
firewalls?: CloudPulseResources[]
832+
): CloudPulseNodebalancers[] | undefined => {
833+
// If data is undefined or xFilter/firewalls is undefined or empty, return undefined
834+
if (!data || !xFilter || !Object.keys(xFilter).length || !firewalls?.length) {
835+
return undefined;
836+
}
837+
838+
// Map the nodebalancers to the CloudPulseNodebalancers interface
839+
const nodebalancers: CloudPulseNodebalancers[] = data.map((nodebalancer) => ({
840+
id: String(nodebalancer.id),
841+
label: nodebalancer.label,
842+
associated_entity_region: nodebalancer.region,
843+
}));
844+
845+
const firewallObj = firewalls.find(
846+
(firewall) => firewall.id === String(xFilter[RESOURCE_ID])
847+
);
848+
849+
return nodebalancers.filter((nodebalancer) => {
850+
return Object.entries(xFilter).every(([key, filterValue]) => {
851+
// If the filter key is the resource id, check if the nodebalancer is associated with the selected firewall
852+
if (key === RESOURCE_ID) {
853+
return firewallObj?.entities?.[nodebalancer.id];
854+
}
855+
const nodebalancerValue =
856+
nodebalancer[key as keyof CloudPulseNodebalancers];
857+
return nodebalancerValue === filterValue;
858+
});
859+
});
860+
};

packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { queryFactory } from 'src/queries/cloudpulse/queries';
55
import {
66
ENDPOINT,
77
INTERFACE_IDS_PLACEHOLDER_TEXT,
8+
NODEBALANCER_ID,
89
PARENT_ENTITY_REGION,
910
REGION,
1011
RESOURCE_ID,
@@ -322,6 +323,7 @@ export const FIREWALL_CONFIG: Readonly<CloudPulseServiceTypeFilterMap> = {
322323
},
323324
],
324325
serviceType: 'firewall',
326+
associatedEntityType: 'linode',
325327
};
326328

327329
export const FIREWALL_NODEBALANCER_CONFIG: Readonly<CloudPulseServiceTypeFilterMap> =
@@ -362,8 +364,28 @@ export const FIREWALL_NODEBALANCER_CONFIG: Readonly<CloudPulseServiceTypeFilterM
362364
},
363365
name: 'NodeBalancer Region',
364366
},
367+
{
368+
configuration: {
369+
dependency: [PARENT_ENTITY_REGION, RESOURCE_ID],
370+
filterKey: NODEBALANCER_ID,
371+
filterType: 'string',
372+
isFilterable: true,
373+
isMetricsFilter: false,
374+
isMultiSelect: true,
375+
isOptional: true,
376+
name: 'NodeBalancers',
377+
neededInViews: [
378+
CloudPulseAvailableViews.central,
379+
CloudPulseAvailableViews.service,
380+
],
381+
placeholder: 'Select NodeBalancers',
382+
priority: 3,
383+
},
384+
name: 'NodeBalancers',
385+
},
365386
],
366387
serviceType: 'firewall',
388+
associatedEntityType: 'nodebalancer',
367389
};
368390

369391
export const OBJECTSTORAGE_CONFIG_BUCKET: Readonly<CloudPulseServiceTypeFilterMap> =

packages/manager/src/features/CloudPulse/Utils/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export const PARENT_ENTITY_REGION = 'associated_entity_region';
1616

1717
export const RESOURCES = 'resources';
1818

19+
export const NODEBALANCER_ID = 'nodebalancer_id';
20+
1921
export const INTERVAL = 'interval';
2022

2123
export const TIME_DURATION = 'dateTimeDuration';

0 commit comments

Comments
 (0)