diff --git a/frontend-modern/scripts/canonical-platform-audit.mjs b/frontend-modern/scripts/canonical-platform-audit.mjs index 120933ab4..b0b9c43e7 100644 --- a/frontend-modern/scripts/canonical-platform-audit.mjs +++ b/frontend-modern/scripts/canonical-platform-audit.mjs @@ -31,6 +31,7 @@ const ALLOWLIST = new Set([ 'src/components/Storage/storageSourceOptions.ts', 'src/components/Storage/useStorageExpansionState.ts', 'src/components/Storage/useStorageResourceHighlight.ts', + 'src/features/alerts/useAlertGroupExpansion.ts', 'src/components/Infrastructure/resourceBadges.ts', 'src/components/Settings/reportingResourceTypes.ts', 'src/utils/canonicalResourceTypes.ts', diff --git a/frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx b/frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx index 460698490..2233b6298 100644 --- a/frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx +++ b/frontend-modern/src/features/alerts/AlertOverviewActiveAlertsSection.tsx @@ -12,6 +12,7 @@ import { } from '@/utils/alertOverviewPresentation'; import { AlertOverviewAlertCard } from './AlertOverviewAlertCard'; +import { useAlertGroupExpansion } from './useAlertGroupExpansion'; import type { AlertIncidentTimelineState } from './useAlertIncidentTimelineState'; import type { AlertOverviewState } from './useAlertOverviewState'; @@ -25,6 +26,7 @@ interface AlertOverviewActiveAlertsSectionProps { } export function AlertOverviewActiveAlertsSection(props: AlertOverviewActiveAlertsSectionProps) { + const { isGroupExpanded, toggleGroup } = useAlertGroupExpansion(); return (
@@ -116,13 +118,41 @@ export function AlertOverviewActiveAlertsSection(props: AlertOverviewActiveAlert {getAlertListEmptyState(props.showAcknowledged)}
- - {(alert) => ( - + + {(group) => ( +
+ + 0}> +
+ + +
+ + {(alert) => ( + + )} + +
+
+
+
+
)}
diff --git a/frontend-modern/src/features/alerts/useAlertGroupExpansion.ts b/frontend-modern/src/features/alerts/useAlertGroupExpansion.ts new file mode 100644 index 000000000..bb12909dd --- /dev/null +++ b/frontend-modern/src/features/alerts/useAlertGroupExpansion.ts @@ -0,0 +1,18 @@ +import { createSignal } from 'solid-js'; + +export function useAlertGroupExpansion() { + const [expandedGroups, setExpandedGroups] = createSignal>(new Set()); + + const toggleGroup = (key: string) => { + setExpandedGroups((prev) => { + const next = new Set(prev); + if (next.has(key)) next.delete(key); + else next.add(key); + return next; + }); + }; + + const isGroupExpanded = (key: string) => expandedGroups().has(key); + + return { isGroupExpanded, toggleGroup }; +} diff --git a/frontend-modern/src/features/alerts/useAlertOverviewState.ts b/frontend-modern/src/features/alerts/useAlertOverviewState.ts index d2852f85b..6a46eba96 100644 --- a/frontend-modern/src/features/alerts/useAlertOverviewState.ts +++ b/frontend-modern/src/features/alerts/useAlertOverviewState.ts @@ -5,6 +5,21 @@ import type { Alert } from '@/types/api'; import type { Override } from './types'; import { useAlertAcknowledgementState } from './useAlertAcknowledgementState'; +export interface AlertGroup { + key: string; + primary: Alert; + related: Alert[]; +} + +export function computeAlertGroupKey(alert: Alert): string { + const rid = alert.resourceId ?? ''; + const segments = rid.split('/'); + if (segments.length > 2) { + return segments.slice(0, -1).join('/'); + } + return rid; +} + export interface UseAlertOverviewStateProps { activeAlerts: Accessor>; overrides: Accessor; @@ -66,9 +81,29 @@ export function useAlertOverviewState(props: UseAlertOverviewStateProps) { }), ); + const groupedAlerts = createMemo(() => { + const sorted = filteredAlerts(); + const groups = new Map(); + for (const alert of sorted) { + const key = computeAlertGroupKey(alert); + const existing = groups.get(key); + if (existing) { + existing.push(alert); + } else { + groups.set(key, [alert]); + } + } + const result: AlertGroup[] = []; + for (const [key, alerts] of groups) { + result.push({ key, primary: alerts[0], related: alerts.slice(1) }); + } + return result; + }); + return { alertStats, filteredAlerts, + groupedAlerts, unacknowledgedAlerts, processingAlerts, bulkAckProcessing,