From 96daee1173cdc3cadd9dfbc3f54b2ed4c2af675a Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 19 Mar 2026 20:10:37 +0000 Subject: [PATCH] Move Kubernetes context helper into agent resources --- .../subsystems/performance-and-scalability.md | 7 ++-- .../internal/subsystems/unified-resources.md | 6 +-- .../utils/__tests__/agentResources.test.ts | 13 +++++++ frontend-modern/src/utils/agentResources.ts | 38 ++++++++++++++++--- frontend-modern/src/utils/resourceIdentity.ts | 15 ++------ 5 files changed, 55 insertions(+), 24 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index 9cc403b67..c6446f683 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -140,9 +140,10 @@ also routes its Kubernetes-cluster fallback through the same preferred resource display contract, so navigation context does not leak raw `displayName` values for governed clusters. That same workloads-link path and the dashboard workload projection now also -share the canonical Kubernetes context prefix helper, so route labels and pod -grouping keep using the same cluster-context source of truth instead of -rebuilding the `clusterName`/`context`/`clusterId` prefix locally. +share the canonical Kubernetes context prefix helper in the shared +agent-resource layer, so route labels and pod grouping keep using the same +cluster-context source of truth instead of rebuilding the +`clusterName`/`context`/`clusterId` prefix locally. The drawer's Kubernetes namespace/deployment tabs use the canonical cluster-name helper for fetch keys, so the visible navigation label stays separate from the backend cluster lookup contract. diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index d3ee8b8da..9cc5c6fce 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -297,9 +297,9 @@ The drawer's discovery mapper also reuses that helper for pod fallback agent IDs, so the resource-detail path and the dashboard path stay aligned on the same cluster-name source of truth. The dashboard workload projection and workloads-link route helpers also share -the same Kubernetes context prefix helper, so pod grouping and cluster -navigation keep the same cluster-context prefix before any surface-specific -display fallback is applied. +the same Kubernetes context prefix helper in the shared agent-resource +layer, so pod grouping and cluster navigation keep the same cluster-context +prefix before any surface-specific display fallback is applied. The unified-resource projection also reuses that same prefix helper for projected Kubernetes `clusterId`, so the shared resource store stays aligned with the dashboard and detail surfaces on the same canonical cluster-context diff --git a/frontend-modern/src/utils/__tests__/agentResources.test.ts b/frontend-modern/src/utils/__tests__/agentResources.test.ts index c6f618759..319e9afec 100644 --- a/frontend-modern/src/utils/__tests__/agentResources.test.ts +++ b/frontend-modern/src/utils/__tests__/agentResources.test.ts @@ -131,6 +131,19 @@ describe('agentResources', () => { }), ), ).toBe('cluster-1'); + + expect( + getActionableKubernetesClusterIdFromResource( + makeResource({ + type: 'k8s-cluster', + kubernetes: { + clusterName: 'cluster-a', + context: 'cluster-context', + clusterId: 'cluster-a-id', + }, + }), + ), + ).toBe('cluster-a'); }); it('detects docker workloads scope from explicit docker facets instead of source lists', () => { diff --git a/frontend-modern/src/utils/agentResources.ts b/frontend-modern/src/utils/agentResources.ts index a6069f979..90384d6ce 100644 --- a/frontend-modern/src/utils/agentResources.ts +++ b/frontend-modern/src/utils/agentResources.ts @@ -19,6 +19,23 @@ const AGENT_PROFILE_ASSIGNABLE_TYPES = new Set([ const asRecord = (value: unknown): Record | undefined => value && typeof value === 'object' ? (value as Record) : undefined; +type KubernetesContextLike = { + clusterId?: string | null; + name?: string | null; + kubernetes?: { + clusterName?: string | null; + context?: string | null; + clusterId?: string | null; + } | null; + platformData?: { + kubernetes?: { + clusterName?: string | null; + context?: string | null; + clusterId?: string | null; + } | null; + } | null; +}; + export const getPlatformDataRecord = (resource: Resource): Record | undefined => resource.platformData ? (resource.platformData as Record) : undefined; @@ -80,21 +97,30 @@ export const hasDockerWorkloadsScope = (resource: Resource): boolean => { export const getActionableKubernetesClusterIdFromResource = ( resource: Resource, ): string | undefined => { - const platformData = getPlatformDataRecord(resource); - const kubernetes = asRecord(platformData?.kubernetes); - if (resource.discoveryTarget?.resourceType === 'pod' && resource.discoveryTarget.resourceId) { return resource.discoveryTarget.resourceId; } return ( - asTrimmedString(resource.kubernetes?.clusterId) || - asTrimmedString(kubernetes?.clusterId) || - asTrimmedString(platformData?.clusterId) || + getPreferredResourceKubernetesContext(resource) || (resource.type === 'k8s-cluster' ? resource.id : undefined) ); }; +export const getPreferredResourceKubernetesContext = ( + resource: KubernetesContextLike, +): string | undefined => { + return ( + asTrimmedString(resource.kubernetes?.clusterName) || + asTrimmedString(resource.kubernetes?.context) || + asTrimmedString(resource.kubernetes?.clusterId) || + asTrimmedString(resource.platformData?.kubernetes?.clusterName) || + asTrimmedString(resource.platformData?.kubernetes?.context) || + asTrimmedString(resource.platformData?.kubernetes?.clusterId) || + asTrimmedString(resource.clusterId) + ); +}; + export const getMetricsChartKeyCandidatesFromResource = (resource: Resource): string[] => { const candidates = [ asTrimmedString(resource.metricsTarget?.resourceId), diff --git a/frontend-modern/src/utils/resourceIdentity.ts b/frontend-modern/src/utils/resourceIdentity.ts index af108cd0f..4846d629b 100644 --- a/frontend-modern/src/utils/resourceIdentity.ts +++ b/frontend-modern/src/utils/resourceIdentity.ts @@ -10,6 +10,7 @@ import { getActionableAgentIdFromResource, getActionableDockerRuntimeIdFromResource, getActionableKubernetesClusterIdFromResource, + getPreferredResourceKubernetesContext, getPlatformAgentRecord, getPlatformDataRecord, } from '@/utils/agentResources'; @@ -318,6 +319,8 @@ export const getPreferredResourceHostname = (resource: Resource): string | undef ); }; +export { getPreferredResourceKubernetesContext }; + export const getPreferredResourceClusterName = ( resource: ResourceClusterNameLike, ): string | undefined => @@ -332,18 +335,6 @@ export const getPreferredResourceClusterName = ( ); })(); -export const getPreferredResourceKubernetesContext = ( - resource: ResourceClusterNameLike, -): string | undefined => { - const kubernetes = resource.kubernetes || resource.platformData?.kubernetes; - return ( - asTrimmedString(kubernetes?.clusterName) || - asTrimmedString(kubernetes?.context) || - asTrimmedString(kubernetes?.clusterId) || - asTrimmedString(resource.clusterId) - ); -}; - export const getPreferredResourceDisplayName = (resource: Resource): string => requiresGovernedResourceDisplay(resource.policy) ? getResourcePolicyDisplayLabel(resource)