From ac4b0806ee6334aae65fdf166ea778058dc6f56c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 22 Mar 2026 17:39:42 +0000 Subject: [PATCH] Split alert schedule tab section owners --- .../v6/internal/subsystems/alerts.md | 6 + .../subsystems/frontend-primitives.md | 5 + .../features/alerts/AlertCooldownSection.tsx | 98 +++ .../alerts/AlertEscalationSection.tsx | 132 ++++ .../features/alerts/AlertGroupingSection.tsx | 136 ++++ .../alerts/AlertQuietHoursSection.tsx | 182 +++++ .../features/alerts/AlertRecoverySection.tsx | 38 + .../alerts/AlertScheduleSummarySection.tsx | 103 +++ .../src/features/alerts/tabs/ScheduleTab.tsx | 657 ++---------------- .../pages/__tests__/Alerts.helpers.test.ts | 9 + .../frontendResourceTypeBoundaries.test.ts | 30 +- 11 files changed, 774 insertions(+), 622 deletions(-) create mode 100644 frontend-modern/src/features/alerts/AlertCooldownSection.tsx create mode 100644 frontend-modern/src/features/alerts/AlertEscalationSection.tsx create mode 100644 frontend-modern/src/features/alerts/AlertGroupingSection.tsx create mode 100644 frontend-modern/src/features/alerts/AlertQuietHoursSection.tsx create mode 100644 frontend-modern/src/features/alerts/AlertRecoverySection.tsx create mode 100644 frontend-modern/src/features/alerts/AlertScheduleSummarySection.tsx diff --git a/docs/release-control/v6/internal/subsystems/alerts.md b/docs/release-control/v6/internal/subsystems/alerts.md index 7d25151ed..7f535a23c 100644 --- a/docs/release-control/v6/internal/subsystems/alerts.md +++ b/docs/release-control/v6/internal/subsystems/alerts.md @@ -271,6 +271,12 @@ provider-catalog loading and provider-default application, while `frontend-modern/src/components/Alerts/EmailProviderSelect.tsx` stays the render shell and consumes the canonical `UIEmailConfig` feature type instead of keeping a second local email-config interface. +The alert scheduling surface now follows the same shell/section split: +`frontend-modern/src/features/alerts/useAlertScheduleState.ts` owns schedule +runtime and default/reset policy, while +`frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx` stays the shell and +composes the dedicated quiet-hours, cooldown, grouping, recovery, escalation, +and summary section owners instead of carrying those panels inline. Alert filter metadata and grouped header consumers must also preserve the canonical `agent` and `node` header boundary when reusing shared filter diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 6f91f69aa..528febdab 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -467,6 +467,11 @@ provider-catalog loading and provider-default application, while `frontend-modern/src/components/Alerts/EmailProviderSelect.tsx` stays the render shell and should not re-accumulate `NotificationsAPI.getEmailProviders` or a second local email-config contract inline. +The alert scheduling surface now follows the same shell-versus-section split: +`frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx` should compose the +dedicated quiet-hours, cooldown, grouping, recovery, escalation, and summary +section owners while `frontend-modern/src/features/alerts/useAlertScheduleState.ts` +remains the canonical runtime owner. The same rule now also covers cross-tab incident timelines: the shared runtime owner is `frontend-modern/src/features/alerts/useAlertIncidentTimelineState.ts`, while `frontend-modern/src/features/alerts/OverviewTab.tsx` and diff --git a/frontend-modern/src/features/alerts/AlertCooldownSection.tsx b/frontend-modern/src/features/alerts/AlertCooldownSection.tsx new file mode 100644 index 000000000..8a9d2f1c5 --- /dev/null +++ b/frontend-modern/src/features/alerts/AlertCooldownSection.tsx @@ -0,0 +1,98 @@ +import { Show } from 'solid-js'; + +import { + controlClass, + formHelpText, + formField, + labelClass, +} from '@/components/shared/Form'; +import { SettingsPanel } from '@/components/shared/SettingsPanel'; +import { Toggle } from '@/components/shared/Toggle'; +import { + ALERT_CONFIG_COOLDOWN_DESCRIPTION, + ALERT_CONFIG_COOLDOWN_MAX_ALERTS_HELP, + ALERT_CONFIG_COOLDOWN_MAX_ALERTS_LABEL, + ALERT_CONFIG_COOLDOWN_MAX_ALERTS_SUFFIX, + ALERT_CONFIG_COOLDOWN_PERIOD_HELP, + ALERT_CONFIG_COOLDOWN_PERIOD_LABEL, + ALERT_CONFIG_COOLDOWN_PERIOD_SUFFIX, + ALERT_CONFIG_COOLDOWN_TITLE, + getAlertConfigToggleStatusLabel, +} from '@/utils/alertConfigPresentation'; + +import type { CooldownConfig } from './types'; + +interface AlertCooldownSectionProps { + cooldown: CooldownConfig; + setCooldownEnabled: (value: boolean) => void; + setCooldownMinutes: (value: string) => void; + setCooldownMaxAlerts: (value: string) => void; +} + +export function AlertCooldownSection(props: AlertCooldownSectionProps) { + return ( + props.setCooldownEnabled(event.currentTarget.checked)} + containerClass="sm:self-start" + label={ + + {getAlertConfigToggleStatusLabel(props.cooldown.enabled)} + + } + /> + } + class="space-y-4" + > + +
+
+
+ +
+ props.setCooldownMinutes(event.currentTarget.value)} + class={controlClass('pr-16')} + /> + + {ALERT_CONFIG_COOLDOWN_PERIOD_SUFFIX} + +
+

{ALERT_CONFIG_COOLDOWN_PERIOD_HELP}

+
+ +
+ +
+ props.setCooldownMaxAlerts(event.currentTarget.value)} + class={controlClass('pr-16')} + /> + + {ALERT_CONFIG_COOLDOWN_MAX_ALERTS_SUFFIX} + +
+

{ALERT_CONFIG_COOLDOWN_MAX_ALERTS_HELP}

+
+
+
+
+
+ ); +} diff --git a/frontend-modern/src/features/alerts/AlertEscalationSection.tsx b/frontend-modern/src/features/alerts/AlertEscalationSection.tsx new file mode 100644 index 000000000..0a7aa4e93 --- /dev/null +++ b/frontend-modern/src/features/alerts/AlertEscalationSection.tsx @@ -0,0 +1,132 @@ +import { For, Show } from 'solid-js'; + +import { controlClass, formHelpText } from '@/components/shared/Form'; +import { SettingsPanel } from '@/components/shared/SettingsPanel'; +import { Toggle } from '@/components/shared/Toggle'; +import { + ALERT_CONFIG_ESCALATION_ADD_LABEL, + ALERT_CONFIG_ESCALATION_AFTER_LABEL, + ALERT_CONFIG_ESCALATION_DESCRIPTION, + ALERT_CONFIG_ESCALATION_MINUTES_SUFFIX, + ALERT_CONFIG_ESCALATION_NOTIFY_LABEL, + ALERT_CONFIG_ESCALATION_REMOVE_TITLE, + ALERT_CONFIG_ESCALATION_TITLE, + getAlertConfigEscalationHelp, + getAlertConfigEscalationNotifyLabel, + getAlertConfigToggleStatusLabel, +} from '@/utils/alertConfigPresentation'; + +import type { EscalationConfig, EscalationNotifyTarget } from './types'; + +interface AlertEscalationSectionProps { + escalation: EscalationConfig; + setEscalationEnabled: (value: boolean) => void; + setEscalationAfter: (index: number, value: string) => void; + setEscalationNotify: (index: number, value: EscalationNotifyTarget) => void; + removeEscalationLevel: (index: number) => void; + addEscalationLevel: () => void; +} + +export function AlertEscalationSection(props: AlertEscalationSectionProps) { + return ( + props.setEscalationEnabled(event.currentTarget.checked)} + containerClass="sm:self-start" + label={ + + {getAlertConfigToggleStatusLabel(props.escalation.enabled)} + + } + /> + } + class="space-y-4" + > + +
+

{getAlertConfigEscalationHelp()}

+ + {(level, index) => ( +
+
+
+ + {ALERT_CONFIG_ESCALATION_AFTER_LABEL} + + props.setEscalationAfter(index(), event.currentTarget.value)} + class={`${controlClass('px-2 py-1 text-sm')} w-20`} + /> + + {ALERT_CONFIG_ESCALATION_MINUTES_SUFFIX} + +
+
+ + {ALERT_CONFIG_ESCALATION_NOTIFY_LABEL} + + +
+
+ +
+ )} +
+ + +
+
+
+ ); +} diff --git a/frontend-modern/src/features/alerts/AlertGroupingSection.tsx b/frontend-modern/src/features/alerts/AlertGroupingSection.tsx new file mode 100644 index 000000000..0aef0697e --- /dev/null +++ b/frontend-modern/src/features/alerts/AlertGroupingSection.tsx @@ -0,0 +1,136 @@ +import { Show } from 'solid-js'; + +import { + formHelpText, + formField, + labelClass, +} from '@/components/shared/Form'; +import { SettingsPanel } from '@/components/shared/SettingsPanel'; +import { Toggle } from '@/components/shared/Toggle'; +import { + ALERT_CONFIG_GROUPING_BY_GUEST, + ALERT_CONFIG_GROUPING_BY_NODE, + ALERT_CONFIG_GROUPING_DESCRIPTION, + ALERT_CONFIG_GROUPING_STRATEGY_LABEL, + ALERT_CONFIG_GROUPING_TITLE, + ALERT_CONFIG_GROUPING_WINDOW_HELP, + ALERT_CONFIG_GROUPING_WINDOW_LABEL, + getAlertConfigToggleStatusLabel, +} from '@/utils/alertConfigPresentation'; +import { + getAlertGroupingCardClass, + getAlertGroupingCheckboxClass, +} from '@/utils/alertGroupingPresentation'; + +import type { GroupingConfig } from './types'; + +interface AlertGroupingSectionProps { + grouping: GroupingConfig; + setGroupingEnabled: (value: boolean) => void; + setGroupingWindow: (value: string) => void; + setGroupingByNode: (value: boolean) => void; + setGroupingByGuest: (value: boolean) => void; +} + +export function AlertGroupingSection(props: AlertGroupingSectionProps) { + return ( + props.setGroupingEnabled(event.currentTarget.checked)} + containerClass="sm:self-start" + label={ + + {getAlertConfigToggleStatusLabel(props.grouping.enabled)} + + } + /> + } + class="space-y-4" + > + +
+
+ +
+ props.setGroupingWindow(event.currentTarget.value)} + class="flex-1" + /> +
+ {props.grouping.window} min +
+
+

{ALERT_CONFIG_GROUPING_WINDOW_HELP}

+
+ +
+ + {ALERT_CONFIG_GROUPING_STRATEGY_LABEL} + +
+ + + +
+
+
+
+
+ ); +} diff --git a/frontend-modern/src/features/alerts/AlertQuietHoursSection.tsx b/frontend-modern/src/features/alerts/AlertQuietHoursSection.tsx new file mode 100644 index 000000000..3930ae4c2 --- /dev/null +++ b/frontend-modern/src/features/alerts/AlertQuietHoursSection.tsx @@ -0,0 +1,182 @@ +import { For, Show } from 'solid-js'; + +import { controlClass, formField, labelClass } from '@/components/shared/Form'; +import { SettingsPanel } from '@/components/shared/SettingsPanel'; +import { Toggle } from '@/components/shared/Toggle'; +import { + ALERT_CONFIG_QUIET_HOURS_DESCRIPTION, + ALERT_CONFIG_QUIET_HOURS_END_TIME_LABEL, + ALERT_CONFIG_QUIET_HOURS_START_TIME_LABEL, + ALERT_CONFIG_QUIET_HOURS_TIMEZONE_LABEL, + ALERT_CONFIG_QUIET_HOURS_TITLE, +} from '@/utils/alertConfigPresentation'; +import { + getAlertQuietDayButtonClass, + getAlertQuietSuppressCardClass, + getAlertQuietSuppressCheckboxClass, +} from '@/utils/alertSchedulePresentation'; + +import { ALERT_SCHEDULE_DAYS, ALERT_SCHEDULE_TIMEZONES } from './useAlertScheduleState'; +import type { QuietHoursConfig } from './types'; + +interface QuietSuppressOption { + key: keyof QuietHoursConfig['suppress']; + label: string; + description: string; +} + +interface AlertQuietHoursSectionProps { + quietHours: QuietHoursConfig; + quietHourSuppressOptions: QuietSuppressOption[]; + weekdaysOnly: boolean; + weekendsOnly: boolean; + setQuietHoursEnabled: (value: boolean) => void; + setQuietHoursStart: (value: string) => void; + setQuietHoursEnd: (value: string) => void; + setQuietHoursTimezone: (value: string) => void; + toggleQuietDay: (day: keyof QuietHoursConfig['days']) => void; + setQuietSuppressCategory: (category: keyof QuietHoursConfig['suppress'], value: boolean) => void; +} + +export function AlertQuietHoursSection(props: AlertQuietHoursSectionProps) { + return ( + props.setQuietHoursEnabled(event.currentTarget.checked)} + containerClass="sm:self-start" + label={ + + {props.quietHours.enabled ? 'Enabled' : 'Disabled'} + + } + /> + } + class="space-y-4" + > + +
+
+
+ + props.setQuietHoursStart(event.currentTarget.value)} + class={controlClass('font-mono')} + /> +
+
+ + props.setQuietHoursEnd(event.currentTarget.value)} + class={controlClass('font-mono')} + /> +
+
+ + +
+
+ +
+ + Quiet days + +
+ + {(day) => ( + + )} + +
+

+ Weekdays only + Weekends only +

+
+ +
+ + Suppress categories + +

+ Critical alerts in selected categories will stay silent during quiet hours. +

+
+ + {(option) => ( + + )} + +
+
+
+
+
+ ); +} diff --git a/frontend-modern/src/features/alerts/AlertRecoverySection.tsx b/frontend-modern/src/features/alerts/AlertRecoverySection.tsx new file mode 100644 index 000000000..deac9fa3c --- /dev/null +++ b/frontend-modern/src/features/alerts/AlertRecoverySection.tsx @@ -0,0 +1,38 @@ +import { SettingsPanel } from '@/components/shared/SettingsPanel'; +import { Toggle } from '@/components/shared/Toggle'; +import { formHelpText } from '@/components/shared/Form'; +import { + ALERT_CONFIG_RECOVERY_DESCRIPTION, + ALERT_CONFIG_RECOVERY_TITLE, + getAlertConfigRecoveryHelp, + getAlertConfigToggleStatusLabel, +} from '@/utils/alertConfigPresentation'; + +interface AlertRecoverySectionProps { + notifyOnResolve: boolean; + setNotifyOnResolveEnabled: (value: boolean) => void; +} + +export function AlertRecoverySection(props: AlertRecoverySectionProps) { + return ( + props.setNotifyOnResolveEnabled(event.currentTarget.checked)} + containerClass="sm:self-start" + label={ + + {getAlertConfigToggleStatusLabel(props.notifyOnResolve)} + + } + /> + } + class="space-y-3" + > +

{getAlertConfigRecoveryHelp()}

+
+ ); +} diff --git a/frontend-modern/src/features/alerts/AlertScheduleSummarySection.tsx b/frontend-modern/src/features/alerts/AlertScheduleSummarySection.tsx new file mode 100644 index 000000000..68f59fc74 --- /dev/null +++ b/frontend-modern/src/features/alerts/AlertScheduleSummarySection.tsx @@ -0,0 +1,103 @@ +import { Show } from 'solid-js'; + +import { SettingsPanel } from '@/components/shared/SettingsPanel'; +import { + ALERT_CONFIG_SUMMARY_DESCRIPTION, + ALERT_CONFIG_SUMMARY_TITLE, + getAlertConfigSummaryAllDisabled, + getAlertConfigSummaryCooldown, + getAlertConfigSummaryEscalation, + getAlertConfigSummaryGrouping, + getAlertConfigSummaryQuietHours, + getAlertConfigSummaryRecoveryEnabled, + getAlertConfigSummarySuppressing, +} from '@/utils/alertConfigPresentation'; + +import type { + CooldownConfig, + EscalationConfig, + GroupingConfig, + QuietHoursConfig, +} from './types'; + +interface QuietSuppressOption { + key: keyof QuietHoursConfig['suppress']; + label: string; + description: string; +} + +interface AlertScheduleSummarySectionProps { + quietHours: QuietHoursConfig; + cooldown: CooldownConfig; + grouping: GroupingConfig; + notifyOnResolve: boolean; + escalation: EscalationConfig; + quietHourSuppressOptions: QuietSuppressOption[]; +} + +export function AlertScheduleSummarySection(props: AlertScheduleSummarySectionProps) { + return ( + + +

+ {getAlertConfigSummaryQuietHours( + props.quietHours.start, + props.quietHours.end, + props.quietHours.timezone, + )} +

+
+ +

+ {getAlertConfigSummarySuppressing( + props.quietHourSuppressOptions + .filter((option) => props.quietHours.suppress[option.key]) + .map((option) => option.label), + )} +

+
+ +

{getAlertConfigSummaryCooldown(props.cooldown.minutes, props.cooldown.maxAlerts)}

+
+ +

+ {getAlertConfigSummaryGrouping( + props.grouping.window, + props.grouping.byNode ?? false, + props.grouping.byGuest ?? false, + )} +

+
+ +

{getAlertConfigSummaryRecoveryEnabled()}

+
+ 0}> +

{getAlertConfigSummaryEscalation(props.escalation.levels.length)}

+
+ +

{getAlertConfigSummaryAllDisabled()}

+
+
+ ); +} diff --git a/frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx b/frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx index 4c6a2c194..a041da929 100644 --- a/frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx +++ b/frontend-modern/src/features/alerts/tabs/ScheduleTab.tsx @@ -1,84 +1,18 @@ -import { For, Show } from 'solid-js'; - import { - controlClass, - formHelpText, - formField, - labelClass, -} from '@/components/shared/Form'; -import { SettingsPanel } from '@/components/shared/SettingsPanel'; -import { Toggle } from '@/components/shared/Toggle'; -import { - ALERT_CONFIG_COOLDOWN_DESCRIPTION, - ALERT_CONFIG_COOLDOWN_MAX_ALERTS_HELP, - ALERT_CONFIG_COOLDOWN_MAX_ALERTS_LABEL, - ALERT_CONFIG_COOLDOWN_MAX_ALERTS_SUFFIX, - ALERT_CONFIG_COOLDOWN_PERIOD_HELP, - ALERT_CONFIG_COOLDOWN_PERIOD_LABEL, - ALERT_CONFIG_COOLDOWN_PERIOD_SUFFIX, - ALERT_CONFIG_COOLDOWN_TITLE, - ALERT_CONFIG_ESCALATION_ADD_LABEL, - ALERT_CONFIG_ESCALATION_AFTER_LABEL, - ALERT_CONFIG_ESCALATION_DESCRIPTION, - ALERT_CONFIG_ESCALATION_MINUTES_SUFFIX, - ALERT_CONFIG_ESCALATION_NOTIFY_LABEL, - ALERT_CONFIG_ESCALATION_REMOVE_TITLE, - ALERT_CONFIG_ESCALATION_TITLE, - ALERT_CONFIG_GROUPING_BY_GUEST, - ALERT_CONFIG_GROUPING_BY_NODE, - ALERT_CONFIG_GROUPING_DESCRIPTION, - ALERT_CONFIG_GROUPING_STRATEGY_LABEL, - ALERT_CONFIG_GROUPING_TITLE, - ALERT_CONFIG_GROUPING_WINDOW_HELP, - ALERT_CONFIG_GROUPING_WINDOW_LABEL, - ALERT_CONFIG_QUIET_HOURS_DESCRIPTION, - ALERT_CONFIG_QUIET_HOURS_END_TIME_LABEL, - ALERT_CONFIG_QUIET_HOURS_START_TIME_LABEL, - ALERT_CONFIG_QUIET_HOURS_TIMEZONE_LABEL, - ALERT_CONFIG_QUIET_HOURS_TITLE, - ALERT_CONFIG_RECOVERY_DESCRIPTION, - ALERT_CONFIG_RECOVERY_TITLE, ALERT_CONFIG_SCHEDULING_DESCRIPTION, ALERT_CONFIG_SCHEDULING_TITLE, - ALERT_CONFIG_SUMMARY_DESCRIPTION, - ALERT_CONFIG_SUMMARY_TITLE, - getAlertConfigEscalationHelp, - getAlertConfigEscalationNotifyLabel, getAlertConfigQuietHourSuppressOptions, - getAlertConfigRecoveryHelp, getAlertConfigResetDefaultsLabel, getAlertConfigResetDefaultsTitle, - getAlertConfigSummaryAllDisabled, - getAlertConfigSummaryCooldown, - getAlertConfigSummaryEscalation, - getAlertConfigSummaryGrouping, - getAlertConfigSummaryQuietHours, - getAlertConfigSummaryRecoveryEnabled, - getAlertConfigSummarySuppressing, - getAlertConfigToggleStatusLabel, } from '@/utils/alertConfigPresentation'; -import { - getAlertGroupingCardClass, - getAlertGroupingCheckboxClass, -} from '@/utils/alertGroupingPresentation'; -import { - getAlertQuietDayButtonClass, - getAlertQuietSuppressCardClass, - getAlertQuietSuppressCheckboxClass, -} from '@/utils/alertSchedulePresentation'; - -import { - ALERT_SCHEDULE_DAYS, - ALERT_SCHEDULE_TIMEZONES, - useAlertScheduleState, -} from '../useAlertScheduleState'; -import type { - CooldownConfig, - EscalationConfig, - EscalationNotifyTarget, - GroupingConfig, - QuietHoursConfig, -} from '../types'; +import { AlertCooldownSection } from '../AlertCooldownSection'; +import { AlertEscalationSection } from '../AlertEscalationSection'; +import { AlertGroupingSection } from '../AlertGroupingSection'; +import { AlertQuietHoursSection } from '../AlertQuietHoursSection'; +import { AlertRecoverySection } from '../AlertRecoverySection'; +import { AlertScheduleSummarySection } from '../AlertScheduleSummarySection'; +import { useAlertScheduleState } from '../useAlertScheduleState'; +import type { CooldownConfig, EscalationConfig, GroupingConfig, QuietHoursConfig } from '../types'; export interface ScheduleTabProps { setHasUnsavedChanges: (value: boolean) => void; @@ -131,541 +65,56 @@ export function ScheduleTab(props: ScheduleTabProps) {
- { - scheduleState.setQuietHoursEnabled(event.currentTarget.checked); - }} - containerClass="sm:self-start" - label={ - - {props.quietHours().enabled ? 'Enabled' : 'Disabled'} - - } - /> - } - class="space-y-4" - > - -
-
-
- - { - scheduleState.setQuietHoursStart(event.currentTarget.value); - }} - class={controlClass('font-mono')} - /> -
-
- - { - scheduleState.setQuietHoursEnd(event.currentTarget.value); - }} - class={controlClass('font-mono')} - /> -
-
- - -
-
+ -
- - Quiet days - -
- - {(day) => ( - - )} - -
-

- - Weekdays only - - - Weekends only - -

-
+ -
- - Suppress categories - -

- Critical alerts in selected categories will stay silent during quiet hours. -

-
- - {(option) => ( - - )} - -
-
-
-
-
+ - { - scheduleState.setCooldownEnabled(event.currentTarget.checked); - }} - containerClass="sm:self-start" - label={ - - {getAlertConfigToggleStatusLabel(props.cooldown().enabled)} - - } - /> - } - class="space-y-4" - > - -
-
-
- -
- { - scheduleState.setCooldownMinutes(event.currentTarget.value); - }} - class={controlClass('pr-16')} - /> - - {ALERT_CONFIG_COOLDOWN_PERIOD_SUFFIX} - -
-

{ALERT_CONFIG_COOLDOWN_PERIOD_HELP}

-
+ -
- -
- { - scheduleState.setCooldownMaxAlerts(event.currentTarget.value); - }} - class={controlClass('pr-16')} - /> - - {ALERT_CONFIG_COOLDOWN_MAX_ALERTS_SUFFIX} - -
-

{ALERT_CONFIG_COOLDOWN_MAX_ALERTS_HELP}

-
-
-
-
-
+ - { - scheduleState.setGroupingEnabled(event.currentTarget.checked); - }} - containerClass="sm:self-start" - label={ - - {getAlertConfigToggleStatusLabel(props.grouping().enabled)} - - } - /> - } - class="space-y-4" - > - -
-
- -
- { - scheduleState.setGroupingWindow(event.currentTarget.value); - }} - class="flex-1" - /> -
- {props.grouping().window} min -
-
-

{ALERT_CONFIG_GROUPING_WINDOW_HELP}

-
- -
- - {ALERT_CONFIG_GROUPING_STRATEGY_LABEL} - -
- - - -
-
-
-
-
- - { - scheduleState.setNotifyOnResolveEnabled(event.currentTarget.checked); - }} - containerClass="sm:self-start" - label={ - - {getAlertConfigToggleStatusLabel(props.notifyOnResolve())} - - } - /> - } - class="space-y-3" - > -

{getAlertConfigRecoveryHelp()}

-
- - { - scheduleState.setEscalationEnabled(event.currentTarget.checked); - }} - containerClass="sm:self-start" - label={ - - {getAlertConfigToggleStatusLabel(props.escalation().enabled)} - - } - /> - } - class="space-y-4" - > - -
-

{getAlertConfigEscalationHelp()}

- - {(level, index) => ( -
-
-
- - {ALERT_CONFIG_ESCALATION_AFTER_LABEL} - - { - scheduleState.setEscalationAfter( - index(), - event.currentTarget.value, - ); - }} - class={`${controlClass('px-2 py-1 text-sm')} w-20`} - /> - - {ALERT_CONFIG_ESCALATION_MINUTES_SUFFIX} - -
-
- - {ALERT_CONFIG_ESCALATION_NOTIFY_LABEL} - - -
-
- -
- )} -
- - -
-
-
- - - -

- {getAlertConfigSummaryQuietHours( - props.quietHours().start, - props.quietHours().end, - props.quietHours().timezone, - )} -

-
- -

- {getAlertConfigSummarySuppressing( - quietHourSuppressOptions - .filter((option) => props.quietHours().suppress[option.key]) - .map((option) => option.label), - )} -

-
- -

- {getAlertConfigSummaryCooldown( - props.cooldown().minutes, - props.cooldown().maxAlerts, - )} -

-
- -

- {getAlertConfigSummaryGrouping( - props.grouping().window, - props.grouping().byNode ?? false, - props.grouping().byGuest ?? false, - )} -

-
- -

{getAlertConfigSummaryRecoveryEnabled()}

-
- 0}> -

{getAlertConfigSummaryEscalation(props.escalation().levels.length)}

-
- -

{getAlertConfigSummaryAllDisabled()}

-
-
+
); diff --git a/frontend-modern/src/pages/__tests__/Alerts.helpers.test.ts b/frontend-modern/src/pages/__tests__/Alerts.helpers.test.ts index a8ec98337..af7306123 100644 --- a/frontend-modern/src/pages/__tests__/Alerts.helpers.test.ts +++ b/frontend-modern/src/pages/__tests__/Alerts.helpers.test.ts @@ -309,6 +309,15 @@ describe('tab path helpers', () => { expect(alertScheduleStateSource).toContain('createDefaultGrouping'); expect(alertScheduleStateSource).toContain('createDefaultEscalation'); expect(alertScheduleTabSource).toContain('getAlertConfigQuietHourSuppressOptions'); + expect(alertScheduleTabSource).toContain('AlertQuietHoursSection'); + expect(alertScheduleTabSource).toContain('AlertCooldownSection'); + expect(alertScheduleTabSource).toContain('AlertGroupingSection'); + expect(alertScheduleTabSource).toContain('AlertRecoverySection'); + expect(alertScheduleTabSource).toContain('AlertEscalationSection'); + expect(alertScheduleTabSource).toContain('AlertScheduleSummarySection'); + expect(alertScheduleTabSource).not.toContain('ALERT_CONFIG_COOLDOWN_TITLE'); + expect(alertScheduleTabSource).not.toContain('ALERT_CONFIG_QUIET_HOURS_TITLE'); + expect(alertScheduleTabSource).not.toContain('ALERT_CONFIG_ESCALATION_TITLE'); expect(alertThresholdsTabSource).toContain('ThresholdsTable'); expect(thresholdsTableSource).toContain( "import { useThresholdsTableState } from '@/features/alerts/thresholds/hooks/useThresholdsTableState';", diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts index 9fb00267b..8f29172ff 100644 --- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts +++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts @@ -2938,11 +2938,6 @@ describe('frontend resource type boundaries', () => { 'export function useThresholdsOverrideMutations', ); expect(thresholdsOverrideMutationsHookSource).toContain('matchesAlertIdentifier'); - expect(alertScheduleTabSource).toContain('getAlertGroupingCardClass'); - expect(alertScheduleTabSource).toContain('getAlertGroupingCheckboxClass'); - expect(alertScheduleTabSource).toContain('getAlertQuietDayButtonClass'); - expect(alertScheduleTabSource).toContain('getAlertQuietSuppressCardClass'); - expect(alertScheduleTabSource).toContain('getAlertQuietSuppressCheckboxClass'); expect(alertsPageSource).not.toContain('getAlertConfigUnsavedChangesLabel'); expect(alertsConfigurationSurfaceSource).toContain('getAlertConfigUnsavedChangesLabel'); expect(alertsConfigurationSurfaceSource).toContain('getAlertConfigSaveChangesLabel'); @@ -2952,18 +2947,17 @@ describe('frontend resource type boundaries', () => { expect(alertsPageSource).toContain('getAlertConfigLeaveConfirmation'); expect(alertScheduleTabSource).toContain('getAlertConfigResetDefaultsLabel'); expect(alertScheduleTabSource).toContain('getAlertConfigResetDefaultsTitle'); - expect(alertScheduleTabSource).toContain('getAlertConfigToggleStatusLabel'); - expect(alertScheduleTabSource).toContain('getAlertConfigSummaryQuietHours'); - expect(alertScheduleTabSource).toContain('getAlertConfigSummarySuppressing'); - expect(alertScheduleTabSource).toContain('getAlertConfigSummaryCooldown'); - expect(alertScheduleTabSource).toContain('getAlertConfigSummaryGrouping'); - expect(alertScheduleTabSource).toContain('getAlertConfigSummaryRecoveryEnabled'); - expect(alertScheduleTabSource).toContain('getAlertConfigSummaryEscalation'); expect(alertScheduleTabSource).toContain('getAlertConfigQuietHourSuppressOptions'); - expect(alertScheduleTabSource).toContain('ALERT_CONFIG_COOLDOWN_PERIOD_LABEL'); - expect(alertScheduleTabSource).toContain('ALERT_CONFIG_COOLDOWN_MAX_ALERTS_LABEL'); - expect(alertScheduleTabSource).toContain('ALERT_CONFIG_GROUPING_WINDOW_LABEL'); - expect(alertScheduleTabSource).toContain('ALERT_CONFIG_GROUPING_STRATEGY_LABEL'); + expect(alertScheduleTabSource).toContain('AlertQuietHoursSection'); + expect(alertScheduleTabSource).toContain('AlertCooldownSection'); + expect(alertScheduleTabSource).toContain('AlertGroupingSection'); + expect(alertScheduleTabSource).toContain('AlertRecoverySection'); + expect(alertScheduleTabSource).toContain('AlertEscalationSection'); + expect(alertScheduleTabSource).toContain('AlertScheduleSummarySection'); + expect(alertScheduleTabSource).not.toContain('ALERT_CONFIG_COOLDOWN_PERIOD_LABEL'); + expect(alertScheduleTabSource).not.toContain('ALERT_CONFIG_COOLDOWN_MAX_ALERTS_LABEL'); + expect(alertScheduleTabSource).not.toContain('ALERT_CONFIG_GROUPING_WINDOW_LABEL'); + expect(alertScheduleTabSource).not.toContain('ALERT_CONFIG_GROUPING_STRATEGY_LABEL'); expect(alertsPageSource).not.toContain('const statusClasses ='); expect(alertsPageSource).not.toContain('const levelClasses ='); expect(alertsPageSource).not.toContain("alert.source === 'ai' ? 'Patrol' : 'Alert'"); @@ -3896,8 +3890,8 @@ describe('frontend resource type boundaries', () => { expect(alertDestinationsTabSource).toContain('AlertEmailDestinationsSection'); expect(alertDestinationsTabSource).toContain('AlertWebhookDestinationsSection'); expect(alertScheduleTabSource).toContain('getAlertConfigQuietHourSuppressOptions'); - expect(alertScheduleTabSource).toContain('getAlertGroupingCardClass'); - expect(alertScheduleTabSource).toContain('getAlertQuietDayButtonClass'); + expect(alertScheduleTabSource).toContain('AlertGroupingSection'); + expect(alertScheduleTabSource).toContain('AlertQuietHoursSection'); }); it('keeps alert resource table vocabulary in a shared presentation utility', () => {