Converge platform alert severity filters

This commit is contained in:
rcourtman
2026-06-13 08:32:12 +01:00
parent 7239f60748
commit ecf0cefdf3
10 changed files with 240 additions and 80 deletions
@@ -2425,6 +2425,12 @@ label formatting and severity-bucket-to-status-indicator mapping. Docker,
Kubernetes, TrueNAS, vSphere, and future platform alert tables must compose
`AlertSeverityBadge` and `AlertSeverityDot`; they must not recreate
`severityVariant`, `severityTextClass`, or severity badge spans locally.
Platform alert severity filters follow the same shared-template rule.
`frontend-modern/src/features/platformPage/platformAlertSeverityFilterOptions.tsx`
owns the canonical All/Critical/Warning/Info option labels, tones, and leading
dots for platform alert table toolbars. Platform alert tables must call
`getPlatformAlertSeverityFilterOptions` instead of declaring local severity
filter arrays or calling `filterChipStatusDot` directly for those filters.
Read-only metadata badges follow the same primitive-owned shell rule.
`frontend-modern/src/components/shared/MetadataBadge.tsx` owns filled and
outlined appearances, compact sizing, shape, typed tone vocabulary, fit
@@ -1784,7 +1784,9 @@ severity dots, badges, and severity label formatting must compose
`AlertSeverityDot`, `AlertSeverityBadge`, and
`formatAlertSeverityLabel` from the frontend-primitives-owned shared path
instead of restoring table-local `severityVariant`, `severityTextClass`, or
severity-label helpers,
severity-label helpers, and their severity toolbar filters must use
`getPlatformAlertSeverityFilterOptions` instead of restoring local
All/Critical/Warning/Info option arrays,
and local operator identity labels now split from governed detail summaries:
infrastructure tables, selectors, links, and drawer headings must preserve the
canonical local instance identity (`displayName`, canonical display name,
@@ -766,6 +766,59 @@
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-severity-filter-options",
"category": "filter-options",
"summary": "Platform alert tables must get All/Critical/Warning/Info severity filter labels, tones, and leading dots from the shared platform alert severity filter-options primitive instead of recreating local option arrays.",
"canonical": {
"path": "src/features/platformPage/platformAlertSeverityFilterOptions.tsx",
"export": "getPlatformAlertSeverityFilterOptions"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerAlertsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesAlertsTable.tsx" },
{ "path": "src/features/truenas/TrueNASAlertsTable.tsx" },
{ "path": "src/features/vmware/VsphereAlertsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerAlertsTable.tsx",
"patterns": [
"filterChipStatusDot(",
"value: 'critical'",
"value: 'warning'",
"value: 'info'"
]
},
{
"path": "src/features/kubernetes/KubernetesAlertsTable.tsx",
"patterns": [
"filterChipStatusDot(",
"value: 'critical'",
"value: 'warning'",
"value: 'info'"
]
},
{
"path": "src/features/truenas/TrueNASAlertsTable.tsx",
"patterns": ["value: 'critical'", "value: 'warning'", "value: 'info'"]
},
{
"path": "src/features/vmware/VsphereAlertsTable.tsx",
"patterns": [
"filterChipStatusDot(",
"value: 'critical'",
"value: 'warning'",
"value: 'info'"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/platformAlertSeverityFilterOptions.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "metadata-badge-shell",
"category": "metadata-badge",
@@ -2451,6 +2504,31 @@
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-severity-filter-options-required",
"category": "filter-options",
"summary": "Feature alert tables with severityBucket-backed PlatformTableToolbar status filters must compose the shared platform alert severity filter-options primitive.",
"canonical": {
"path": "src/features/platformPage/platformAlertSeverityFilterOptions.tsx",
"export": "getPlatformAlertSeverityFilterOptions"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"triggerPatterns": ["severityBucket", "PlatformTableToolbar", "statusOptions"],
"requiredPatterns": ["getPlatformAlertSeverityFilterOptions"],
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/platformAlertSeverityFilterOptions.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-row-detail-toggle-inline-detail",
"category": "row-disclosure",
@@ -133,6 +133,7 @@ import kubernetesPageSurfaceSource from '@/features/kubernetes/KubernetesPageSur
import proxmoxPageSurfaceSource from '@/features/proxmox/ProxmoxPageSurface.tsx?raw';
import standalonePageSurfaceSource from '@/features/standalone/StandalonePageSurface.tsx?raw';
import sharedPlatformPageSource from '@/features/platformPage/sharedPlatformPage.tsx?raw';
import platformAlertSeverityFilterOptionsSource from '@/features/platformPage/platformAlertSeverityFilterOptions.tsx?raw';
import platformResourceDetailTableRowSource from '@/features/platformPage/PlatformResourceDetailTableRow.tsx?raw';
import truenasPageSurfaceSource from '@/features/truenas/TrueNASPageSurface.tsx?raw';
import truenasProtectionTableSource from '@/features/truenas/TrueNASProtectionTable.tsx?raw';
@@ -1672,7 +1673,7 @@ describe('shared primitive guardrails', () => {
);
});
it('keeps platform alert severity indicators on the shared severity badge primitive', () => {
it('keeps platform alert severity indicators and filters on shared alert severity primitives', () => {
const registry = JSON.parse(sharedTemplateRegistrySource) as {
rules?: Array<{
id: string;
@@ -1701,12 +1702,18 @@ describe('shared primitive guardrails', () => {
const registeredRule = registry.rules?.find(
(rule) => rule.id === 'platform-alert-severity-indicator-shell',
);
const filterOptionsRule = registry.rules?.find(
(rule) => rule.id === 'platform-alert-severity-filter-options',
);
const localHelperGuard = registry.patternGuards?.find(
(guard) => guard.id === 'platform-alert-severity-local-helper',
);
const requiredGuard = registry.requiredPatternGuards?.find(
(guard) => guard.id === 'platform-alert-severity-indicator-required',
);
const requiredFilterOptionsGuard = registry.requiredPatternGuards?.find(
(guard) => guard.id === 'platform-alert-severity-filter-options-required',
);
const alertTableConsumerPaths = [
'src/features/docker/DockerAlertsTable.tsx',
'src/features/kubernetes/KubernetesAlertsTable.tsx',
@@ -1719,12 +1726,52 @@ describe('shared primitive guardrails', () => {
expect(registeredRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual(
alertTableConsumerPaths,
);
expect(filterOptionsRule?.canonical?.path).toBe(
'src/features/platformPage/platformAlertSeverityFilterOptions.tsx',
);
expect(filterOptionsRule?.canonical?.export).toBe('getPlatformAlertSeverityFilterOptions');
expect(filterOptionsRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual(
alertTableConsumerPaths,
);
expect(registeredRule?.forbiddenPatterns).toEqual(
alertTableConsumerPaths.map((path) => ({
path,
patterns: ['severityVariant', 'severityTextClass'],
})),
);
expect(filterOptionsRule?.forbiddenPatterns).toEqual([
{
path: 'src/features/docker/DockerAlertsTable.tsx',
patterns: [
'filterChipStatusDot(',
"value: 'critical'",
"value: 'warning'",
"value: 'info'",
],
},
{
path: 'src/features/kubernetes/KubernetesAlertsTable.tsx',
patterns: [
'filterChipStatusDot(',
"value: 'critical'",
"value: 'warning'",
"value: 'info'",
],
},
{
path: 'src/features/truenas/TrueNASAlertsTable.tsx',
patterns: ["value: 'critical'", "value: 'warning'", "value: 'info'"],
},
{
path: 'src/features/vmware/VsphereAlertsTable.tsx',
patterns: [
'filterChipStatusDot(',
"value: 'critical'",
"value: 'warning'",
"value: 'info'",
],
},
]);
expect(localHelperGuard?.canonical?.path).toBe('src/components/shared/AlertSeverityBadge.tsx');
expect(localHelperGuard?.canonical?.export).toBe('AlertSeverityBadge');
expect(localHelperGuard?.allPatterns).toEqual(['severityVariant', 'severityTextClass']);
@@ -1746,10 +1793,30 @@ describe('shared primitive guardrails', () => {
expect(requiredGuard?.requiredPatterns).toEqual(['AlertSeverityBadge', 'AlertSeverityDot']);
expect(requiredGuard?.allowedPaths ?? []).toHaveLength(0);
expect(requiredGuard?.ignoredPaths ?? []).toHaveLength(0);
expect(requiredFilterOptionsGuard?.canonical?.path).toBe(
'src/features/platformPage/platformAlertSeverityFilterOptions.tsx',
);
expect(requiredFilterOptionsGuard?.canonical?.export).toBe(
'getPlatformAlertSeverityFilterOptions',
);
expect(requiredFilterOptionsGuard?.triggerPatterns).toEqual([
'severityBucket',
'PlatformTableToolbar',
'statusOptions',
]);
expect(requiredFilterOptionsGuard?.requiredPatterns).toEqual([
'getPlatformAlertSeverityFilterOptions',
]);
expect(requiredFilterOptionsGuard?.allowedPaths ?? []).toHaveLength(0);
expect(requiredFilterOptionsGuard?.ignoredPaths ?? []).toHaveLength(0);
expect(alertSeverityBadgeSource).toContain('StatusIndicatorBadge');
expect(alertSeverityBadgeSource).toContain('StatusDot');
expect(alertSeverityBadgeSource).toContain('getAlertSeverityIndicator');
expect(platformAlertSeverityFilterOptionsSource).toContain(
'getPlatformAlertSeverityFilterOptions',
);
expect(platformAlertSeverityFilterOptionsSource).toContain('filterChipStatusDot');
expect(alertSeverityPresentationSource).toContain('getAlertSeverityIndicatorVariant');
expect(alertSeverityPresentationSource).toContain('formatAlertSeverityLabel');
@@ -1762,8 +1829,13 @@ describe('shared primitive guardrails', () => {
expect(source).toContain('AlertSeverityBadge');
expect(source).toContain('AlertSeverityDot');
expect(source).toContain('formatAlertSeverityLabel');
expect(source).toContain('getPlatformAlertSeverityFilterOptions');
expect(source).not.toContain('severityVariant');
expect(source).not.toContain('severityTextClass');
expect(source).not.toContain('filterChipStatusDot(');
expect(source).not.toContain("value: 'critical'");
expect(source).not.toContain("value: 'warning'");
expect(source).not.toContain("value: 'info'");
expect(source).not.toContain('text-red-700 dark:text-red-300');
expect(source).not.toContain('text-amber-700 dark:text-amber-300');
}
@@ -10,14 +10,12 @@ import {
import { AlertSeverityBadge, AlertSeverityDot } from '@/components/shared/AlertSeverityBadge';
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
import { filterChipStatusDot } from '@/components/shared/FilterBar';
import {
PlatformTableEmptyState,
PlatformTableToolbar,
createPlatformTableFilterState,
getPlatformTableCellClassForKind,
getPlatformTableHeadClassForKind,
type PlatformTableFilterOption,
PlatformTableShell,
} from '@/features/platformPage/sharedPlatformPage';
import {
@@ -25,6 +23,7 @@ import {
createPlatformResourceDetailState,
getPlatformResourceDetailRowClass,
} from '@/features/platformPage/PlatformResourceDetailTableRow';
import { getPlatformAlertSeverityFilterOptions } from '@/features/platformPage/platformAlertSeverityFilterOptions';
import type { ResourceType } from '@/types/resource';
import { getAlertFilteredEmptyState } from '@/utils/alertOverviewPresentation';
import { formatAlertSeverityLabel } from '@/utils/alertSeverityPresentation';
@@ -34,27 +33,8 @@ import {
type DockerIncidentSeverityFilter,
} from './dockerPageModel';
const DOCKER_INCIDENT_STATUS_OPTIONS: PlatformTableFilterOption<DockerIncidentSeverityFilter>[] = [
{ value: 'all', label: 'All' },
{
value: 'critical',
label: 'Critical',
tone: 'danger',
leading: filterChipStatusDot('bg-red-500'),
},
{
value: 'warning',
label: 'Warning',
tone: 'warning',
leading: filterChipStatusDot('bg-amber-500'),
},
{
value: 'info',
label: 'Info',
tone: 'success',
leading: filterChipStatusDot('bg-emerald-500'),
},
];
const DOCKER_INCIDENT_STATUS_OPTIONS =
getPlatformAlertSeverityFilterOptions<DockerIncidentSeverityFilter>();
const formatResourceType = (type: ResourceType): string => {
switch (type) {
@@ -10,14 +10,12 @@ import {
import { AlertSeverityBadge, AlertSeverityDot } from '@/components/shared/AlertSeverityBadge';
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
import { filterChipStatusDot } from '@/components/shared/FilterBar';
import {
PlatformTableEmptyState,
PlatformTableToolbar,
createPlatformTableFilterState,
getPlatformTableCellClassForKind,
getPlatformTableHeadClassForKind,
type PlatformTableFilterOption,
PlatformTableShell,
} from '@/features/platformPage/sharedPlatformPage';
import {
@@ -25,6 +23,7 @@ import {
createPlatformResourceDetailState,
getPlatformResourceDetailRowClass,
} from '@/features/platformPage/PlatformResourceDetailTableRow';
import { getPlatformAlertSeverityFilterOptions } from '@/features/platformPage/platformAlertSeverityFilterOptions';
import type { ResourceType } from '@/types/resource';
import { getAlertFilteredEmptyState } from '@/utils/alertOverviewPresentation';
import { formatAlertSeverityLabel } from '@/utils/alertSeverityPresentation';
@@ -34,28 +33,8 @@ import {
type KubernetesIncidentSeverityFilter,
} from './kubernetesPageModel';
const KUBERNETES_INCIDENT_STATUS_OPTIONS: PlatformTableFilterOption<KubernetesIncidentSeverityFilter>[] =
[
{ value: 'all', label: 'All' },
{
value: 'critical',
label: 'Critical',
tone: 'danger',
leading: filterChipStatusDot('bg-red-500'),
},
{
value: 'warning',
label: 'Warning',
tone: 'warning',
leading: filterChipStatusDot('bg-amber-500'),
},
{
value: 'info',
label: 'Info',
tone: 'success',
leading: filterChipStatusDot('bg-emerald-500'),
},
];
const KUBERNETES_INCIDENT_STATUS_OPTIONS =
getPlatformAlertSeverityFilterOptions<KubernetesIncidentSeverityFilter>();
const formatResourceType = (type: ResourceType): string => {
switch (type) {
@@ -0,0 +1,35 @@
import { cleanup, render } from '@solidjs/testing-library';
import { afterEach, describe, expect, it } from 'vitest';
import { For } from 'solid-js';
import { getPlatformAlertSeverityFilterOptions } from '../platformAlertSeverityFilterOptions';
afterEach(cleanup);
describe('getPlatformAlertSeverityFilterOptions', () => {
it('returns the canonical platform alert severity filter labels and tones', () => {
const options = getPlatformAlertSeverityFilterOptions();
expect(options.map(({ value, label, tone }) => ({ value, label, tone }))).toEqual([
{ value: 'all', label: 'All', tone: undefined },
{ value: 'critical', label: 'Critical', tone: 'danger' },
{ value: 'warning', label: 'Warning', tone: 'warning' },
{ value: 'info', label: 'Info', tone: 'success' },
]);
});
it('renders the canonical severity filter leading dots', () => {
const options = getPlatformAlertSeverityFilterOptions();
render(() => (
<div>
<For each={options}>{(option) => option.leading}</For>
</div>
));
const dots = Array.from(document.querySelectorAll('span[aria-hidden="true"]'));
expect(dots).toHaveLength(3);
expect(dots[0]).toHaveClass('bg-red-500');
expect(dots[1]).toHaveClass('bg-amber-500');
expect(dots[2]).toHaveClass('bg-emerald-500');
});
});
@@ -0,0 +1,33 @@
import { filterChipStatusDot } from '@/components/shared/FilterBar';
import type { PlatformTableFilterOption } from '@/features/platformPage/sharedPlatformPage';
export type PlatformAlertSeverityFilterValue = 'all' | 'critical' | 'warning' | 'info';
const PLATFORM_ALERT_SEVERITY_FILTER_OPTIONS: PlatformTableFilterOption<PlatformAlertSeverityFilterValue>[] =
[
{ value: 'all', label: 'All' },
{
value: 'critical',
label: 'Critical',
tone: 'danger',
leading: filterChipStatusDot('bg-red-500'),
},
{
value: 'warning',
label: 'Warning',
tone: 'warning',
leading: filterChipStatusDot('bg-amber-500'),
},
{
value: 'info',
label: 'Info',
tone: 'success',
leading: filterChipStatusDot('bg-emerald-500'),
},
];
export function getPlatformAlertSeverityFilterOptions<
TFilter extends PlatformAlertSeverityFilterValue,
>(): PlatformTableFilterOption<TFilter>[] {
return PLATFORM_ALERT_SEVERITY_FILTER_OPTIONS as PlatformTableFilterOption<TFilter>[];
}
@@ -8,7 +8,6 @@ import {
createPlatformTableFilterState,
getPlatformTableCellClassForKind,
getPlatformTableHeadClassForKind,
type PlatformTableFilterOption,
PlatformTableShell,
} from '@/features/platformPage/sharedPlatformPage';
import {
@@ -16,6 +15,7 @@ import {
createPlatformResourceDetailState,
getPlatformResourceDetailRowClass,
} from '@/features/platformPage/PlatformResourceDetailTableRow';
import { getPlatformAlertSeverityFilterOptions } from '@/features/platformPage/platformAlertSeverityFilterOptions';
import {
filterTrueNASIncidents,
type TrueNASIncidentRow,
@@ -33,13 +33,8 @@ import type { Resource, ResourceType } from '@/types/resource';
import { getAlertFilteredEmptyState } from '@/utils/alertOverviewPresentation';
import { formatAlertSeverityLabel } from '@/utils/alertSeverityPresentation';
const TRUENAS_INCIDENT_STATUS_OPTIONS: PlatformTableFilterOption<TrueNASIncidentSeverityFilter>[] =
[
{ value: 'all', label: 'All' },
{ value: 'critical', label: 'Critical', tone: 'danger' },
{ value: 'warning', label: 'Warning', tone: 'warning' },
{ value: 'info', label: 'Info', tone: 'success' },
];
const TRUENAS_INCIDENT_STATUS_OPTIONS =
getPlatformAlertSeverityFilterOptions<TrueNASIncidentSeverityFilter>();
const formatResourceType = (type: ResourceType): string => {
switch (type) {
@@ -10,14 +10,12 @@ import {
import { AlertSeverityBadge, AlertSeverityDot } from '@/components/shared/AlertSeverityBadge';
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
import { filterChipStatusDot } from '@/components/shared/FilterBar';
import {
PlatformTableEmptyState,
PlatformTableToolbar,
createPlatformTableFilterState,
getPlatformTableCellClassForKind,
getPlatformTableHeadClassForKind,
type PlatformTableFilterOption,
PlatformTableShell,
} from '@/features/platformPage/sharedPlatformPage';
import {
@@ -25,6 +23,7 @@ import {
createPlatformResourceDetailState,
getPlatformResourceDetailRowClass,
} from '@/features/platformPage/PlatformResourceDetailTableRow';
import { getPlatformAlertSeverityFilterOptions } from '@/features/platformPage/platformAlertSeverityFilterOptions';
import type { ResourceType } from '@/types/resource';
import { getAlertFilteredEmptyState } from '@/utils/alertOverviewPresentation';
import { formatAlertSeverityLabel } from '@/utils/alertSeverityPresentation';
@@ -34,27 +33,8 @@ import {
type VmwareIncidentSeverityFilter,
} from './vmwarePageModel';
const VSPHERE_INCIDENT_STATUS_OPTIONS: PlatformTableFilterOption<VmwareIncidentSeverityFilter>[] = [
{ value: 'all', label: 'All' },
{
value: 'critical',
label: 'Critical',
tone: 'danger',
leading: filterChipStatusDot('bg-red-500'),
},
{
value: 'warning',
label: 'Warning',
tone: 'warning',
leading: filterChipStatusDot('bg-amber-500'),
},
{
value: 'info',
label: 'Info',
tone: 'success',
leading: filterChipStatusDot('bg-emerald-500'),
},
];
const VSPHERE_INCIDENT_STATUS_OPTIONS =
getPlatformAlertSeverityFilterOptions<VmwareIncidentSeverityFilter>();
const formatResourceType = (type: ResourceType): string => {
switch (type) {