From cbe23dc0eabc04139bdf52ed286b6fd21520be3d Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Wed, 5 Nov 2025 10:06:34 +0530 Subject: [PATCH 1/7] upcoming:[DI-28000] - Add lke dashboard --- packages/api-v4/src/cloudpulse/types.ts | 2 + .../features/CloudPulse/Utils/FilterConfig.ts | 41 +++++++++++++++-- .../src/features/CloudPulse/Utils/models.ts | 3 +- .../features/CloudPulse/Utils/utils.test.ts | 46 ++++++++++++++++++- .../src/features/CloudPulse/Utils/utils.ts | 13 ++++++ .../shared/CloudPulseDashboardSelect.tsx | 10 +++- .../shared/CloudPulseRegionSelect.tsx | 14 +++++- packages/manager/src/mocks/serverHandlers.ts | 30 +++++++++++- .../manager/src/queries/cloudpulse/queries.ts | 3 ++ .../utilities/src/__data__/regionsData.ts | 2 + 10 files changed, 155 insertions(+), 9 deletions(-) diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index 787cc496c44..af46910604f 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -8,6 +8,7 @@ export type CloudPulseServiceType = | 'dbaas' | 'firewall' | 'linode' + | 'lke' | 'nodebalancer' | 'objectstorage'; export type AlertClass = 'dedicated' | 'shared'; @@ -381,6 +382,7 @@ export const capabilityServiceTypeMapping: Record< firewall: 'Cloud Firewall', objectstorage: 'Object Storage', blockstorage: 'Block Storage', + lke: 'Kubernetes Enterprise', }; /** diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts b/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts index a68c9968303..75ae75b61ab 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts @@ -11,10 +11,10 @@ import { RESOURCE_ID, } from './constants'; import { CloudPulseAvailableViews, CloudPulseSelectTypes } from './models'; -import { filterFirewallResources } from './utils'; +import { filterFirewallResources, filterKubernetesClusters } from './utils'; import type { CloudPulseServiceTypeFilterMap } from './models'; -import type { Firewall } from '@linode/api-v4'; +import type { Firewall, KubernetesCluster } from '@linode/api-v4'; const TIME_DURATION = 'Time Range'; @@ -470,7 +470,41 @@ export const BLOCKSTORAGE_CONFIG: Readonly = { ], serviceType: 'blockstorage', }; - +export const LKE_CONFIG: Readonly = { + capability: capabilityServiceTypeMapping['lke'], + filters: [ + { + configuration: { + filterKey: 'region', + filterType: 'string', + isFilterable: false, + isMetricsFilter: false, + name: 'Region', + priority: 1, + neededInViews: [CloudPulseAvailableViews.central], + }, + name: 'Region', + }, + { + configuration: { + dependency: ['region'], + filterKey: 'resource_id', + filterType: 'string', + isFilterable: true, + isMetricsFilter: true, + isMultiSelect: true, + name: 'Clusters', + neededInViews: [CloudPulseAvailableViews.central], + placeholder: 'Select Clusters', + priority: 2, + filterFn: (resources: KubernetesCluster[]) => + filterKubernetesClusters(resources), + }, + name: 'Clusters', + }, + ], + serviceType: 'lke', +}; export const FILTER_CONFIG: Readonly< Map > = new Map([ @@ -481,4 +515,5 @@ export const FILTER_CONFIG: Readonly< [6, OBJECTSTORAGE_CONFIG_BUCKET], [7, BLOCKSTORAGE_CONFIG], [8, FIREWALL_NODEBALANCER_CONFIG], + [9, LKE_CONFIG], ]); diff --git a/packages/manager/src/features/CloudPulse/Utils/models.ts b/packages/manager/src/features/CloudPulse/Utils/models.ts index 0386c8bd7a2..2a6e9ec2941 100644 --- a/packages/manager/src/features/CloudPulse/Utils/models.ts +++ b/packages/manager/src/features/CloudPulse/Utils/models.ts @@ -6,6 +6,7 @@ import type { DatabaseInstance, DatabaseType, Firewall, + KubernetesCluster, Linode, NodeBalancer, ObjectStorageBucket, @@ -59,11 +60,11 @@ export type QueryFunctionType = | DatabaseInstance[] | DatabaseType[] | Firewall[] + | KubernetesCluster[] | Linode[] | NodeBalancer[] | ObjectStorageBucket[] | Volume[]; - /** * The non array types of QueryFunctionType like DatabaseEngine|DatabaseType */ diff --git a/packages/manager/src/features/CloudPulse/Utils/utils.test.ts b/packages/manager/src/features/CloudPulse/Utils/utils.test.ts index 657235635bb..3c6d3be0e5f 100644 --- a/packages/manager/src/features/CloudPulse/Utils/utils.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/utils.test.ts @@ -1,7 +1,7 @@ import { regionFactory } from '@linode/utilities'; import { describe, expect, it } from 'vitest'; -import { serviceTypesFactory } from 'src/factories'; +import { kubernetesClusterFactory, serviceTypesFactory } from 'src/factories'; import { firewallEntityfactory, firewallFactory, @@ -25,6 +25,7 @@ import { arePortsValid, areValidInterfaceIds, filterFirewallResources, + filterKubernetesClusters, getAssociatedEntityType, getEnabledServiceTypes, getFilteredDimensions, @@ -452,6 +453,49 @@ describe('getEnabledServiceTypes', () => { }); }); +describe('filterKubernetesClusters', () => { + it('should return the filtered kubernetes clusters for enterprise', () => { + const clusters = [ + ...kubernetesClusterFactory.buildList(5, { tier: 'standard' }), + ...kubernetesClusterFactory.buildList(5, { tier: 'enterprise' }), + ]; + expect(filterKubernetesClusters(clusters)).toHaveLength(5); + }); + it('should return the filtered kubernetes clusters for enterprise sorted by label', () => { + const clusters = [ + kubernetesClusterFactory.build({ + tier: 'enterprise', + label: 'pl-labkrk-2-redis-cluster', + }), + kubernetesClusterFactory.build({ + tier: 'enterprise', + label: 'pl-labkrk-2-mr-api-4', + }), + kubernetesClusterFactory.build({ + tier: 'enterprise', + label: 'pl-labkrk-2-alertmanager2', + }), + kubernetesClusterFactory.build({ + tier: 'enterprise', + label: 'pl-labkrk-2-alertmanager', + }), + ]; + + expect(filterKubernetesClusters(clusters)[0].label).toBe( + 'pl-labkrk-2-alertmanager' + ); + expect(filterKubernetesClusters(clusters)[1].label).toBe( + 'pl-labkrk-2-alertmanager2' + ); + expect(filterKubernetesClusters(clusters)[2].label).toBe( + 'pl-labkrk-2-mr-api-4' + ); + expect(filterKubernetesClusters(clusters)[3].label).toBe( + 'pl-labkrk-2-redis-cluster' + ); + }); +}); + describe('isValidFilter', () => { const valuedDim: Dimension = { dimension_label: 'browser', diff --git a/packages/manager/src/features/CloudPulse/Utils/utils.ts b/packages/manager/src/features/CloudPulse/Utils/utils.ts index c49c50500ae..2c2f6481d44 100644 --- a/packages/manager/src/features/CloudPulse/Utils/utils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/utils.ts @@ -39,6 +39,7 @@ import type { Dimension, Firewall, FirewallDeviceEntity, + KubernetesCluster, MonitoringCapabilities, ResourcePage, Service, @@ -587,3 +588,15 @@ export const filterFirewallResources = ( }) ); }; + +/** + * @param clusters The list of kubernetes clusters + * @returns The filtered kubernetes clusters based on the tier + */ +export const filterKubernetesClusters = ( + clusters: KubernetesCluster[] +): KubernetesCluster[] => { + return clusters + .filter(({ tier }) => tier === 'enterprise') + .sort((a, b) => a.label.localeCompare(b.label)); +}; diff --git a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardSelect.tsx b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardSelect.tsx index 07153cc8529..533191f2f93 100644 --- a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardSelect.tsx +++ b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardSelect.tsx @@ -52,7 +52,15 @@ export const CloudPulseDashboardSelect = React.memo( isLoading: serviceTypesLoading, } = useCloudPulseServiceTypes(true); - const { aclpServices } = useFlags(); + // const { aclpServices } = useFlags(); + const aclpServices = { + lke: { + metrics: { + enabled: true, + beta: true, + }, + }, + } // Get formatted enabled service types based on the LD flag const serviceTypes: CloudPulseServiceType[] = getEnabledServiceTypes( diff --git a/packages/manager/src/features/CloudPulse/shared/CloudPulseRegionSelect.tsx b/packages/manager/src/features/CloudPulse/shared/CloudPulseRegionSelect.tsx index 49f7f6ebbd5..c49ad4c0fd7 100644 --- a/packages/manager/src/features/CloudPulse/shared/CloudPulseRegionSelect.tsx +++ b/packages/manager/src/features/CloudPulse/shared/CloudPulseRegionSelect.tsx @@ -15,7 +15,10 @@ import { } from '../Utils/constants'; import { deepEqual, filterUsingDependentFilters } from '../Utils/FilterBuilder'; import { FILTER_CONFIG } from '../Utils/FilterConfig'; -import { getAssociatedEntityType } from '../Utils/utils'; +import { + getAssociatedEntityType, + getResourcesFilterConfig, +} from '../Utils/utils'; import { CLOUD_PULSE_TEXT_FIELD_PROPS } from './styles'; import type { Item } from '../Alerts/constants'; @@ -56,6 +59,11 @@ export const CloudPulseRegionSelect = React.memo( } = props; const { data: regions, isError, isLoading } = useRegionsQuery(); + // Get the resources filter configuration for the dashboard + const resourcesFilterConfig = getResourcesFilterConfig( + selectedDashboard?.id + ); + const filterFn = resourcesFilterConfig?.filterFn; const { data: resources, isError: isResourcesError, @@ -66,7 +74,9 @@ export const CloudPulseRegionSelect = React.memo( {}, { ...(RESOURCE_FILTER_MAP[selectedDashboard?.service_type ?? ''] ?? {}), - } + }, + undefined, + filterFn ); const flags = useFlags(); diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts index 14d3237a9b9..b978e4d63fa 100644 --- a/packages/manager/src/mocks/serverHandlers.ts +++ b/packages/manager/src/mocks/serverHandlers.ts @@ -1164,7 +1164,13 @@ export const handlers = [ http.get('*/lke/clusters', async () => { const clusters = kubernetesAPIResponse.buildList(10); - return HttpResponse.json(makeResourcePage(clusters)); + const enterpriseClusters = kubernetesAPIResponse.buildList(11, { + tier: 'enterprise', + region: 'ap-west', + }); + return HttpResponse.json( + makeResourcePage([...clusters, ...enterpriseClusters]) + ); }), http.get('*/lke/types', async () => { const lkeTypes = [ @@ -3168,6 +3174,14 @@ export const handlers = [ regions: 'us-iad,us-east', alert: serviceAlertFactory.build({ scope: ['entity'] }), }), + serviceTypesFactory.build({ + label: 'LKE Enterprise', + service_type: 'lke', + regions: 'us-iad,us-east', + alert: serviceAlertFactory.build({ + scope: ['entity', 'account', 'region'], + }), + }), ], }; @@ -3182,6 +3196,7 @@ export const handlers = [ firewall: 'Firewalls', objectstorage: 'Object Storage', blockstorage: 'Block Storage', + lke: 'LKE Enterprise', }; const response = serviceTypesFactory.build({ service_type: `${serviceType}`, @@ -3288,6 +3303,16 @@ export const handlers = [ ); } + if (params.serviceType === 'lke') { + response.data.push( + dashboardFactory.build({ + id: 9, + label: 'LKE Enterprise Dashboard', + service_type: 'lke', + }) + ); + } + return HttpResponse.json(response); }), http.get( @@ -3680,6 +3705,9 @@ export const handlers = [ } else if (id === '8') { serviceType = 'firewall'; dashboardLabel = 'Firewall Nodebalancer Dashboard'; + } else if (id === '9') { + serviceType = 'lke'; + dashboardLabel = 'Kubernetes Enterprise Dashboard'; } else { serviceType = 'linode'; dashboardLabel = 'Linode Service I/O Statistics'; diff --git a/packages/manager/src/queries/cloudpulse/queries.ts b/packages/manager/src/queries/cloudpulse/queries.ts index 309169997f2..024e680db08 100644 --- a/packages/manager/src/queries/cloudpulse/queries.ts +++ b/packages/manager/src/queries/cloudpulse/queries.ts @@ -16,6 +16,7 @@ import { } from '@linode/queries'; import { createQueryKeys } from '@lukemorales/query-key-factory'; +import { kubernetesQueries } from '../kubernetes'; import { objectStorageQueries } from '../object-storage/queries'; import { getAllBucketsFromEndpoints, @@ -128,6 +129,8 @@ export const queryFactory = createQueryKeys(key, { queryFn: () => getAllLinodesRequest(params, filters), // since we don't have query factory implementation, in linodes.ts, once it is ready we will reuse that, untill then we will use same query keys queryKey: ['linodes', params, filters], }; + case 'lke': + return kubernetesQueries.lists._ctx.all; case 'nodebalancer': return nodebalancerQueries.nodebalancers._ctx.all(params, filters); case 'objectstorage': diff --git a/packages/utilities/src/__data__/regionsData.ts b/packages/utilities/src/__data__/regionsData.ts index 13bb19985e6..d014016312f 100644 --- a/packages/utilities/src/__data__/regionsData.ts +++ b/packages/utilities/src/__data__/regionsData.ts @@ -8,6 +8,7 @@ export const regions: Region[] = [ 'Block Storage', 'GPU Linodes', 'Kubernetes', + 'Kubernetes Enterprise', 'Cloud Firewall', 'Vlans', 'VPCs', @@ -36,6 +37,7 @@ export const regions: Region[] = [ 'Linodes', 'Managed Databases', 'Block Storage', + 'Kubernetes Enterprise', ], }, }, From dd798c4bc33d9a8b63accae9194bc34d2d43f6d7 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Wed, 5 Nov 2025 11:51:48 +0530 Subject: [PATCH 2/7] upcoming: [DI-28000] - Add no region msg --- packages/manager/src/features/CloudPulse/Utils/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/manager/src/features/CloudPulse/Utils/constants.ts b/packages/manager/src/features/CloudPulse/Utils/constants.ts index 2095c2852be..0a31f88491e 100644 --- a/packages/manager/src/features/CloudPulse/Utils/constants.ts +++ b/packages/manager/src/features/CloudPulse/Utils/constants.ts @@ -98,6 +98,7 @@ export const NO_REGION_MESSAGE: Record = { 6: 'No Object Storage buckets configured in any region.', 7: 'No volumes configured in any regions.', 8: 'No firewalls configured in any Nodebalancer regions.', + 9: 'No clusters configured in any regions.', }; export const HELPER_TEXT: Record = { From b746dc90cc12f7a941237e49c7486ef2fb9c306d Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Wed, 5 Nov 2025 11:54:05 +0530 Subject: [PATCH 3/7] upcoming: [DI-28000] - Remove temp override --- .../CloudPulse/shared/CloudPulseDashboardSelect.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardSelect.tsx b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardSelect.tsx index 533191f2f93..07153cc8529 100644 --- a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardSelect.tsx +++ b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardSelect.tsx @@ -52,15 +52,7 @@ export const CloudPulseDashboardSelect = React.memo( isLoading: serviceTypesLoading, } = useCloudPulseServiceTypes(true); - // const { aclpServices } = useFlags(); - const aclpServices = { - lke: { - metrics: { - enabled: true, - beta: true, - }, - }, - } + const { aclpServices } = useFlags(); // Get formatted enabled service types based on the LD flag const serviceTypes: CloudPulseServiceType[] = getEnabledServiceTypes( From 3df2c3d2eff780417d3854936dcac16bdc00ece4 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Wed, 5 Nov 2025 14:46:12 +0530 Subject: [PATCH 4/7] upcoming: [DI-28000] - Add test case --- .../shared/CloudPulseRegionSelect.test.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/manager/src/features/CloudPulse/shared/CloudPulseRegionSelect.test.tsx b/packages/manager/src/features/CloudPulse/shared/CloudPulseRegionSelect.test.tsx index 744071010af..14d53e1a7e8 100644 --- a/packages/manager/src/features/CloudPulse/shared/CloudPulseRegionSelect.test.tsx +++ b/packages/manager/src/features/CloudPulse/shared/CloudPulseRegionSelect.test.tsx @@ -304,6 +304,21 @@ describe('CloudPulseRegionSelect', () => { expect(screen.getByText(NO_REGION_MESSAGE[4])).toBeVisible(); }); + it('should render a Region Select component with correct info message when no regions are available for lke service type', async () => { + const user = userEvent.setup(); + renderWithTheme( + + ); + await user.click(screen.getByRole('button', { name: 'Open' })); + expect(screen.getByText(NO_REGION_MESSAGE[9])).toBeVisible(); + }); + it('Should show the correct linode region in the dropdown for firewall service type when savePreferences is true', async () => { const user = userEvent.setup(); queryMocks.useRegionsQuery.mockReturnValue({ From 362959689101ae1b34adb588de76db4f5ac91675 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Wed, 5 Nov 2025 18:51:00 +0530 Subject: [PATCH 5/7] upcoming: [DI-28000] - Update type and no region msg --- packages/api-v4/src/cloudpulse/types.ts | 2 +- packages/manager/src/features/CloudPulse/Utils/constants.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index af46910604f..4ac6b19d7f8 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -382,7 +382,7 @@ export const capabilityServiceTypeMapping: Record< firewall: 'Cloud Firewall', objectstorage: 'Object Storage', blockstorage: 'Block Storage', - lke: 'Kubernetes Enterprise', + lke: 'Kubernetes', }; /** diff --git a/packages/manager/src/features/CloudPulse/Utils/constants.ts b/packages/manager/src/features/CloudPulse/Utils/constants.ts index 0a31f88491e..82f9e6b4599 100644 --- a/packages/manager/src/features/CloudPulse/Utils/constants.ts +++ b/packages/manager/src/features/CloudPulse/Utils/constants.ts @@ -98,7 +98,7 @@ export const NO_REGION_MESSAGE: Record = { 6: 'No Object Storage buckets configured in any region.', 7: 'No volumes configured in any regions.', 8: 'No firewalls configured in any Nodebalancer regions.', - 9: 'No clusters configured in any regions.', + 9: 'No LKE clusters configured in any regions.', }; export const HELPER_TEXT: Record = { From 526fd6cf0d67d7c276d25517597d7b2c4c92db0d Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Wed, 5 Nov 2025 18:52:31 +0530 Subject: [PATCH 6/7] upcoming: [DI-28000] - Update mocks --- packages/utilities/src/__data__/regionsData.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/utilities/src/__data__/regionsData.ts b/packages/utilities/src/__data__/regionsData.ts index d014016312f..28101c979eb 100644 --- a/packages/utilities/src/__data__/regionsData.ts +++ b/packages/utilities/src/__data__/regionsData.ts @@ -8,7 +8,6 @@ export const regions: Region[] = [ 'Block Storage', 'GPU Linodes', 'Kubernetes', - 'Kubernetes Enterprise', 'Cloud Firewall', 'Vlans', 'VPCs', @@ -37,7 +36,7 @@ export const regions: Region[] = [ 'Linodes', 'Managed Databases', 'Block Storage', - 'Kubernetes Enterprise', + 'Kubernetes', ], }, }, From fcb9d6b8e02d2edad4b5cbfb494a83e3b392c807 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Thu, 6 Nov 2025 21:33:43 +0530 Subject: [PATCH 7/7] upcoming: [DI-28000] - Add changesets --- .../.changeset/pr-13064-upcoming-features-1762444425315.md | 5 +++++ .../.changeset/pr-13064-upcoming-features-1762444937800.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 packages/api-v4/.changeset/pr-13064-upcoming-features-1762444425315.md create mode 100644 packages/manager/.changeset/pr-13064-upcoming-features-1762444937800.md diff --git a/packages/api-v4/.changeset/pr-13064-upcoming-features-1762444425315.md b/packages/api-v4/.changeset/pr-13064-upcoming-features-1762444425315.md new file mode 100644 index 00000000000..6c93107913d --- /dev/null +++ b/packages/api-v4/.changeset/pr-13064-upcoming-features-1762444425315.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Upcoming Features +--- + +CloudPulse-Metrics: Update `CloudPulseServiceType` and `CapabilityServiceTypeMapping` at types.ts for new service - lke ([#13064](https://github.com/linode/manager/pull/13064)) diff --git a/packages/manager/.changeset/pr-13064-upcoming-features-1762444937800.md b/packages/manager/.changeset/pr-13064-upcoming-features-1762444937800.md new file mode 100644 index 00000000000..cc8dda8811e --- /dev/null +++ b/packages/manager/.changeset/pr-13064-upcoming-features-1762444937800.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +CloudPulse-Metrics: Update `FilterConfig.ts`, add lke service and the associated filters ([#13064](https://github.com/linode/manager/pull/13064))