Centralize frontend policy summaries

This commit is contained in:
rcourtman
2026-03-19 02:22:27 +00:00
parent 401e2c0df4
commit 6af50809a4
3 changed files with 138 additions and 45 deletions
@@ -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<ResourcePolicySummaryProps> = (props) => {
const posture = () => props.posture;
const className = () => props.class?.trim() ?? '';
const sensitivitySummaries = () => getResourcePolicySensitivitySummaries(posture());
const routingSummaries = () => getResourcePolicyRoutingSummaries(posture());
const redactionSummaries = () => getResourcePolicyRedactionSummaries(posture());
return (
<Show when={posture()}>
@@ -33,50 +33,32 @@ export const ResourcePolicySummary: Component<ResourcePolicySummaryProps> = (pro
</div>
<dl class="mt-3 grid grid-cols-2 gap-2 text-sm">
<For each={RESOURCE_POLICY_SENSITIVITY_ORDER}>
{(sensitivity) => {
const count = () => value().sensitivity_counts?.[sensitivity] ?? 0;
return (
<div class="rounded-md bg-surface px-3 py-2">
<dt class="text-xs uppercase tracking-wide text-muted">
{getResourceSensitivityLabel(sensitivity)}
</dt>
<dd class="mt-1 font-semibold text-base-content">{count()}</dd>
</div>
);
}}
<For each={sensitivitySummaries()}>
{(item) => (
<div class="rounded-md bg-surface px-3 py-2">
<dt class="text-xs uppercase tracking-wide text-muted">{item.label}</dt>
<dd class="mt-1 font-semibold text-base-content">{item.count}</dd>
</div>
)}
</For>
<For each={RESOURCE_POLICY_ROUTING_ORDER}>
{(scope) => {
const count = () => value().routing_counts?.[scope] ?? 0;
return (
<div class="rounded-md bg-surface px-3 py-2">
<dt class="text-xs uppercase tracking-wide text-muted">
{getResourceRoutingScopeLabel(scope)}
</dt>
<dd class="mt-1 font-semibold text-base-content">{count()}</dd>
</div>
);
}}
<For each={routingSummaries()}>
{(item) => (
<div class="rounded-md bg-surface px-3 py-2">
<dt class="text-xs uppercase tracking-wide text-muted">{item.label}</dt>
<dd class="mt-1 font-semibold text-base-content">{item.count}</dd>
</div>
)}
</For>
</dl>
<Show
when={RESOURCE_POLICY_REDACTION_ORDER.some(
(hint) => (value().redaction_counts?.[hint] ?? 0) > 0,
)}
>
<Show when={redactionSummaries().length > 0}>
<div class="mt-2 flex flex-wrap gap-1">
<For each={RESOURCE_POLICY_REDACTION_ORDER}>
{(hint) => {
const count = value().redaction_counts?.[hint] ?? 0;
if (!count) return null;
return (
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
{getResourceRedactionHintLabel(hint)} {count}
</span>
);
}}
<For each={redactionSummaries()}>
{(item) => (
<span class="inline-flex items-center rounded bg-surface-alt px-1.5 py-0.5 text-[10px]">
{item.label} {item.count}
</span>
)}
</For>
</div>
</Show>
@@ -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',
@@ -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 = <T extends string>(
counts: Partial<Record<T, number>> | 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,
);