mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-08 09:53:25 +00:00
Unify monitored system disclosure presentation
This commit is contained in:
parent
c862ed3090
commit
24fa055ec1
11 changed files with 97 additions and 42 deletions
|
|
@ -239,9 +239,11 @@ signal entirely, the settings surface should degrade to a safe customer-facing
|
|||
fallback instead of an unexplained placeholder glyph.
|
||||
That same billing support boundary now also owns the shared monitored-system
|
||||
presentation helper. `frontend-modern/src/utils/monitoredSystemPresentation.ts`
|
||||
is the canonical owner for ledger labels, safe fallback summaries, and
|
||||
source/type attribution wording, so the settings panel must consume that
|
||||
helper instead of redefining customer-facing monitored-system copy inline.
|
||||
is the canonical owner for monitored-system brief/disclosure copy, ledger
|
||||
labels, safe fallback summaries, and source/type attribution wording, so the
|
||||
settings panel, Pro usage section, and counting-rules disclosure must consume
|
||||
that helper instead of redefining customer-facing monitored-system copy inline
|
||||
or keeping a parallel copy in generic self-hosted plan utilities.
|
||||
Frontend billing/admin surfaces must not synthesize `plan_version` from
|
||||
subscription lifecycle state. When a hosted billing record lacks a plan label,
|
||||
the UI must preserve that absence instead of fabricating values like `active`
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Show, createSignal, type Component } from 'solid-js';
|
||||
import {
|
||||
SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION,
|
||||
SELF_HOSTED_MONITORED_SYSTEMS_DISCLOSURE_LABEL,
|
||||
SELF_HOSTED_MONITORED_SYSTEMS_HIDE_LABEL,
|
||||
} from '@/utils/selfHostedPlans';
|
||||
getMonitoredSystemDisclosureDefinition,
|
||||
getMonitoredSystemDisclosureToggleLabel,
|
||||
} from '@/utils/monitoredSystemPresentation';
|
||||
|
||||
interface MonitoredSystemDefinitionDisclosureProps {
|
||||
summary?: string;
|
||||
|
|
@ -34,12 +33,12 @@ export const MonitoredSystemDefinitionDisclosure: Component<
|
|||
aria-expanded={open()}
|
||||
onClick={() => setOpen((current) => !current)}
|
||||
>
|
||||
{open() ? SELF_HOSTED_MONITORED_SYSTEMS_HIDE_LABEL : SELF_HOSTED_MONITORED_SYSTEMS_DISCLOSURE_LABEL}
|
||||
{getMonitoredSystemDisclosureToggleLabel(open())}
|
||||
</button>
|
||||
|
||||
<Show when={open()}>
|
||||
<p class={props.detailClass ?? 'max-w-2xl text-xs text-muted'}>
|
||||
{SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION}
|
||||
{getMonitoredSystemDisclosureDefinition()}
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,14 +25,12 @@ import { PulseLogoIcon } from '@/components/icons/PulseLogoIcon';
|
|||
import {
|
||||
formatMonitoredSystemLatestIncludedSignalSentence,
|
||||
formatMonitoredSystemSurfaceAttribution,
|
||||
getMonitoredSystemLedgerDescription,
|
||||
getMonitoredSystemCountingDetailsToggleLabel,
|
||||
getMonitoredSystemExplanationFallbackSummary,
|
||||
getMonitoredSystemLedgerPresentation,
|
||||
getMonitoredSystemStatusFallbackSummary,
|
||||
} from '@/utils/monitoredSystemPresentation';
|
||||
import {
|
||||
SELF_HOSTED_MONITORED_SYSTEM_LEDGER_DESCRIPTION,
|
||||
} from '@/utils/selfHostedPlans';
|
||||
import { MonitoredSystemDefinitionDisclosure } from '@/components/Commercial/MonitoredSystemDefinitionDisclosure';
|
||||
|
||||
interface MonitoredSystemLedgerPanelProps {
|
||||
|
|
@ -289,7 +287,7 @@ export function MonitoredSystemLedgerPanel(props: MonitoredSystemLedgerPanelProp
|
|||
return (
|
||||
<SettingsPanel
|
||||
title={presentation.panelTitle}
|
||||
description={SELF_HOSTED_MONITORED_SYSTEM_LEDGER_DESCRIPTION}
|
||||
description={getMonitoredSystemLedgerDescription()}
|
||||
icon={<PulseLogoIcon class="w-5 h-5" />}
|
||||
bodyClass="space-y-4"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { CommercialBillingShell, CommercialSection } from './CommercialBillingSe
|
|||
import { ProLicensePlanSection } from './ProLicensePlanSection';
|
||||
import { SelfHostedCommercialActivationSection } from './SelfHostedCommercialActivationSection';
|
||||
import { useProLicensePanelState } from './useProLicensePanelState';
|
||||
import { SELF_HOSTED_MONITORED_SYSTEMS_BRIEF } from '@/utils/selfHostedPlans';
|
||||
import { getMonitoredSystemBriefSummary } from '@/utils/monitoredSystemPresentation';
|
||||
|
||||
export const ProLicensePanel: Component = () => {
|
||||
const state = useProLicensePanelState();
|
||||
|
|
@ -52,7 +52,7 @@ export const ProLicensePanel: Component = () => {
|
|||
|
||||
<CommercialSection
|
||||
title="Usage"
|
||||
description={SELF_HOSTED_MONITORED_SYSTEMS_BRIEF}
|
||||
description={getMonitoredSystemBriefSummary()}
|
||||
>
|
||||
<MonitoredSystemLedgerPanel embedded />
|
||||
</CommercialSection>
|
||||
|
|
|
|||
|
|
@ -159,9 +159,17 @@ describe('ProLicensePanel', () => {
|
|||
expect(
|
||||
screen.queryByText(/a monitored system is a top-level machine or cluster/i),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'View counting rules' })).toHaveAttribute(
|
||||
'aria-expanded',
|
||||
'false',
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'View counting rules' }));
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Hide counting rules' })).toHaveAttribute(
|
||||
'aria-expanded',
|
||||
'true',
|
||||
);
|
||||
expect(
|
||||
screen.getByText(/a monitored system is a top-level machine or cluster/i),
|
||||
).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ import relaySettingsPanelSource from '../RelaySettingsPanel.tsx?raw';
|
|||
import relayPairingSectionSource from '../RelayPairingSection.tsx?raw';
|
||||
import monitoredSystemLedgerPanelSource from '../MonitoredSystemLedgerPanel.tsx?raw';
|
||||
import proLicensePanelSource from '../ProLicensePanel.tsx?raw';
|
||||
import monitoredSystemDefinitionDisclosureSource from '@/components/Commercial/MonitoredSystemDefinitionDisclosure.tsx?raw';
|
||||
import proLicensePlanSectionSource from '../ProLicensePlanSection.tsx?raw';
|
||||
import commercialBillingSectionsSource from '../CommercialBillingSections.tsx?raw';
|
||||
import selfHostedCommercialActivationSectionSource from '../SelfHostedCommercialActivationSection.tsx?raw';
|
||||
|
|
@ -510,6 +511,15 @@ describe('Settings architecture guardrails', () => {
|
|||
expect(monitoredSystemLedgerPanelSource).toContain('getMonitoredSystemCountingDetailsToggleLabel');
|
||||
expect(monitoredSystemLedgerPanelSource).not.toContain('No monitored systems counted.');
|
||||
expect(monitoredSystemLedgerPanelSource).not.toContain('Current status');
|
||||
expect(monitoredSystemLedgerPanelSource).toContain('getMonitoredSystemLedgerDescription');
|
||||
expect(proLicensePanelSource).toContain('@/utils/monitoredSystemPresentation');
|
||||
expect(proLicensePanelSource).toContain('getMonitoredSystemBriefSummary');
|
||||
expect(monitoredSystemDefinitionDisclosureSource).toContain(
|
||||
'@/utils/monitoredSystemPresentation',
|
||||
);
|
||||
expect(monitoredSystemDefinitionDisclosureSource).toContain(
|
||||
'getMonitoredSystemDisclosureToggleLabel',
|
||||
);
|
||||
expect(proLicensePanelStateSource).toContain('buildSelfHostedCommercialPlanModel');
|
||||
expect(proLicensePanelStateSource).toContain('loadLicenseStatus(true)');
|
||||
expect(proLicensePlanSectionSource).toContain('CommercialStatGrid');
|
||||
|
|
@ -518,6 +528,12 @@ describe('Settings architecture guardrails', () => {
|
|||
expect(monitoredSystemPresentationSource).toContain(
|
||||
'export function getMonitoredSystemCountingDetailsToggleLabel',
|
||||
);
|
||||
expect(monitoredSystemPresentationSource).toContain(
|
||||
'export function getMonitoredSystemBriefSummary',
|
||||
);
|
||||
expect(monitoredSystemPresentationSource).toContain(
|
||||
'export function getMonitoredSystemDisclosureToggleLabel',
|
||||
);
|
||||
expect(selfHostedCommercialActivationSectionSource).toContain('License / Activation Key');
|
||||
expect(selfHostedCommercialActivationSectionSource).toContain('Start 14-day Pro Trial');
|
||||
expect(organizationBillingPanelSource).toContain('./CommercialBillingSections');
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ 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 monitoredSystemLedgerPanelSource from '@/components/Settings/MonitoredSystemLedgerPanel.tsx?raw';
|
||||
import monitoredSystemDefinitionDisclosureSource from '@/components/Commercial/MonitoredSystemDefinitionDisclosure.tsx?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';
|
||||
|
|
@ -3001,8 +3002,15 @@ describe('frontend resource type boundaries', () => {
|
|||
expect(monitoredSystemLedgerPanelSource).toContain(
|
||||
'getMonitoredSystemCountingDetailsToggleLabel',
|
||||
);
|
||||
expect(monitoredSystemLedgerPanelSource).toContain('getMonitoredSystemLedgerDescription');
|
||||
expect(monitoredSystemLedgerPanelSource).not.toContain('No monitored systems counted.');
|
||||
expect(monitoredSystemLedgerPanelSource).not.toContain('Current status');
|
||||
expect(monitoredSystemDefinitionDisclosureSource).toContain(
|
||||
'@/utils/monitoredSystemPresentation',
|
||||
);
|
||||
expect(monitoredSystemDefinitionDisclosureSource).toContain(
|
||||
'getMonitoredSystemDisclosureToggleLabel',
|
||||
);
|
||||
expect(monitoredSystemPresentationSource).toContain(
|
||||
'export function getMonitoredSystemLedgerPresentation',
|
||||
);
|
||||
|
|
@ -3012,6 +3020,12 @@ describe('frontend resource type boundaries', () => {
|
|||
expect(monitoredSystemPresentationSource).toContain(
|
||||
'export function formatMonitoredSystemLatestIncludedSignalSentence',
|
||||
);
|
||||
expect(monitoredSystemPresentationSource).toContain(
|
||||
'export function getMonitoredSystemBriefSummary',
|
||||
);
|
||||
expect(monitoredSystemPresentationSource).toContain(
|
||||
'export function getMonitoredSystemDisclosureToggleLabel',
|
||||
);
|
||||
expect(whatsNewModalSource).toContain('useWhatsNewModalState');
|
||||
expect(whatsNewModalSource).toContain('WHATS_NEW_FEATURE_CARDS');
|
||||
expect(whatsNewModalSource).not.toContain('createLocalStorageBooleanSignal');
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
getMonitoredSystemBriefSummary,
|
||||
formatMonitoredSystemLatestIncludedSignalSentence,
|
||||
formatMonitoredSystemSurfaceAttribution,
|
||||
getMonitoredSystemCountingDetailsToggleLabel,
|
||||
getMonitoredSystemDisclosureDefinition,
|
||||
getMonitoredSystemDisclosureToggleLabel,
|
||||
getMonitoredSystemExplanationFallbackSummary,
|
||||
getMonitoredSystemLedgerDescription,
|
||||
getMonitoredSystemLedgerPresentation,
|
||||
getMonitoredSystemSourceLabel,
|
||||
getMonitoredSystemStatusFallbackSummary,
|
||||
|
|
@ -14,8 +18,15 @@ import {
|
|||
describe('monitoredSystemPresentation', () => {
|
||||
it('returns canonical ledger labels and fallback copy', () => {
|
||||
expect(getMonitoredSystemLedgerPresentation()).toEqual({
|
||||
briefSummary: 'Billing is based on monitored systems. Child resources are included.',
|
||||
sectionTitle: 'Monitored Systems',
|
||||
panelTitle: 'Monitored System Ledger',
|
||||
disclosureButtonLabel: 'View counting rules',
|
||||
disclosureHideLabel: 'Hide counting rules',
|
||||
disclosureDefinition:
|
||||
'A monitored system is a top-level machine or cluster Pulse actively monitors. Each system counts once no matter how Pulse collects it. Child resources like VMs, containers, pods, disks, backups, and services are included.',
|
||||
ledgerDescription:
|
||||
'Review the monitored systems currently counted against your Pulse Pro plan limit.',
|
||||
tableNameLabel: 'Name',
|
||||
tableStatusLabel: 'Status',
|
||||
tableLatestIncludedSignalLabel: 'Latest Included Signal',
|
||||
|
|
@ -30,6 +41,15 @@ describe('monitoredSystemPresentation', () => {
|
|||
fallbackStatusSummary:
|
||||
'Pulse cannot determine a canonical runtime status for this monitored system yet.',
|
||||
});
|
||||
expect(getMonitoredSystemBriefSummary()).toBe(
|
||||
'Billing is based on monitored systems. Child resources are included.',
|
||||
);
|
||||
expect(getMonitoredSystemDisclosureToggleLabel(false)).toBe('View counting rules');
|
||||
expect(getMonitoredSystemDisclosureToggleLabel(true)).toBe('Hide counting rules');
|
||||
expect(getMonitoredSystemDisclosureDefinition()).toContain('top-level machine or cluster');
|
||||
expect(getMonitoredSystemLedgerDescription()).toBe(
|
||||
'Review the monitored systems currently counted against your Pulse Pro plan limit.',
|
||||
);
|
||||
expect(getMonitoredSystemCountingDetailsToggleLabel(false)).toBe('View counting details');
|
||||
expect(getMonitoredSystemCountingDetailsToggleLabel(true)).toBe('Hide counting details');
|
||||
expect(getMonitoredSystemExplanationFallbackSummary()).toBe(
|
||||
|
|
|
|||
|
|
@ -2,25 +2,11 @@ import { describe, expect, it } from 'vitest';
|
|||
|
||||
import {
|
||||
SELF_HOSTED_FEATURE_ROWS,
|
||||
SELF_HOSTED_MONITORED_SYSTEMS_BRIEF,
|
||||
SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION,
|
||||
SELF_HOSTED_PLAN_BY_TIER,
|
||||
SELF_HOSTED_PLAN_DEFINITIONS,
|
||||
} from '../selfHostedPlans';
|
||||
|
||||
describe('selfHostedPlans', () => {
|
||||
it('keeps the monitored-system wording concise by default and explicit on disclosure', () => {
|
||||
expect(SELF_HOSTED_MONITORED_SYSTEMS_BRIEF).toBe(
|
||||
'Billing is based on monitored systems. Child resources are included.',
|
||||
);
|
||||
expect(SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION).toContain(
|
||||
'top-level machine or cluster',
|
||||
);
|
||||
expect(SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION).toContain('counts once');
|
||||
expect(SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION).toContain('VMs');
|
||||
expect(SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION).toContain('services');
|
||||
});
|
||||
|
||||
it('keeps self-hosted plan limits aligned across tier cards and comparison rows', () => {
|
||||
expect(SELF_HOSTED_PLAN_DEFINITIONS.map((tier) => tier.name)).toEqual([
|
||||
'Community',
|
||||
|
|
|
|||
|
|
@ -9,8 +9,15 @@ const titleCaseWords = (value: string): string =>
|
|||
.join(' ');
|
||||
|
||||
const MONITORED_SYSTEM_LEDGER_PRESENTATION = {
|
||||
briefSummary: 'Billing is based on monitored systems. Child resources are included.',
|
||||
sectionTitle: 'Monitored Systems',
|
||||
panelTitle: 'Monitored System Ledger',
|
||||
disclosureButtonLabel: 'View counting rules',
|
||||
disclosureHideLabel: 'Hide counting rules',
|
||||
disclosureDefinition:
|
||||
'A monitored system is a top-level machine or cluster Pulse actively monitors. Each system counts once no matter how Pulse collects it. Child resources like VMs, containers, pods, disks, backups, and services are included.',
|
||||
ledgerDescription:
|
||||
'Review the monitored systems currently counted against your Pulse Pro plan limit.',
|
||||
tableNameLabel: 'Name',
|
||||
tableStatusLabel: 'Status',
|
||||
tableLatestIncludedSignalLabel: 'Latest Included Signal',
|
||||
|
|
@ -30,6 +37,24 @@ export function getMonitoredSystemLedgerPresentation() {
|
|||
return MONITORED_SYSTEM_LEDGER_PRESENTATION;
|
||||
}
|
||||
|
||||
export function getMonitoredSystemBriefSummary(): string {
|
||||
return MONITORED_SYSTEM_LEDGER_PRESENTATION.briefSummary;
|
||||
}
|
||||
|
||||
export function getMonitoredSystemDisclosureToggleLabel(open: boolean): string {
|
||||
return open
|
||||
? MONITORED_SYSTEM_LEDGER_PRESENTATION.disclosureHideLabel
|
||||
: MONITORED_SYSTEM_LEDGER_PRESENTATION.disclosureButtonLabel;
|
||||
}
|
||||
|
||||
export function getMonitoredSystemDisclosureDefinition(): string {
|
||||
return MONITORED_SYSTEM_LEDGER_PRESENTATION.disclosureDefinition;
|
||||
}
|
||||
|
||||
export function getMonitoredSystemLedgerDescription(): string {
|
||||
return MONITORED_SYSTEM_LEDGER_PRESENTATION.ledgerDescription;
|
||||
}
|
||||
|
||||
export function getMonitoredSystemCountingDetailsToggleLabel(expanded: boolean): string {
|
||||
return expanded
|
||||
? MONITORED_SYSTEM_LEDGER_PRESENTATION.countingDetailsExpandedLabel
|
||||
|
|
|
|||
|
|
@ -19,19 +19,6 @@ export interface SelfHostedFeatureRow {
|
|||
proPlus: boolean | string;
|
||||
}
|
||||
|
||||
export const SELF_HOSTED_MONITORED_SYSTEMS_BRIEF =
|
||||
'Billing is based on monitored systems. Child resources are included.';
|
||||
|
||||
export const SELF_HOSTED_MONITORED_SYSTEMS_DISCLOSURE_LABEL = 'View counting rules';
|
||||
|
||||
export const SELF_HOSTED_MONITORED_SYSTEMS_HIDE_LABEL = 'Hide counting rules';
|
||||
|
||||
export const SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION =
|
||||
'A monitored system is a top-level machine or cluster Pulse actively monitors. Each system counts once no matter how Pulse collects it. Child resources like VMs, containers, pods, disks, backups, and services are included.';
|
||||
|
||||
export const SELF_HOSTED_MONITORED_SYSTEM_LEDGER_DESCRIPTION =
|
||||
'Review the monitored systems currently counted against your Pulse Pro plan limit.';
|
||||
|
||||
export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] = [
|
||||
{
|
||||
tier: 'community',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue