Extract alerts configuration surface

This commit is contained in:
rcourtman 2026-03-20 15:48:17 +00:00
parent 5f0f6119c8
commit 4ff36e2b90
6 changed files with 1555 additions and 1616 deletions

View file

@ -152,11 +152,19 @@ The alerts page shell in `frontend-modern/src/pages/Alerts.tsx` must now keep
destinations, history, schedule, and thresholds rendering feature-owned under
`frontend-modern/src/features/alerts/tabs/`. New alert tab surfaces should be
extracted as feature modules instead of remaining page-local function blocks,
so the page owns navigation/save orchestration while tab files own their
so the page owns navigation and cross-surface routing while tab files own their
runtime presentation, tab-local interaction logic, and any history-table
presentation or thresholds-table adapter logic that does not belong in a shared
primitive.
Alert configuration load/save state, notification config reloads, and threshold
override normalization now route through
`frontend-modern/src/features/alerts/AlertsConfigurationSurface.tsx` instead of
living inline in `frontend-modern/src/pages/Alerts.tsx`. The page shell owns
navigation, activation chrome, and cross-surface routing; the configuration
surface owns the alert config controller and composes the destinations,
schedule, and thresholds tabs beneath that feature boundary.
Alert filter metadata and grouped header consumers must also preserve the
canonical `agent` and `node` header boundary when reusing shared filter
primitives. Frontend alert tables may not drift back to ad hoc host-key

View file

@ -220,8 +220,8 @@ Pulse resource, then layer on additional context.
The settings shell is now also a governed frontend primitive boundary.
The alerts page shell now follows that same page-shell rule for feature tabs:
`frontend-modern/src/pages/Alerts.tsx` owns navigation, load/save
orchestration, and cross-tab state, while feature-owned tab surfaces such as
`frontend-modern/src/pages/Alerts.tsx` owns navigation and cross-surface
routing, while feature-owned tab surfaces such as
`frontend-modern/src/features/alerts/tabs/DestinationsTab.tsx` and
`frontend-modern/src/features/alerts/tabs/HistoryTab.tsx` plus
`frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx` and
@ -231,6 +231,15 @@ continue by extracting page-local tab blocks into feature modules rather than
expanding the top-level page file again, and history-table behavior or
thresholds-table adapter logic should stay feature-owned unless it graduates
into a shared primitive used by more than one alert surface.
The alerts page now also applies the same shell-versus-feature rule to
configuration orchestration. `frontend-modern/src/pages/Alerts.tsx` is the page
shell, while `frontend-modern/src/features/alerts/AlertsConfigurationSurface.tsx`
owns alert config load/save behavior, notification-config reloads, defaults,
and threshold-override normalization for the destinations, schedule, and
thresholds tabs. Future cleanup should continue by moving page-local config
control flow into that feature surface or a narrower shared primitive, not back
into the top-level page shell.
Top-level settings surfaces must route through `Settings.tsx`,
`SettingsPageShell.tsx`, and
`frontend-modern/src/components/shared/SettingsPanel.tsx` instead of

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest';
import alertsPageSource from '@/pages/Alerts.tsx?raw';
import alertsConfigurationSurfaceSource from '@/features/alerts/AlertsConfigurationSurface.tsx?raw';
import alertDestinationsTabSource from '@/features/alerts/tabs/DestinationsTab.tsx?raw';
import alertHistoryTabSource from '@/features/alerts/tabs/HistoryTab.tsx?raw';
import alertScheduleTabSource from '@/features/alerts/tabs/ScheduleTab.tsx?raw';
@ -154,23 +155,32 @@ describe('tab path helpers', () => {
expect(tabFromPath('/alerts/summary', custom)).toBe('overview');
});
it('keeps destinations, history, schedule, and thresholds tabs feature-owned', () => {
it('keeps alerts configuration owned by a feature surface instead of the page shell', () => {
expect(alertsPageSource).toContain(
"import { DestinationsTab } from '@/features/alerts/tabs/DestinationsTab';",
"import { AlertsConfigurationSurface } from '@/features/alerts/AlertsConfigurationSurface';",
);
expect(alertsPageSource).toContain(
"import { HistoryTab } from '@/features/alerts/tabs/HistoryTab';",
);
expect(alertsPageSource).toContain(
"import { ScheduleTab } from '@/features/alerts/tabs/ScheduleTab';",
expect(alertsPageSource).not.toContain('const loadAlertConfiguration = async');
expect(alertsPageSource).not.toContain('const FACTORY_GUEST_DEFAULTS =');
expect(alertsConfigurationSurfaceSource).toContain(
"import { DestinationsTab } from './tabs/DestinationsTab';",
);
expect(alertsPageSource).toContain(
"import { ThresholdsTab } from '@/features/alerts/tabs/ThresholdsTab';",
expect(alertsConfigurationSurfaceSource).toContain(
"import { ScheduleTab } from './tabs/ScheduleTab';",
);
expect(alertsConfigurationSurfaceSource).toContain(
"import { ThresholdsTab } from './tabs/ThresholdsTab';",
);
expect(alertsConfigurationSurfaceSource).toContain('AlertsAPI.getConfig');
expect(alertsConfigurationSurfaceSource).toContain('NotificationsAPI.getEmailConfig');
expect(alertsConfigurationSurfaceSource).toContain('NotificationsAPI.updateEmailConfig');
expect(alertsConfigurationSurfaceSource).toContain("eventBus.on('org_switched'");
expect(alertsPageSource).toContain(
"import { HistoryTab } from '@/features/alerts/tabs/HistoryTab';",
);
expect(alertsPageSource).not.toContain('function DestinationsTab(');
expect(alertsPageSource).not.toContain('function HistoryTab(');
expect(alertsPageSource).not.toContain('function ScheduleTab(');
expect(alertsPageSource).not.toContain('function ThresholdsTab(');
expect(alertDestinationsTabSource).toContain('NotificationsAPI.getWebhooks');
expect(alertHistoryTabSource).toContain('AlertsAPI.getHistory');
expect(alertHistoryTabSource).toContain('IncidentTimelinePanel');

View file

@ -223,6 +223,7 @@ import alertOverviewPresentationSource from '@/utils/alertOverviewPresentation.t
import alertResourceTablePresentationSource from '@/utils/alertResourceTablePresentation.ts?raw';
import alertWebhookPresentationSource from '@/utils/alertWebhookPresentation.ts?raw';
import alertOverviewTabSource from '@/features/alerts/OverviewTab.tsx?raw';
import alertsConfigurationSurfaceSource from '@/features/alerts/AlertsConfigurationSurface.tsx?raw';
import alertDestinationsTabSource from '@/features/alerts/tabs/DestinationsTab.tsx?raw';
import alertHistoryTabSource from '@/features/alerts/tabs/HistoryTab.tsx?raw';
import alertScheduleTabSource from '@/features/alerts/tabs/ScheduleTab.tsx?raw';
@ -2188,7 +2189,9 @@ describe('frontend resource type boundaries', () => {
expect(deployStatusPresentationSource).toContain('export const getDeployStatusPresentation');
expect(alertHistoryTabSource).toContain('getAlertIncidentStatusPresentation');
expect(alertHistoryTabSource).toContain('getAlertIncidentLevelBadgeClass');
expect(alertsPageSource).toContain('getAlertDestinationsConfigLoadError');
expect(alertsPageSource).toContain("import { AlertsConfigurationSurface } from '@/features/alerts/AlertsConfigurationSurface';");
expect(alertsPageSource).not.toContain('getAlertDestinationsConfigLoadError');
expect(alertsConfigurationSurfaceSource).toContain('getAlertDestinationsConfigLoadError');
expect(alertDestinationsTabSource).toContain('getAlertDestinationsWebhookLoadError');
expect(alertDestinationsTabSource).toContain('getAlertDestinationsLoadErrorBanner');
expect(alertDestinationsTabSource).toContain('getAlertDestinationsAppriseTargetsHelp');
@ -2249,7 +2252,8 @@ describe('frontend resource type boundaries', () => {
expect(alertsPageSource).toContain('getAlertsMobileTabClass');
expect(alertsPageSource).toContain('getAlertsTabTitle');
expect(alertsPageSource).toContain('getAlertsTabGroups');
expect(alertsPageSource).toContain("import { ThresholdsTab } from '@/features/alerts/tabs/ThresholdsTab';");
expect(alertsConfigurationSurfaceSource).toContain("import { ThresholdsTab } from './tabs/ThresholdsTab';");
expect(alertsPageSource).not.toContain("import { ThresholdsTab } from '@/features/alerts/tabs/ThresholdsTab';");
expect(alertsPageSource).not.toContain("import { ThresholdsTable } from '@/components/Alerts/ThresholdsTable';");
expect(alertsPageSource).not.toContain('function ThresholdsTab(');
expect(alertThresholdsTabSource).toContain("import { ThresholdsTable } from '@/components/Alerts/ThresholdsTable';");
@ -2259,11 +2263,12 @@ describe('frontend resource type boundaries', () => {
expect(alertScheduleTabSource).toContain('getAlertQuietDayButtonClass');
expect(alertScheduleTabSource).toContain('getAlertQuietSuppressCardClass');
expect(alertScheduleTabSource).toContain('getAlertQuietSuppressCheckboxClass');
expect(alertsPageSource).toContain('getAlertConfigUnsavedChangesLabel');
expect(alertsPageSource).toContain('getAlertConfigSaveChangesLabel');
expect(alertsPageSource).toContain('getAlertConfigDiscardedSuccess');
expect(alertsPageSource).toContain('getAlertConfigReloadFailure');
expect(alertsPageSource).toContain('getAlertConfigDiscardLabel');
expect(alertsPageSource).not.toContain('getAlertConfigUnsavedChangesLabel');
expect(alertsConfigurationSurfaceSource).toContain('getAlertConfigUnsavedChangesLabel');
expect(alertsConfigurationSurfaceSource).toContain('getAlertConfigSaveChangesLabel');
expect(alertsConfigurationSurfaceSource).toContain('getAlertConfigDiscardedSuccess');
expect(alertsConfigurationSurfaceSource).toContain('getAlertConfigReloadFailure');
expect(alertsConfigurationSurfaceSource).toContain('getAlertConfigDiscardLabel');
expect(alertsPageSource).toContain('getAlertConfigLeaveConfirmation');
expect(alertScheduleTabSource).toContain('getAlertConfigResetDefaultsLabel');
expect(alertScheduleTabSource).toContain('getAlertConfigResetDefaultsTitle');
@ -3045,10 +3050,13 @@ describe('frontend resource type boundaries', () => {
it('keeps alerts configuration tabs feature-owned instead of page-local', () => {
expect(alertsPageSource).toContain(
"import { DestinationsTab } from '@/features/alerts/tabs/DestinationsTab';",
"import { AlertsConfigurationSurface } from '@/features/alerts/AlertsConfigurationSurface';",
);
expect(alertsPageSource).toContain(
"import { ScheduleTab } from '@/features/alerts/tabs/ScheduleTab';",
expect(alertsConfigurationSurfaceSource).toContain(
"import { DestinationsTab } from './tabs/DestinationsTab';",
);
expect(alertsConfigurationSurfaceSource).toContain(
"import { ScheduleTab } from './tabs/ScheduleTab';",
);
expect(alertsPageSource).not.toContain('function DestinationsTab(');
expect(alertsPageSource).not.toContain('function ScheduleTab(');