From 6af50809a4da028f090df6172feb2ec436beab5b Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 19 Mar 2026 02:22:27 +0000 Subject: [PATCH] Centralize frontend policy summaries --- .../Infrastructure/ResourcePolicySummary.tsx | 72 +++++++------------ .../resourcePolicyPresentation.test.ts | 53 ++++++++++++++ .../src/utils/resourcePolicyPresentation.ts | 58 +++++++++++++++ 3 files changed, 138 insertions(+), 45 deletions(-) diff --git a/frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx b/frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx index 02c53bd06..ea5edfe95 100644 --- a/frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx +++ b/frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx @@ -1,12 +1,9 @@ import { For, Show, type Component } from 'solid-js'; import type { IntelligencePolicyPostureSummary } from '@/types/aiIntelligence'; import { - RESOURCE_POLICY_REDACTION_ORDER, - RESOURCE_POLICY_ROUTING_ORDER, - RESOURCE_POLICY_SENSITIVITY_ORDER, - getResourceRedactionHintLabel, - getResourceRoutingScopeLabel, - getResourceSensitivityLabel, + getResourcePolicyRedactionSummaries, + getResourcePolicyRoutingSummaries, + getResourcePolicySensitivitySummaries, } from '@/utils/resourcePolicyPresentation'; interface ResourcePolicySummaryProps { @@ -18,6 +15,9 @@ interface ResourcePolicySummaryProps { export const ResourcePolicySummary: Component = (props) => { const posture = () => props.posture; const className = () => props.class?.trim() ?? ''; + const sensitivitySummaries = () => getResourcePolicySensitivitySummaries(posture()); + const routingSummaries = () => getResourcePolicyRoutingSummaries(posture()); + const redactionSummaries = () => getResourcePolicyRedactionSummaries(posture()); return ( @@ -33,50 +33,32 @@ export const ResourcePolicySummary: Component = (pro
- - {(sensitivity) => { - const count = () => value().sensitivity_counts?.[sensitivity] ?? 0; - return ( -
-
- {getResourceSensitivityLabel(sensitivity)} -
-
{count()}
-
- ); - }} + + {(item) => ( +
+
{item.label}
+
{item.count}
+
+ )}
- - {(scope) => { - const count = () => value().routing_counts?.[scope] ?? 0; - return ( -
-
- {getResourceRoutingScopeLabel(scope)} -
-
{count()}
-
- ); - }} + + {(item) => ( +
+
{item.label}
+
{item.count}
+
+ )}
- (value().redaction_counts?.[hint] ?? 0) > 0, - )} - > + 0}>
- - {(hint) => { - const count = value().redaction_counts?.[hint] ?? 0; - if (!count) return null; - return ( - - {getResourceRedactionHintLabel(hint)} {count} - - ); - }} + + {(item) => ( + + {item.label} {item.count} + + )}
diff --git a/frontend-modern/src/utils/__tests__/resourcePolicyPresentation.test.ts b/frontend-modern/src/utils/__tests__/resourcePolicyPresentation.test.ts index 4d10193ae..010950828 100644 --- a/frontend-modern/src/utils/__tests__/resourcePolicyPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/resourcePolicyPresentation.test.ts @@ -4,6 +4,9 @@ import { RESOURCE_POLICY_REDACTION_ORDER, RESOURCE_POLICY_ROUTING_ORDER, RESOURCE_POLICY_SENSITIVITY_ORDER, + getResourcePolicyRedactionSummaries, + getResourcePolicyRoutingSummaries, + getResourcePolicySensitivitySummaries, getResourcePolicyRedactionLabels, getResourceRedactionHintLabel, getResourceRoutingScopeLabel, @@ -31,6 +34,56 @@ describe('resourcePolicyPresentation utils', () => { ).toEqual(['Hostname', 'IP Address']); }); + it('formats canonical policy count summaries', () => { + expect( + getResourcePolicySensitivitySummaries({ + total_resources: 3, + sensitivity_counts: { + public: 1, + internal: 2, + }, + routing_counts: {}, + }), + ).toEqual([ + { label: 'Public', count: 1 }, + { label: 'Internal', count: 2 }, + { label: 'Sensitive', count: 0 }, + { label: 'Restricted', count: 0 }, + ]); + + expect( + getResourcePolicyRoutingSummaries({ + total_resources: 3, + sensitivity_counts: {}, + routing_counts: { + 'cloud-summary': 1, + 'local-first': 2, + }, + }), + ).toEqual([ + { label: 'Cloud Summary', count: 1 }, + { label: 'Local First', count: 2 }, + { label: 'Local Only', count: 0 }, + ]); + + expect( + getResourcePolicyRedactionSummaries({ + total_resources: 3, + sensitivity_counts: {}, + routing_counts: { + 'cloud-summary': 1, + }, + redaction_counts: { + hostname: 2, + path: 1, + }, + }), + ).toEqual([ + { label: 'Hostname', count: 2 }, + { label: 'Path', count: 1 }, + ]); + }); + it('exports canonical policy ordering', () => { expect(RESOURCE_POLICY_SENSITIVITY_ORDER).toEqual([ 'public', diff --git a/frontend-modern/src/utils/resourcePolicyPresentation.ts b/frontend-modern/src/utils/resourcePolicyPresentation.ts index c7ac13934..d9ea2b2e2 100644 --- a/frontend-modern/src/utils/resourcePolicyPresentation.ts +++ b/frontend-modern/src/utils/resourcePolicyPresentation.ts @@ -4,6 +4,7 @@ import type { ResourceRoutingScope, ResourceSensitivity, } from '@/types/resource'; +import type { IntelligencePolicyPostureSummary } from '@/types/aiIntelligence'; type PolicyBadgePresentation = { label: string; @@ -11,6 +12,11 @@ type PolicyBadgePresentation = { className: string; }; +export type ResourcePolicyCountSummary = { + label: string; + count: number; +}; + export const RESOURCE_POLICY_SENSITIVITY_ORDER: ResourceSensitivity[] = [ 'public', 'internal', @@ -106,3 +112,55 @@ export const getResourceRedactionHintLabel = (hint?: ResourceRedactionHint): str export const getResourcePolicyRedactionLabels = (policy?: ResourcePolicy): string[] => (policy?.routing.redact ?? []).map((hint) => getResourceRedactionHintLabel(hint)); + +const buildCountSummaries = ( + counts: Partial> | undefined, + order: readonly T[], + labelFn: (value: T) => string, + includeZeroCounts: boolean, +): ResourcePolicyCountSummary[] => { + if (!counts) return []; + + const summaries: ResourcePolicyCountSummary[] = []; + for (const value of order) { + const count = counts[value] ?? 0; + if (!includeZeroCounts && count <= 0) { + continue; + } + summaries.push({ + label: labelFn(value), + count, + }); + } + return summaries; +}; + +export const getResourcePolicySensitivitySummaries = ( + posture?: IntelligencePolicyPostureSummary | null, +): ResourcePolicyCountSummary[] => + buildCountSummaries( + posture?.sensitivity_counts, + RESOURCE_POLICY_SENSITIVITY_ORDER, + getResourceSensitivityLabel, + true, + ); + +export const getResourcePolicyRoutingSummaries = ( + posture?: IntelligencePolicyPostureSummary | null, +): ResourcePolicyCountSummary[] => + buildCountSummaries( + posture?.routing_counts, + RESOURCE_POLICY_ROUTING_ORDER, + getResourceRoutingScopeLabel, + true, + ); + +export const getResourcePolicyRedactionSummaries = ( + posture?: IntelligencePolicyPostureSummary | null, +): ResourcePolicyCountSummary[] => + buildCountSummaries( + posture?.redaction_counts, + RESOURCE_POLICY_REDACTION_ORDER, + getResourceRedactionHintLabel, + false, + );