From 91528fdb1c12120d595ffb7937b4b2c712672a7b Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 23 Mar 2026 08:56:44 +0000 Subject: [PATCH] Split monitored system limit warning banner owners --- .../subsystems/frontend-primitives.md | 9 ++ .../MonitoredSystemLimitWarningBanner.tsx | 140 ++++-------------- .../SharedPrimitives.guardrails.test.ts | 51 ++++++- ...MonitoredSystemLimitWarningBanner.test.tsx | 45 ++++++ .../monitoredSystemLimitWarningBannerModel.ts | 86 +++++++++++ ...eMonitoredSystemLimitWarningBannerState.ts | 93 ++++++++++++ .../frontendResourceTypeBoundaries.test.ts | 32 ++++ 7 files changed, 336 insertions(+), 120 deletions(-) create mode 100644 frontend-modern/src/components/shared/monitoredSystemLimitWarningBannerModel.ts create mode 100644 frontend-modern/src/components/shared/useMonitoredSystemLimitWarningBannerState.ts diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 5e3b7e4ba..5ea65135c 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -597,6 +597,15 @@ That banner boundary now also owns the canonical monitored-system naming surface directly: the shared warning component path and exported symbol are `MonitoredSystemLimitWarningBanner`, and future work may not reintroduce an agent-era banner filename or component name as the primary primitive. +That shared monitored-system warning banner now also follows the shell/runtime/model +owner split. `frontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx` +stays the render shell, `frontend-modern/src/components/shared/useMonitoredSystemLimitWarningBannerState.ts` +owns entitlement load, warning metric emission, migration/upgrade click tracking, +and upgrade-link runtime, and +`frontend-modern/src/components/shared/monitoredSystemLimitWarningBannerModel.ts` +owns monitored-system summary, migration copy, overflow summary, and tone/text-class +policy. Future warning-banner work should extend those owners instead of pushing +entitlement orchestration, tracking, or naming math back into the shared shell. First-session educational surfaces must also stay brief, flat, and model-led. When Pulse needs to teach a user how a flow works, the primary on-screen guidance should collapse to a few short descriptions of the real product diff --git a/frontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx b/frontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx index 6f7130e58..48293b18a 100644 --- a/frontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx +++ b/frontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx @@ -1,143 +1,55 @@ -import { Component, Show, createEffect, createMemo } from 'solid-js'; +import { Component, Show } from 'solid-js'; import { - entitlements, - getLimit, - getUpgradeActionUrlOrFallback, - hasMigrationGap, - legacyConnections, -} from '@/stores/license'; -import { - trackUpgradeClicked, - trackUpgradeMetricEvent, - UPGRADE_METRIC_EVENTS, -} from '@/utils/upgradeMetrics'; + MONITORED_SYSTEM_LIMIT_BILLING_HREF, + MONITORED_SYSTEM_LIMIT_INSTALL_COLLECTORS_HREF, + MONITORED_SYSTEM_LIMIT_INSTALL_COLLECTORS_LABEL, + MONITORED_SYSTEM_LIMIT_LEARN_MORE_LABEL, + MONITORED_SYSTEM_LIMIT_UPGRADE_LABEL, +} from './monitoredSystemLimitWarningBannerModel'; +import { useMonitoredSystemLimitWarningBannerState } from './useMonitoredSystemLimitWarningBannerState'; export const MonitoredSystemLimitWarningBanner: Component = () => { - // No onMount load — TrialBanner (mounted above) already calls loadLicenseStatus(). - - const monitoredSystemLimit = createMemo(() => getLimit('max_monitored_systems')); - - const isUrgent = createMemo(() => { - const state = monitoredSystemLimit()?.state; - return state === 'warning' || state === 'enforced'; - }); - - const migrationGap = createMemo(() => hasMigrationGap()); - const migrationCounts = createMemo(() => legacyConnections()); - const legacyConnectionTotal = createMemo(() => { - const counts = migrationCounts(); - return counts.proxmox_nodes + counts.docker_hosts + counts.kubernetes_clusters; - }); - const showBanner = createMemo(() => Boolean(monitoredSystemLimit()) && isUrgent()); - const monitoredSystemSummary = createMemo(() => { - const limit = monitoredSystemLimit(); - if (!limit) return ''; - return `Monitored systems: ${limit.current}/${limit.limit}`; - }); - const legacyBreakdown = createMemo(() => { - const counts = migrationCounts(); - const parts: string[] = []; - if (counts.proxmox_nodes > 0) { - parts.push( - `${counts.proxmox_nodes} Proxmox ${counts.proxmox_nodes === 1 ? 'node' : 'nodes'}`, - ); - } - if (counts.docker_hosts > 0) { - parts.push(`${counts.docker_hosts} Docker ${counts.docker_hosts === 1 ? 'host' : 'hosts'}`); - } - if (counts.kubernetes_clusters > 0) { - parts.push( - `${counts.kubernetes_clusters} Kubernetes ${counts.kubernetes_clusters === 1 ? 'cluster' : 'clusters'}`, - ); - } - return parts.join(', '); - }); - const migrationMessage = createMemo(() => { - const total = legacyConnectionTotal(); - if (total <= 0) return ''; - const noun = total === 1 ? 'resource' : 'resources'; - const breakdown = legacyBreakdown(); - return `You also have ${total} ${noun} connected via API or legacy collectors${breakdown ? ` (${breakdown})` : ''} that count once toward your monitored-system cap when the same top-level system is discovered canonically.`; - }); - const overflowDaysRemaining = createMemo(() => entitlements()?.overflow_days_remaining); - const overflowSummary = createMemo(() => { - const days = overflowDaysRemaining(); - if (!days) return ''; - return `Includes 1 temporary onboarding slot (${days}d remaining)`; - }); - - // Emit limit_warning_shown once when the banner transitions to warning/enforced. - // Uses previous-state tracking to avoid re-emitting on every reactive update. - let prevUrgent = false; - createEffect(() => { - const urgent = isUrgent(); - const limit = monitoredSystemLimit(); - if (urgent && !prevUrgent && limit) { - trackUpgradeMetricEvent({ - type: UPGRADE_METRIC_EVENTS.LIMIT_WARNING_SHOWN, - surface: 'monitored_system_limit_banner', - limit_key: 'max_monitored_systems', - current_value: limit.current, - limit_value: limit.limit, - }); - } - prevUrgent = !!urgent; - }); + const state = useMonitoredSystemLimitWarningBannerState(); return ( - +
diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts index dd295d798..017db3bd8 100644 --- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts +++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts @@ -47,6 +47,7 @@ import infrastructureSelectorModelSource from '@/components/shared/infrastructur import infrastructureSummaryTableModelSource from '@/components/shared/infrastructureSummaryTableModel.ts?raw'; import infrastructureSummaryTableStateSource from '@/components/shared/useInfrastructureSummaryTableState.ts?raw'; import monitoredSystemLimitWarningBannerSource from '@/components/shared/MonitoredSystemLimitWarningBanner.tsx?raw'; +import monitoredSystemLimitWarningBannerModelSource from '@/components/shared/monitoredSystemLimitWarningBannerModel.ts?raw'; import selectionCardGroupSource from '@/components/shared/SelectionCardGroup.tsx?raw'; import tagBadgesSource from '@/components/shared/TagBadges.tsx?raw'; import commandPaletteStateSource from '@/components/shared/useCommandPaletteState.ts?raw'; @@ -68,6 +69,7 @@ import searchTipsPopoverStateSource from '@/components/shared/useSearchTipsPopov import tooltipStateSource from '@/components/shared/useTooltipState.ts?raw'; import trialBannerStateSource from '@/components/shared/useTrialBannerState.ts?raw'; import interactiveSparklineStateSource from '@/components/shared/useInteractiveSparklineState.ts?raw'; +import monitoredSystemLimitWarningBannerStateSource from '@/components/shared/useMonitoredSystemLimitWarningBannerState.ts?raw'; import webInterfaceUrlFieldSource from '@/components/shared/WebInterfaceUrlField.tsx?raw'; import webInterfaceUrlFieldModelSource from '@/components/shared/webInterfaceUrlFieldModel.ts?raw'; import webInterfaceUrlFieldStateSource from '@/components/shared/useWebInterfaceUrlFieldState.ts?raw'; @@ -218,18 +220,55 @@ describe('shared primitive guardrails', () => { }); it('keeps shared fleet limit banner copy on the monitored-system commercial term', () => { - expect(monitoredSystemLimitWarningBannerSource).toContain('Monitored systems:'); - expect(monitoredSystemLimitWarningBannerSource).toContain('monitored-system cap'); - expect(monitoredSystemLimitWarningBannerSource).toContain('Install v6 collectors'); - expect(monitoredSystemLimitWarningBannerSource).not.toContain('v6 Unified Agents:'); - expect(monitoredSystemLimitWarningBannerSource).not.toContain( + expect(monitoredSystemLimitWarningBannerModelSource).toContain('Monitored systems:'); + expect(monitoredSystemLimitWarningBannerModelSource).toContain('monitored-system cap'); + expect(monitoredSystemLimitWarningBannerModelSource).toContain('Install v6 collectors'); + expect(monitoredSystemLimitWarningBannerModelSource).not.toContain('v6 Unified Agents:'); + expect(monitoredSystemLimitWarningBannerModelSource).not.toContain( 'do not count toward Unified Agents.', ); - expect(monitoredSystemLimitWarningBannerSource).not.toContain( + expect(monitoredSystemLimitWarningBannerModelSource).not.toContain( 'Install v6 Unified Agents', ); }); + it('keeps monitored system limit warning banner on shell, runtime, and model owners', () => { + expect(monitoredSystemLimitWarningBannerSource).toContain( + 'useMonitoredSystemLimitWarningBannerState', + ); + expect(monitoredSystemLimitWarningBannerSource).toContain( + 'MONITORED_SYSTEM_LIMIT_LEARN_MORE_LABEL', + ); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('createEffect'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('createMemo'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('loadLicenseStatus'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('trackUpgradeMetricEvent'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('legacyConnections()'); + + expect(monitoredSystemLimitWarningBannerStateSource).toContain( + 'export function useMonitoredSystemLimitWarningBannerState', + ); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('createEffect'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('createMemo'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('loadLicenseStatus'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('trackUpgradeMetricEvent'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('legacyConnections'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('handleUpgradeClick'); + + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'getMonitoredSystemMigrationMessage', + ); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'getMonitoredSystemBannerToneClass', + ); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'MONITORED_SYSTEM_LIMIT_UPGRADE_LABEL', + ); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'MONITORED_SYSTEM_LIMIT_INSTALL_COLLECTORS_LABEL', + ); + }); + it('keeps shared tag badges in the shared primitive boundary', () => { expect(tagBadgesSource).toContain("from '@/components/shared/Tooltip'"); expect(guestRowSource).toContain("from '@/components/shared/TagBadges'"); diff --git a/frontend-modern/src/components/shared/__tests__/MonitoredSystemLimitWarningBanner.test.tsx b/frontend-modern/src/components/shared/__tests__/MonitoredSystemLimitWarningBanner.test.tsx index fe838d4a5..34bf15734 100644 --- a/frontend-modern/src/components/shared/__tests__/MonitoredSystemLimitWarningBanner.test.tsx +++ b/frontend-modern/src/components/shared/__tests__/MonitoredSystemLimitWarningBanner.test.tsx @@ -1,5 +1,8 @@ import { cleanup, render, screen } from '@solidjs/testing-library'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import monitoredSystemLimitWarningBannerSource from '@/components/shared/MonitoredSystemLimitWarningBanner.tsx?raw'; +import monitoredSystemLimitWarningBannerModelSource from '@/components/shared/monitoredSystemLimitWarningBannerModel.ts?raw'; +import monitoredSystemLimitWarningBannerStateSource from '@/components/shared/useMonitoredSystemLimitWarningBannerState.ts?raw'; type MockEntitlements = { overflow_days_remaining?: number; @@ -26,6 +29,7 @@ const mockLegacyConnections = vi.hoisted(() => ); const mockTrackUpgradeMetricEvent = vi.hoisted(() => vi.fn()); const mockTrackUpgradeClicked = vi.hoisted(() => vi.fn()); +const mockLoadLicenseStatus = vi.hoisted(() => vi.fn()); vi.mock('@/stores/license', () => ({ entitlements: mockEntitlements, @@ -33,6 +37,7 @@ vi.mock('@/stores/license', () => ({ getUpgradeActionUrlOrFallback: vi.fn(() => '/pricing?feature=max_monitored_systems'), hasMigrationGap: mockHasMigrationGap, legacyConnections: mockLegacyConnections, + loadLicenseStatus: (...args: unknown[]) => mockLoadLicenseStatus(...args), })); vi.mock('@/utils/upgradeMetrics', () => ({ @@ -54,6 +59,8 @@ describe('MonitoredSystemLimitWarningBanner', () => { docker_hosts: 0, kubernetes_clusters: 0, }); + mockLoadLicenseStatus.mockReset(); + mockLoadLicenseStatus.mockResolvedValue(undefined); }); afterEach(() => { @@ -61,10 +68,48 @@ describe('MonitoredSystemLimitWarningBanner', () => { vi.clearAllMocks(); }); + it('keeps monitored system limit warning banner on shell, runtime, and model owners', () => { + expect(monitoredSystemLimitWarningBannerSource).toContain( + 'useMonitoredSystemLimitWarningBannerState', + ); + expect(monitoredSystemLimitWarningBannerSource).toContain( + 'MONITORED_SYSTEM_LIMIT_LEARN_MORE_LABEL', + ); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('createEffect'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('createMemo'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('loadLicenseStatus'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('trackUpgradeMetricEvent'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('legacyConnections()'); + + expect(monitoredSystemLimitWarningBannerStateSource).toContain( + 'export function useMonitoredSystemLimitWarningBannerState', + ); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('createEffect'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('createMemo'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('loadLicenseStatus'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('trackUpgradeMetricEvent'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('legacyConnections'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('handleUpgradeClick'); + + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'getMonitoredSystemMigrationMessage', + ); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'getMonitoredSystemBannerToneClass', + ); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'MONITORED_SYSTEM_LIMIT_UPGRADE_LABEL', + ); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'MONITORED_SYSTEM_LIMIT_INSTALL_COLLECTORS_LABEL', + ); + }); + it('stays hidden for non-urgent pure v6 installs', async () => { const mod = await import('../MonitoredSystemLimitWarningBanner'); render(() => ); + expect(mockLoadLicenseStatus).toHaveBeenCalled(); expect(screen.queryByText(/Monitored systems:/i)).not.toBeInTheDocument(); }); diff --git a/frontend-modern/src/components/shared/monitoredSystemLimitWarningBannerModel.ts b/frontend-modern/src/components/shared/monitoredSystemLimitWarningBannerModel.ts new file mode 100644 index 000000000..7feaa09bc --- /dev/null +++ b/frontend-modern/src/components/shared/monitoredSystemLimitWarningBannerModel.ts @@ -0,0 +1,86 @@ +type LimitState = { + current: number; + limit: number; + state?: string; +}; + +type LegacyConnectionCounts = { + proxmox_nodes: number; + docker_hosts: number; + kubernetes_clusters: number; +}; + +export const MONITORED_SYSTEM_LIMIT_KEY = 'max_monitored_systems'; +export const MONITORED_SYSTEM_LIMIT_BILLING_HREF = '/settings/system/billing'; +export const MONITORED_SYSTEM_LIMIT_INSTALL_COLLECTORS_HREF = '/settings'; +export const MONITORED_SYSTEM_LIMIT_LEARN_MORE_LABEL = 'Learn more'; +export const MONITORED_SYSTEM_LIMIT_INSTALL_COLLECTORS_LABEL = 'Install v6 collectors'; +export const MONITORED_SYSTEM_LIMIT_UPGRADE_LABEL = 'Upgrade to add more'; + +export function isMonitoredSystemLimitUrgent(limit: LimitState | undefined): boolean { + const state = limit?.state; + return state === 'warning' || state === 'enforced'; +} + +export function shouldShowMonitoredSystemLimitBanner(limit: LimitState | undefined): boolean { + return Boolean(limit) && isMonitoredSystemLimitUrgent(limit); +} + +export function getMonitoredSystemSummary(limit: LimitState | undefined): string { + if (!limit) return ''; + return `Monitored systems: ${limit.current}/${limit.limit}`; +} + +export function getMonitoredSystemLegacyConnectionTotal(counts: LegacyConnectionCounts): number { + return counts.proxmox_nodes + counts.docker_hosts + counts.kubernetes_clusters; +} + +export function getMonitoredSystemLegacyBreakdown(counts: LegacyConnectionCounts): string { + const parts: string[] = []; + + if (counts.proxmox_nodes > 0) { + parts.push( + `${counts.proxmox_nodes} Proxmox ${counts.proxmox_nodes === 1 ? 'node' : 'nodes'}`, + ); + } + if (counts.docker_hosts > 0) { + parts.push(`${counts.docker_hosts} Docker ${counts.docker_hosts === 1 ? 'host' : 'hosts'}`); + } + if (counts.kubernetes_clusters > 0) { + parts.push( + `${counts.kubernetes_clusters} Kubernetes ${ + counts.kubernetes_clusters === 1 ? 'cluster' : 'clusters' + }`, + ); + } + + return parts.join(', '); +} + +export function getMonitoredSystemMigrationMessage(counts: LegacyConnectionCounts): string { + const total = getMonitoredSystemLegacyConnectionTotal(counts); + if (total <= 0) return ''; + + const noun = total === 1 ? 'resource' : 'resources'; + const breakdown = getMonitoredSystemLegacyBreakdown(counts); + return `You also have ${total} ${noun} connected via API or legacy collectors${ + breakdown ? ` (${breakdown})` : '' + } that count once toward your monitored-system cap when the same top-level system is discovered canonically.`; +} + +export function getMonitoredSystemOverflowSummary(daysRemaining: number | undefined): string { + if (!daysRemaining) return ''; + return `Includes 1 temporary onboarding slot (${daysRemaining}d remaining)`; +} + +export function getMonitoredSystemBannerToneClass(isUrgent: boolean): string { + return isUrgent + ? 'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-900 dark:text-amber-100' + : 'border-sky-200 bg-sky-50 text-sky-950 dark:border-sky-900 dark:bg-sky-950 dark:text-sky-100'; +} + +export function getMonitoredSystemMigrationTextClass(isUrgent: boolean): string { + return isUrgent + ? 'text-amber-800 dark:text-amber-200' + : 'text-sky-800 dark:text-sky-200'; +} diff --git a/frontend-modern/src/components/shared/useMonitoredSystemLimitWarningBannerState.ts b/frontend-modern/src/components/shared/useMonitoredSystemLimitWarningBannerState.ts new file mode 100644 index 000000000..4b8b1a2d9 --- /dev/null +++ b/frontend-modern/src/components/shared/useMonitoredSystemLimitWarningBannerState.ts @@ -0,0 +1,93 @@ +import { createEffect, createMemo, onMount } from 'solid-js'; +import { + entitlements, + getLimit, + getUpgradeActionUrlOrFallback, + hasMigrationGap, + legacyConnections, + loadLicenseStatus, +} from '@/stores/license'; +import { + trackUpgradeClicked, + trackUpgradeMetricEvent, + UPGRADE_METRIC_EVENTS, +} from '@/utils/upgradeMetrics'; +import { + getMonitoredSystemBannerToneClass, + getMonitoredSystemMigrationMessage, + getMonitoredSystemMigrationTextClass, + getMonitoredSystemOverflowSummary, + getMonitoredSystemSummary, + isMonitoredSystemLimitUrgent, + MONITORED_SYSTEM_LIMIT_KEY, + shouldShowMonitoredSystemLimitBanner, +} from './monitoredSystemLimitWarningBannerModel'; + +export function useMonitoredSystemLimitWarningBannerState() { + onMount(() => { + void loadLicenseStatus(); + }); + + const monitoredSystemLimit = createMemo(() => getLimit(MONITORED_SYSTEM_LIMIT_KEY)); + const isUrgent = createMemo(() => isMonitoredSystemLimitUrgent(monitoredSystemLimit())); + const showBanner = createMemo(() => shouldShowMonitoredSystemLimitBanner(monitoredSystemLimit())); + const migrationGap = createMemo(() => hasMigrationGap()); + const migrationCounts = createMemo(() => legacyConnections()); + const monitoredSystemSummary = createMemo(() => + getMonitoredSystemSummary(monitoredSystemLimit()), + ); + const migrationMessage = createMemo(() => + getMonitoredSystemMigrationMessage(migrationCounts()), + ); + const overflowSummary = createMemo(() => + getMonitoredSystemOverflowSummary(entitlements()?.overflow_days_remaining), + ); + const toneClass = createMemo(() => getMonitoredSystemBannerToneClass(isUrgent())); + const migrationTextClass = createMemo(() => + getMonitoredSystemMigrationTextClass(isUrgent()), + ); + const upgradeHref = createMemo(() => + getUpgradeActionUrlOrFallback(MONITORED_SYSTEM_LIMIT_KEY), + ); + + let wasUrgent = false; + createEffect(() => { + const urgent = isUrgent(); + const limit = monitoredSystemLimit(); + if (urgent && !wasUrgent && limit) { + trackUpgradeMetricEvent({ + type: UPGRADE_METRIC_EVENTS.LIMIT_WARNING_SHOWN, + surface: 'monitored_system_limit_banner', + limit_key: MONITORED_SYSTEM_LIMIT_KEY, + current_value: limit.current, + limit_value: limit.limit, + }); + } + wasUrgent = urgent; + }); + + const handleInstallCollectorsClick = () => { + trackUpgradeClicked( + 'monitored_system_limit_banner_install_v6_collectors', + MONITORED_SYSTEM_LIMIT_KEY, + ); + }; + + const handleUpgradeClick = () => { + trackUpgradeClicked('monitored_system_limit_banner_upgrade', MONITORED_SYSTEM_LIMIT_KEY); + }; + + return { + handleInstallCollectorsClick, + handleUpgradeClick, + isUrgent, + migrationGap, + migrationMessage, + migrationTextClass, + monitoredSystemSummary, + overflowSummary, + showBanner, + toneClass, + upgradeHref, + }; +} diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts index e3ed3ef99..7615fb71f 100644 --- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts +++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts @@ -44,6 +44,8 @@ import tooltipSource from '@/components/shared/Tooltip.tsx?raw'; import tooltipModelSource from '@/components/shared/tooltipModel.ts?raw'; import trialBannerSource from '@/components/shared/TrialBanner.tsx?raw'; import trialBannerModelSource from '@/components/shared/trialBannerModel.ts?raw'; +import monitoredSystemLimitWarningBannerSource from '@/components/shared/MonitoredSystemLimitWarningBanner.tsx?raw'; +import monitoredSystemLimitWarningBannerModelSource from '@/components/shared/monitoredSystemLimitWarningBannerModel.ts?raw'; import infrastructureSummaryTableSource from '@/components/shared/InfrastructureSummaryTable.tsx?raw'; import infrastructureSummaryTableRowSource from '@/components/shared/InfrastructureSummaryTableRow.tsx?raw'; import interactiveSparklineSource from '@/components/shared/InteractiveSparkline.tsx?raw'; @@ -67,6 +69,7 @@ import searchTipsPopoverStateSource from '@/components/shared/useSearchTipsPopov import tooltipStateSource from '@/components/shared/useTooltipState.ts?raw'; import trialBannerStateSource from '@/components/shared/useTrialBannerState.ts?raw'; import interactiveSparklineStateSource from '@/components/shared/useInteractiveSparklineState.ts?raw'; +import monitoredSystemLimitWarningBannerStateSource from '@/components/shared/useMonitoredSystemLimitWarningBannerState.ts?raw'; import infrastructureSummaryTableStateSource from '@/components/shared/useInfrastructureSummaryTableState.ts?raw'; import resourceBadgePresentationSource from '@/utils/resourceBadgePresentation.ts?raw'; import workloadTypeBadgesSource from '@/components/shared/workloadTypeBadges.ts?raw'; @@ -2751,6 +2754,35 @@ describe('frontend resource type boundaries', () => { expect(trialBannerModelSource).toContain('getTrialBannerToneClass'); expect(trialBannerModelSource).toContain('getTrialBannerStatusLabel'); expect(trialBannerModelSource).toContain('TRIAL_BANNER_UPGRADE_LABEL'); + expect(monitoredSystemLimitWarningBannerSource).toContain( + 'useMonitoredSystemLimitWarningBannerState', + ); + expect(monitoredSystemLimitWarningBannerSource).toContain( + 'MONITORED_SYSTEM_LIMIT_LEARN_MORE_LABEL', + ); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('createEffect'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('createMemo'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('loadLicenseStatus'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('trackUpgradeMetricEvent'); + expect(monitoredSystemLimitWarningBannerSource).not.toContain('legacyConnections()'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('createEffect'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('createMemo'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('loadLicenseStatus'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('trackUpgradeMetricEvent'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('legacyConnections'); + expect(monitoredSystemLimitWarningBannerStateSource).toContain('handleUpgradeClick'); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'getMonitoredSystemMigrationMessage', + ); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'getMonitoredSystemBannerToneClass', + ); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'MONITORED_SYSTEM_LIMIT_UPGRADE_LABEL', + ); + expect(monitoredSystemLimitWarningBannerModelSource).toContain( + 'MONITORED_SYSTEM_LIMIT_INSTALL_COLLECTORS_LABEL', + ); expect(whatsNewModalSource).toContain('useWhatsNewModalState'); expect(whatsNewModalSource).toContain('WHATS_NEW_FEATURE_CARDS'); expect(whatsNewModalSource).not.toContain('createLocalStorageBooleanSignal');