-
Advanced SSO
-
- SAML 2.0 and multi-provider SSO requires Pro. Basic OIDC is available in the free
- tier.
-
+
+ {samlFeatureGateCopy.title}
+
+
{samlFeatureGateCopy.body}
trackUpgradeClicked('settings_sso_providers_banner', 'advanced_sso')}
>
{UPGRADE_ACTION_LABEL}
@@ -195,7 +191,7 @@ export const SSOProvidersPanel: Component = (props) => {
class="min-h-10 sm:min-h-9 px-3 py-2.5 text-sm font-medium border border-border text-base-content rounded-md hover:bg-surface-hover transition-colors flex items-center gap-1.5"
>
- {getSSOProviderAddButtonLabel('saml', true)}
+ {getSSOProviderAddButtonLabel('saml')}
diff --git a/frontend-modern/src/components/Settings/__tests__/AISettings.test.tsx b/frontend-modern/src/components/Settings/__tests__/AISettings.test.tsx
index c2852dda0..58a0c6128 100644
--- a/frontend-modern/src/components/Settings/__tests__/AISettings.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/AISettings.test.tsx
@@ -25,8 +25,6 @@ const loadLicenseStatusMock = vi.fn();
const loadCommercialPostureMock = vi.fn();
const commercialPostureMock = vi.fn();
const entitlementsMock = vi.fn();
-const trackPaywallViewedMock = vi.fn();
-const trackUpgradeClickedMock = vi.fn();
const presentationPolicyHidesUpgradePromptsMock = vi.fn();
vi.mock('@/api/ai', () => ({
@@ -81,11 +79,6 @@ vi.mock('@/stores/sessionPresentationPolicy', () => ({
presentationPolicyHidesUpgradePrompts: () => presentationPolicyHidesUpgradePromptsMock(),
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: (...args: unknown[]) => trackPaywallViewedMock(...args),
- trackUpgradeClicked: (...args: unknown[]) => trackUpgradeClickedMock(...args),
-}));
-
const baseSettings = (): AISettingsType => ({
enabled: false,
model: '',
@@ -130,8 +123,6 @@ const resetAllMocks = () => {
loadCommercialPostureMock.mockReset();
commercialPostureMock.mockReset();
entitlementsMock.mockReset();
- trackPaywallViewedMock.mockReset();
- trackUpgradeClickedMock.mockReset();
presentationPolicyHidesUpgradePromptsMock.mockReset();
};
@@ -341,7 +332,6 @@ describe('AISettings model loading error states', () => {
});
expect(screen.queryByRole('option', { name: /Autonomous/i })).not.toBeInTheDocument();
- expect(trackPaywallViewedMock).not.toHaveBeenCalled();
});
it('keeps an existing autonomous setting visible even when upgrade prompts are hidden', async () => {
diff --git a/frontend-modern/src/components/Settings/__tests__/AgentProfilesPanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/AgentProfilesPanel.test.tsx
index 3d95b48f9..72f3e84f6 100644
--- a/frontend-modern/src/components/Settings/__tests__/AgentProfilesPanel.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/AgentProfilesPanel.test.tsx
@@ -90,11 +90,6 @@ vi.mock('@/utils/logger', () => ({
},
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: vi.fn(),
- trackUpgradeClicked: vi.fn(),
-}));
-
const makeAgentResource = (overrides: Partial
= {}): Resource => ({
id: 'hash-agent-resource-id',
type: 'agent',
diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx
index eab29b81e..8a73e94d6 100644
--- a/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureOperationsModel.test.tsx
@@ -2,6 +2,8 @@ import { describe, expect, it } from 'vitest';
import type { ConnectedInfrastructureItem } from '@/types/api';
import type { UnifiedAgentRow } from '../infrastructureOperationsModel';
import infrastructureInstallerSectionSource from '../InfrastructureInstallerSection.tsx?raw';
+import infrastructureOperationsModelSource from '../infrastructureOperationsModel.tsx?raw';
+import useInfrastructureInstallStateSource from '../useInfrastructureInstallState.tsx?raw';
import {
INSTALL_PROFILE_OPTIONS,
getCapabilityManagementPath,
@@ -230,6 +232,24 @@ describe('infrastructure operations model', () => {
expect(installStateSource).not.toContain("navigate('/dashboard')");
});
+ it('keeps infrastructure install and operations surfaces free of retired commercial telemetry wrappers', () => {
+ for (const source of [
+ infrastructureOperationsModelSource,
+ infrastructureInstallerSectionSource,
+ useInfrastructureInstallStateSource,
+ ]) {
+ expect(source).not.toContain('upgradeMetrics');
+ expect(source).not.toContain('conversionEvents');
+ expect(source).not.toContain('infrastructureOnboardingMetrics');
+ expect(source).not.toContain('UNIFIED_AGENT_TELEMETRY_SURFACE');
+ expect(source).not.toContain('normalizeTelemetryPart');
+ expect(source).not.toContain('trackAgentInstallTokenCreated');
+ expect(source).not.toContain('trackAgentInstallCommandsCopied');
+ expect(source).not.toContain('trackAgentFirstConnected');
+ expect(source).not.toContain('/api/upgrade-metrics/events');
+ }
+ });
+
it('does not reintroduce the retired reporting state hook on the operations state', async () => {
const operationsStateSource = await import(
'../useInfrastructureOperationsState?raw'
diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx
index a5bf44b20..8ce56ee27 100644
--- a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx
@@ -31,29 +31,6 @@ const emptyFleetMember = vi.hoisted(
);
const navigateSpy = vi.hoisted(() => vi.fn());
const presentationPolicyIsReadOnlyMock = vi.hoisted(() => vi.fn(() => false));
-const onboardingMetricsTrackers = vi.hoisted(
- () =>
- [] as Array<{
- recordOpened: ReturnType;
- recordPathSelected: ReturnType;
- recordProbeResult: ReturnType;
- recordCatalogSelected: ReturnType;
- recordCredentialsOpened: ReturnType;
- }>,
-);
-const createInfrastructureOnboardingMetricsTrackerMock = vi.hoisted(() =>
- vi.fn(() => {
- const tracker = {
- recordOpened: vi.fn(),
- recordPathSelected: vi.fn(),
- recordProbeResult: vi.fn(),
- recordCatalogSelected: vi.fn(),
- recordCredentialsOpened: vi.fn(),
- };
- onboardingMetricsTrackers.push(tracker);
- return tracker;
- }),
-);
const originalResizeObserver = globalThis.ResizeObserver;
let latestResizeObserverCallback: ResizeObserverCallback | null = null;
@@ -179,12 +156,6 @@ vi.mock('../AgentProfilesPanel', () => ({
AgentProfilesPanel: () => profiles
,
}));
-vi.mock('@/utils/infrastructureOnboardingMetrics', () => ({
- createInfrastructureOnboardingMetricsTracker: createInfrastructureOnboardingMetricsTrackerMock,
- getSharedInfrastructureOnboardingMetricsTracker: createInfrastructureOnboardingMetricsTrackerMock,
- clearSharedInfrastructureOnboardingMetricsTracker: vi.fn(),
-}));
-
const connectionFixture = (overrides: Partial = {}): Connection => ({
id: 'pve:zeus',
type: 'pve',
@@ -280,8 +251,6 @@ describe('InfrastructureWorkspace', () => {
navigateSpy.mockReset();
presentationPolicyIsReadOnlyMock.mockReset();
presentationPolicyIsReadOnlyMock.mockReturnValue(false);
- createInfrastructureOnboardingMetricsTrackerMock.mockClear();
- onboardingMetricsTrackers.length = 0;
routeState.pathname = '/settings/infrastructure';
routeState.search = '';
connectionState.connections = [connectionFixture()];
@@ -624,8 +593,6 @@ describe('InfrastructureWorkspace', () => {
expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure?add=pve', {
scroll: false,
});
- expect(createInfrastructureOnboardingMetricsTrackerMock).toHaveBeenCalledTimes(1);
- expect(onboardingMetricsTrackers[0]?.recordOpened).toHaveBeenCalledTimes(1);
});
it('opens the platform picker from the Add infrastructure action', () => {
@@ -715,14 +682,12 @@ describe('InfrastructureWorkspace', () => {
expect(screen.getByTestId('install-section')).toBeInTheDocument();
});
- it('creates one onboarding tracker for a direct type route before the add dialog mounts', async () => {
+ it('renders a direct type route before the add dialog mounts', async () => {
routeState.search = '?add=truenas';
renderWorkspace();
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
- expect(createInfrastructureOnboardingMetricsTrackerMock).toHaveBeenCalledTimes(1);
- expect(onboardingMetricsTrackers).toHaveLength(1);
- expect(onboardingMetricsTrackers[0]?.recordOpened).toHaveBeenCalledTimes(1);
+ expect(screen.getByTestId('truenas-section')).toBeInTheDocument();
});
it('opens the manage dialog directly from an existing source card', async () => {
diff --git a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx
index 61f71c55d..b68c72e7f 100644
--- a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx
@@ -28,8 +28,6 @@ import {
let mockEntitlements: LicenseEntitlements | null = null;
-const trackPricingViewedMock = vi.fn();
-const trackCheckoutClickedMock = vi.fn();
const loadRuntimeLicenseStatusMock = vi.fn();
const loadCommercialPostureMock = vi.fn();
const loadLicenseEntitlementsMock = vi.fn();
@@ -98,11 +96,6 @@ vi.mock('@/stores/notifications', () => ({
},
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackPricingViewed: (...args: unknown[]) => trackPricingViewedMock(...args),
- trackCheckoutClicked: (...args: unknown[]) => trackCheckoutClickedMock(...args),
-}));
-
describe('ProLicensePanel', () => {
const renderPanel = () =>
render(() => (
@@ -124,8 +117,6 @@ describe('ProLicensePanel', () => {
loadRuntimeLicenseStatusMock.mockReset();
loadCommercialPostureMock.mockReset();
loadLicenseEntitlementsMock.mockReset();
- trackPricingViewedMock.mockReset();
- trackCheckoutClickedMock.mockReset();
licenseEntitlementsLoadErrorMock.mockReset();
startProTrialMock.mockReset();
activateLicenseMock.mockReset();
@@ -228,10 +219,9 @@ describe('ProLicensePanel', () => {
expect(screen.queryByText('What Relay adds')).not.toBeInTheDocument();
expect(screen.queryByText('What Pulse Pro adds')).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Compare plans' })).not.toBeInTheDocument();
- expect(trackPricingViewedMock).not.toHaveBeenCalled();
});
- it('tracks compare-plan checkout intent from the explicit self-hosted billing handoff', async () => {
+ it('opens compare-plan checkout from the explicit self-hosted billing handoff', async () => {
useLocationMock.mockReturnValue({
search: `?intent=${SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT}`,
pathname: '/settings/system/billing/plan',
@@ -245,11 +235,6 @@ describe('ProLicensePanel', () => {
});
await fireEvent.click(screen.getAllByRole('link', { name: 'Compare plans' })[0]);
-
- expect(trackCheckoutClickedMock).toHaveBeenCalledWith(
- 'settings_self_hosted_billing_compare_prompt',
- SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT,
- );
});
it('keeps explicit self-hosted plan comparison focused on optional paid extras', async () => {
diff --git a/frontend-modern/src/components/Settings/__tests__/RBACPaywallPanels.test.tsx b/frontend-modern/src/components/Settings/__tests__/RBACPaywallPanels.test.tsx
index fdb0e6b55..a24eec576 100644
--- a/frontend-modern/src/components/Settings/__tests__/RBACPaywallPanels.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/RBACPaywallPanels.test.tsx
@@ -11,8 +11,6 @@ const startProTrialMock = vi.fn();
const commercialPostureMock = vi.fn();
const entitlementsMock = vi.fn();
const presentationPolicyHidesUpgradePromptsMock = vi.fn();
-const trackPaywallViewedMock = vi.fn();
-const trackUpgradeClickedMock = vi.fn();
const getRolesMock = vi.fn();
const getUsersMock = vi.fn();
const getUserPermissionsMock = vi.fn();
@@ -45,11 +43,6 @@ vi.mock('@/stores/sessionPresentationPolicy', () => ({
presentationPolicyHidesUpgradePrompts: () => presentationPolicyHidesUpgradePromptsMock(),
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: (...args: unknown[]) => trackPaywallViewedMock(...args),
- trackUpgradeClicked: (...args: unknown[]) => trackUpgradeClickedMock(...args),
-}));
-
vi.mock('@/api/rbac', () => ({
RBACAPI: {
getRoles: (...args: unknown[]) => getRolesMock(...args),
@@ -85,8 +78,6 @@ describe('RBAC paywall settings panels', () => {
commercialPostureMock.mockReset();
entitlementsMock.mockReset();
presentationPolicyHidesUpgradePromptsMock.mockReset();
- trackPaywallViewedMock.mockReset();
- trackUpgradeClickedMock.mockReset();
getRolesMock.mockReset();
getUsersMock.mockReset();
getUserPermissionsMock.mockReset();
@@ -131,7 +122,7 @@ describe('RBAC paywall settings panels', () => {
render(() => );
await waitFor(() => {
- expect(screen.getByText('Custom Roles (Pro)')).toBeInTheDocument();
+ expect(screen.getByText('Custom Roles')).toBeInTheDocument();
});
expect(screen.getByRole('link', { name: 'View plans' })).toHaveAttribute(
@@ -140,7 +131,6 @@ describe('RBAC paywall settings panels', () => {
);
expect(screen.getByRole('button', { name: 'New Role' })).toBeDisabled();
expect(getRolesMock).not.toHaveBeenCalled();
- expect(trackPaywallViewedMock).toHaveBeenCalledWith('rbac', 'settings_roles_panel');
});
it('keeps RBAC upgrade actions quiet when self-hosted upgrade prompts are hidden', async () => {
@@ -156,7 +146,6 @@ describe('RBAC paywall settings panels', () => {
expect(screen.queryByText('Custom Roles (Pro)')).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'View plans' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Start trial' })).not.toBeInTheDocument();
- expect(trackPaywallViewedMock).not.toHaveBeenCalled();
expect(getRolesMock).not.toHaveBeenCalled();
});
@@ -169,7 +158,6 @@ describe('RBAC paywall settings panels', () => {
expect(screen.queryByText('Custom Roles (Pro)')).not.toBeInTheDocument();
expect(getRolesMock).toHaveBeenCalled();
- expect(trackPaywallViewedMock).not.toHaveBeenCalled();
expect(screen.getByRole('button', { name: 'New Role' })).not.toBeDisabled();
});
@@ -179,7 +167,7 @@ describe('RBAC paywall settings panels', () => {
render(() => );
await waitFor(() => {
- expect(screen.getByText('Centralized Access Control (Pro)')).toBeInTheDocument();
+ expect(screen.getByText('Centralized Access Control')).toBeInTheDocument();
});
expect(screen.getByRole('link', { name: 'View plans' })).toHaveAttribute(
@@ -189,7 +177,6 @@ describe('RBAC paywall settings panels', () => {
expect(screen.getByPlaceholderText('Search users...')).toBeDisabled();
expect(getUsersMock).not.toHaveBeenCalled();
expect(getRolesMock).not.toHaveBeenCalled();
- expect(trackPaywallViewedMock).toHaveBeenCalledWith('rbac', 'settings_user_assignments_panel');
});
it('loads user assignments when the RBAC entitlement is granted', async () => {
@@ -202,7 +189,6 @@ describe('RBAC paywall settings panels', () => {
expect(screen.queryByText('Centralized Access Control (Pro)')).not.toBeInTheDocument();
expect(getUsersMock).toHaveBeenCalled();
expect(getRolesMock).toHaveBeenCalled();
- expect(trackPaywallViewedMock).not.toHaveBeenCalled();
expect(screen.getByPlaceholderText('Search users...')).not.toBeDisabled();
});
@@ -219,7 +205,6 @@ describe('RBAC paywall settings panels', () => {
expect(screen.queryByText('Centralized Access Control (Pro)')).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'View plans' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Start trial' })).not.toBeInTheDocument();
- expect(trackPaywallViewedMock).not.toHaveBeenCalled();
expect(getUsersMock).not.toHaveBeenCalled();
expect(getRolesMock).not.toHaveBeenCalled();
});
@@ -262,7 +247,7 @@ describe('RBAC paywall settings panels', () => {
render(() => );
await waitFor(() => {
- expect(screen.getByText('Custom Roles (Pro)')).toBeInTheDocument();
+ expect(screen.getByText('Custom Roles')).toBeInTheDocument();
});
expect(screen.getByRole('link', { name: 'View plans' })).toBeInTheDocument();
diff --git a/frontend-modern/src/components/Settings/__tests__/ReportingPanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/ReportingPanel.test.tsx
index 7032a7d39..9fd87e53f 100644
--- a/frontend-modern/src/components/Settings/__tests__/ReportingPanel.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/ReportingPanel.test.tsx
@@ -18,16 +18,12 @@ vi.mock('../ResourcePicker', () => ({
}): JSX.Element => Mock Resource Picker
,
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackUpgradeClicked: vi.fn(),
-}));
-
const baseCatalog = {
id: 'advanced_reporting',
title: 'Detailed Reporting',
description: 'Canonical reporting surfaces',
lockedState: {
- title: 'Advanced Reporting (Pro)',
+ title: 'Advanced Reporting',
description: 'Canonical locked reporting teaser',
},
guidance: {
diff --git a/frontend-modern/src/components/Settings/__tests__/reportingCatalogModel.test.ts b/frontend-modern/src/components/Settings/__tests__/reportingCatalogModel.test.ts
index 55dbf7a03..e2c72fb62 100644
--- a/frontend-modern/src/components/Settings/__tests__/reportingCatalogModel.test.ts
+++ b/frontend-modern/src/components/Settings/__tests__/reportingCatalogModel.test.ts
@@ -10,7 +10,7 @@ const baseCatalogPayload = {
title: 'Detailed Reporting',
description: 'Canonical reporting surfaces',
lockedState: {
- title: 'Advanced Reporting (Pro)',
+ title: 'Advanced Reporting',
description: 'Canonical locked reporting teaser',
},
guidance: {
@@ -81,7 +81,7 @@ describe('reporting catalog model', () => {
it('parses the canonical reporting catalog payload', () => {
const catalog = parseReportingCatalog(baseCatalogPayload);
- expect(catalog.lockedState.title).toBe('Advanced Reporting (Pro)');
+ expect(catalog.lockedState.title).toBe('Advanced Reporting');
expect(catalog.guidance.title).toBe('Advanced Insights');
expect(catalog.performanceReport.defaultFormat).toBe('pdf');
expect(catalog.performanceReport.ranges[0].windowHours).toBe(24);
@@ -153,7 +153,7 @@ describe('reporting catalog model', () => {
parseReportingCatalog({
...baseCatalogPayload,
lockedState: {
- title: 'Advanced Reporting (Pro)',
+ title: 'Advanced Reporting',
},
}),
).toThrow('Invalid reporting catalog payload');
diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
index 78b6c1a95..03c2b459f 100644
--- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
+++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
@@ -22,6 +22,7 @@ import dataHandlingPanelSource from '../DataHandlingPanel.tsx?raw';
import auditLogPanelSource from '../AuditLogPanel.tsx?raw';
import auditWebhookPanelSource from '../AuditWebhookPanel.tsx?raw';
import reportingPanelSource from '../ReportingPanel.tsx?raw';
+import rbacFeatureGateSectionSource from '../RBACFeatureGateSection.tsx?raw';
import recoverySettingsPanelSource from '../RecoverySettingsPanel.tsx?raw';
import systemLogsPanelSource from '../SystemLogsPanel.tsx?raw';
import updatesSettingsPanelSource from '../UpdatesSettingsPanel.tsx?raw';
@@ -54,10 +55,18 @@ import organizationSharingCreateSectionSource from '../OrganizationSharingCreate
import rolesEditorDialogSource from '../RolesEditorDialog.tsx?raw';
import diagnosticsResultsPanelSource from '../DiagnosticsResultsPanel.tsx?raw';
import diagnosticsModelSource from '../diagnosticsModel.ts?raw';
+import agentProfilesStateSource from '../useAgentProfilesPanelState.ts?raw';
+import auditLogStateSource from '../useAuditLogPanelState.ts?raw';
+import auditWebhookStateSource from '../useAuditWebhookPanelState.ts?raw';
+import rbacFeatureGateStateSource from '../useRBACFeatureGateState.ts?raw';
+import reportingStateSource from '../useReportingPanelState.ts?raw';
+import ssoProvidersPanelSource from '../SSOProvidersPanel.tsx?raw';
+import ssoProvidersStateSource from '../useSSOProvidersState.ts?raw';
import infrastructureOnboardingPresentationSource from '../../../utils/infrastructureOnboardingPresentation.ts?raw';
import selfHostedBillingPresentationSource from '../selfHostedBillingPresentation.ts?raw';
import systemSettingsPresentationSource from '../../../utils/systemSettingsPresentation.ts?raw';
import auditLogPresentationSource from '../../../utils/auditLogPresentation.ts?raw';
+import monitoredSystemLimitWarningBannerSource from '../../shared/MonitoredSystemLimitWarningBanner.tsx?raw';
const settingsRuntimeSources = import.meta.glob(['../*.tsx', '../ConnectionEditor/**/*.tsx'], {
query: '?raw',
@@ -197,6 +206,42 @@ describe('settings architecture guardrails', () => {
expect(aiRuntimeControlsSectionSource).not.toContain('without approval (Pro)');
});
+ it('keeps contextual settings feature gates free of retired commercial telemetry wrappers', () => {
+ for (const source of [
+ agentProfilesPanelSource,
+ agentProfilesStateSource,
+ aiRuntimeControlsSectionSource,
+ auditLogPanelSource,
+ auditLogStateSource,
+ auditWebhookPanelSource,
+ auditWebhookStateSource,
+ monitoredSystemLimitWarningBannerSource,
+ rbacFeatureGateSectionSource,
+ rbacFeatureGateStateSource,
+ reportingPanelSource,
+ reportingStateSource,
+ ssoProvidersPanelSource,
+ ssoProvidersStateSource,
+ ]) {
+ expect(source).not.toContain('upgradeMetrics');
+ expect(source).not.toContain('conversionEvents');
+ expect(source).not.toContain('infrastructureOnboardingMetrics');
+ expect(source).not.toContain('trackPaywallViewed');
+ expect(source).not.toContain('trackUpgradeClicked');
+ expect(source).not.toContain('/api/upgrade-metrics/events');
+ }
+
+ expect(agentProfilesPanelSource).not.toContain('Pro feature');
+ expect(auditWebhookPanelSource).not.toContain('Audit Webhooks (Pro)');
+ expect(reportingPanelSource).not.toContain('Advanced Reporting (Pro)');
+ expect(rbacFeatureGateSectionSource).not.toContain('Custom Roles (Pro)');
+ expect(rbacFeatureGateSectionSource).not.toContain('Centralized Access Control (Pro)');
+ expect(ssoProvidersPanelSource).not.toContain('Add SAML (Pro)');
+ expect(monitoredSystemLimitWarningBannerSource).not.toContain(
+ 'Unlimited self-hosted monitoring',
+ );
+ });
+
it('keeps Docker and Podman update-action copy on the system settings presentation owner', () => {
expect(dockerRuntimeSettingsCardSource).toContain('DOCKER_UPDATE_ACTIONS_SECTION_TITLE');
expect(dockerRuntimeSettingsCardSource).toContain('DOCKER_UPDATE_ACTIONS_ENV_VAR');
@@ -366,12 +411,13 @@ describe('settings architecture guardrails', () => {
expect(infrastructureWorkspaceSource).not.toContain('');
expect(infrastructureWorkspaceSource).toContain('flex h-full min-h-0 flex-col');
expect(infrastructureWorkspaceSource).toContain('showSlotHeader={false}');
- expect(infrastructureWorkspaceSource).toContain('trackInitialCatalogSelection={');
expect(infrastructureWorkspaceSource).toContain(
"onDetectFromAddress={() => openAddFlow('detect')}",
);
expect(infrastructureWorkspaceSource).toContain("onBackToCatalog={() => openAddFlow('pick')}");
- expect(infrastructureWorkspaceSource).toContain('recordCatalogSelection(type);');
+ expect(infrastructureWorkspaceSource).not.toContain('trackInitialCatalogSelection');
+ expect(infrastructureWorkspaceSource).not.toContain('recordCatalogSelection');
+ expect(infrastructureWorkspaceSource).not.toContain('onboardingMetricsTracker');
expect(infrastructureWorkspaceSource).toContain('renderAgentConnectionDetails');
expect(infrastructureWorkspaceSource).not.toContain('InfrastructureOperationsController');
expect(infrastructureWorkspaceSource).not.toContain('PlatformConnectionsWorkspace');
diff --git a/frontend-modern/src/components/Settings/__tests__/settingsReadOnlyPanels.test.tsx b/frontend-modern/src/components/Settings/__tests__/settingsReadOnlyPanels.test.tsx
index bcf3c4024..b4e861bea 100644
--- a/frontend-modern/src/components/Settings/__tests__/settingsReadOnlyPanels.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/settingsReadOnlyPanels.test.tsx
@@ -16,8 +16,6 @@ const showErrorMock = vi.fn();
const showWarningMock = vi.fn();
const loggerErrorMock = vi.fn();
const presentationPolicyHidesUpgradePromptsMock = vi.fn();
-const trackPaywallViewedMock = vi.fn();
-const trackUpgradeClickedMock = vi.fn();
vi.mock('../QuickSecuritySetup', () => ({
QuickSecuritySetup: () => Quick Security Setup
,
@@ -69,11 +67,6 @@ vi.mock('@/utils/logger', () => ({
},
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: (...args: unknown[]) => trackPaywallViewedMock(...args),
- trackUpgradeClicked: (...args: unknown[]) => trackUpgradeClickedMock(...args),
-}));
-
describe('settings read-only panel states', () => {
beforeEach(() => {
hasFeatureMock.mockReset();
@@ -86,8 +79,6 @@ describe('settings read-only panel states', () => {
showWarningMock.mockReset();
loggerErrorMock.mockReset();
presentationPolicyHidesUpgradePromptsMock.mockReset();
- trackPaywallViewedMock.mockReset();
- trackUpgradeClickedMock.mockReset();
hasFeatureMock.mockReturnValue(true);
presentationPolicyHidesUpgradePromptsMock.mockReturnValue(false);
@@ -180,7 +171,6 @@ describe('settings read-only panel states', () => {
expect(screen.queryByText('Audit Webhooks (Pro)')).not.toBeInTheDocument();
expect(screen.queryByText(/require Pro/i)).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'View plans' })).not.toBeInTheDocument();
- expect(trackPaywallViewedMock).not.toHaveBeenCalled();
expect(apiFetchJSONMock).not.toHaveBeenCalled();
});
});
diff --git a/frontend-modern/src/components/Settings/__tests__/useReportingPanelState.test.ts b/frontend-modern/src/components/Settings/__tests__/useReportingPanelState.test.ts
index 2d6e5b764..88490624e 100644
--- a/frontend-modern/src/components/Settings/__tests__/useReportingPanelState.test.ts
+++ b/frontend-modern/src/components/Settings/__tests__/useReportingPanelState.test.ts
@@ -16,7 +16,7 @@ const catalogPayload = {
title: 'Detailed Reporting',
description: 'Canonical reporting surfaces',
lockedState: {
- title: 'Advanced Reporting (Pro)',
+ title: 'Advanced Reporting',
description: 'Canonical locked reporting teaser',
},
guidance: {
@@ -105,10 +105,6 @@ describe('useReportingPanelState', () => {
showWarning: vi.fn(),
}));
- vi.doMock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: vi.fn(),
- }));
-
vi.doMock('@/stores/license', () => ({
hasFeature: vi.fn(
(feature: string) => feature === 'advanced_reporting' && hasReportingFeature,
@@ -178,7 +174,7 @@ describe('useReportingPanelState', () => {
expect(apiFetchMock).toHaveBeenCalledWith('/api/admin/reports/catalog');
expect(hookState.reportingCatalog()?.title).toBe('Detailed Reporting');
- expect(hookState.reportingCatalog()?.lockedState.title).toBe('Advanced Reporting (Pro)');
+ expect(hookState.reportingCatalog()?.lockedState.title).toBe('Advanced Reporting');
expect(hookState.isLocked()).toBe(true);
dispose();
@@ -207,10 +203,6 @@ describe('useReportingPanelState', () => {
showWarning: vi.fn(),
}));
- vi.doMock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: vi.fn(),
- }));
-
vi.doMock('@/stores/license', () => ({
hasFeature: vi.fn(() => false),
runtimeCapabilitiesLoaded: vi.fn(() => false),
@@ -267,10 +259,6 @@ describe('useReportingPanelState', () => {
showWarning: vi.fn(),
}));
- vi.doMock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: vi.fn(),
- }));
-
vi.doMock('@/stores/license', () => ({
hasFeature: vi.fn(
(feature: string) => feature === 'advanced_reporting' && hasReportingFeature,
@@ -333,10 +321,6 @@ describe('useReportingPanelState', () => {
showWarning: vi.fn(),
}));
- vi.doMock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: vi.fn(),
- }));
-
vi.doMock('@/stores/license', () => ({
hasFeature: vi.fn(
(feature: string) => feature === 'advanced_reporting' && hasReportingFeature,
@@ -388,10 +372,6 @@ describe('useReportingPanelState', () => {
showWarning: vi.fn(),
}));
- vi.doMock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: vi.fn(),
- }));
-
vi.doMock('@/stores/license', () => ({
hasFeature: vi.fn(
(feature: string) => feature === 'advanced_reporting' && hasReportingFeature,
diff --git a/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx b/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx
index 5dab3593f..b70c33246 100644
--- a/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx
+++ b/frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx
@@ -6,7 +6,6 @@ import { type PlatformConnectionsView } from './platformConnectionsModel';
import { buildInfrastructureWorkspacePath } from './infrastructureWorkspaceModel';
export const TOKEN_PLACEHOLDER = '';
-export const UNIFIED_AGENT_TELEMETRY_SURFACE = 'settings_unified_agents';
export type AgentPlatform = 'linux' | 'macos' | 'freebsd' | 'windows';
export type UnifiedAgentStatus = 'active' | 'removed';
@@ -111,12 +110,6 @@ export const buildDefaultTokenName = () => {
return `Agent ${stamp}`;
};
-export const normalizeTelemetryPart = (value: string) =>
- value
- .toLowerCase()
- .replace(/[^a-z0-9]+/g, '_')
- .replace(/^_+|_+$/g, '');
-
export const shellQuoteArg = (value: string) => `'${value.replace(/'/g, `'\"'\"'`)}'`;
export const getCapabilitySurfaceLabel = (capability: AgentCapability) => {
diff --git a/frontend-modern/src/components/Settings/reportingCatalogModel.ts b/frontend-modern/src/components/Settings/reportingCatalogModel.ts
index 554afc977..d4aeb7df8 100644
--- a/frontend-modern/src/components/Settings/reportingCatalogModel.ts
+++ b/frontend-modern/src/components/Settings/reportingCatalogModel.ts
@@ -70,8 +70,9 @@ export function buildLegacyReportingCatalogFallback(): ReportingCatalog {
title: 'Detailed Reporting',
description: 'Generate performance reports across infrastructure and workloads.',
lockedState: {
- title: 'Advanced Reporting (Pro)',
- description: 'Generate PDF and CSV performance reports across infrastructure and workload resources.',
+ title: 'Advanced Reporting',
+ description:
+ 'Generate PDF and CSV performance reports across infrastructure and workload resources on paid self-hosted and hosted plans.',
},
guidance: {
title: 'Advanced Insights',
diff --git a/frontend-modern/src/components/Settings/settingsFeatureGates.ts b/frontend-modern/src/components/Settings/settingsFeatureGates.ts
index c2cf2a334..7fb443a26 100644
--- a/frontend-modern/src/components/Settings/settingsFeatureGates.ts
+++ b/frontend-modern/src/components/Settings/settingsFeatureGates.ts
@@ -1,5 +1,4 @@
import type { SettingsTab } from './settingsNavigationModel';
-import { trackPaywallViewed } from '@/utils/upgradeMetrics';
import { getFeatureMinTierLabel } from '@/utils/licensePresentation';
export const tabFeatureRequirements: Partial> = {
@@ -41,9 +40,6 @@ export function getTabLockReason(
if (!runtimeCapabilitiesLoaded()) return null;
if (requiredFeatures.every((feature) => hasFeature(feature))) return null;
const primaryRequiredFeature = requiredFeatures[0];
- if (primaryRequiredFeature) {
- trackPaywallViewed(primaryRequiredFeature, 'settings_tab');
- }
const tierLabel = getFeatureMinTierLabel(primaryRequiredFeature);
return `This settings section requires ${tierLabel}.`;
}
diff --git a/frontend-modern/src/components/Settings/useAISettingsState.ts b/frontend-modern/src/components/Settings/useAISettingsState.ts
index 0fdfc82ae..b06b58f76 100644
--- a/frontend-modern/src/components/Settings/useAISettingsState.ts
+++ b/frontend-modern/src/components/Settings/useAISettingsState.ts
@@ -1,4 +1,4 @@
-import { createEffect, createMemo, createSignal, onMount } from 'solid-js';
+import { createMemo, createSignal, onMount } from 'solid-js';
import { createStore } from 'solid-js/store';
import { AIAPI } from '@/api/ai';
import { AIChatAPI, type ChatSession, type FileChange } from '@/api/aiChat';
@@ -36,7 +36,6 @@ import {
getAISettingsToggleErrorMessage,
} from '@/utils/aiSettingsPresentation';
import { logger } from '@/utils/logger';
-import { trackPaywallViewed } from '@/utils/upgradeMetrics';
export const useAISettingsState = () => {
const [settings, setSettings] = createSignal(null);
@@ -152,14 +151,6 @@ export const useAISettingsState = () => {
clearAIRuntimeModels();
};
- createEffect((wasPaywallVisible) => {
- const isPaywallVisible = form.controlLevel === 'autonomous' && autoFixLocked();
- if (showUpgradePrompts() && isPaywallVisible && !wasPaywallVisible) {
- trackPaywallViewed('ai_autofix', 'settings_ai_patrol_autofix');
- }
- return isPaywallVisible;
- }, false);
-
const resetForm = (data: AISettingsType | null) => {
if (!data) {
setForm({
diff --git a/frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts b/frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts
index d5c28f15b..14468aa8e 100644
--- a/frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts
+++ b/frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts
@@ -1,4 +1,4 @@
-import { createEffect, createMemo, createSignal, onMount } from 'solid-js';
+import { createMemo, createSignal, onMount } from 'solid-js';
import { useWebSocket } from '@/contexts/appRuntime';
import {
AgentProfilesAPI,
@@ -26,7 +26,6 @@ import {
getUpgradeActionButtonClass,
UPGRADE_ACTION_LABEL,
} from '@/utils/upgradePresentation';
-import { trackPaywallViewed, trackUpgradeClicked } from '@/utils/upgradeMetrics';
import { KNOWN_SETTINGS } from './agentProfileSettings';
import {
getActionableAgentIdFromResource,
@@ -205,14 +204,6 @@ export const useAgentProfilesPanelState = () => {
}
};
- createEffect((wasPaywallVisible) => {
- const isPaywallVisible = !checkingLicense() && !hasAgentProfiles();
- if (showUpgradePrompts() && isPaywallVisible && !wasPaywallVisible) {
- trackPaywallViewed('agent_profiles', 'settings_agent_profiles_panel');
- }
- return isPaywallVisible;
- }, false);
-
onMount(async () => {
await loadRuntimeCapabilities();
@@ -372,7 +363,6 @@ export const useAgentProfilesPanelState = () => {
showUpgradePrompts,
showModal,
showSuggestModal,
- trackUpgradeClicked,
unknownKeys,
updateSetting,
editingProfile,
diff --git a/frontend-modern/src/components/Settings/useAuditLogPanelState.ts b/frontend-modern/src/components/Settings/useAuditLogPanelState.ts
index d133d1146..8803a8215 100644
--- a/frontend-modern/src/components/Settings/useAuditLogPanelState.ts
+++ b/frontend-modern/src/components/Settings/useAuditLogPanelState.ts
@@ -11,7 +11,6 @@ import { hasFeature, runtimeCapabilitiesLoaded } from '@/stores/license';
import { getUpgradeActionDestination } from '@/stores/licenseCommercial';
import { presentationPolicyHidesUpgradePrompts } from '@/stores/sessionPresentationPolicy';
import { loadRuntimeCapabilities } from '@/stores/license';
-import { trackPaywallViewed, trackUpgradeClicked } from '@/utils/upgradeMetrics';
export interface AuditEvent {
id: string;
@@ -511,18 +510,6 @@ export const useAuditLogPanelState = () => {
const goToNextPage = () => goToOffset(pageOffset() + pageSize());
const goToLastPage = () => goToOffset((totalPages() - 1) * pageSize());
- const handleUpgradeClick = () => {
- trackUpgradeClicked('settings_audit_log_panel', 'audit_logging');
- };
-
- createEffect((wasPaywallVisible) => {
- const isPaywallVisible = showUpgradePaywall();
- if (showUpgradePrompts() && isPaywallVisible && !wasPaywallVisible) {
- trackPaywallViewed('audit_logging', 'settings_audit_log_panel');
- }
- return isPaywallVisible;
- }, false);
-
onMount(() => {
setIsMounted(true);
void loadRuntimeCapabilities();
@@ -586,7 +573,6 @@ export const useAuditLogPanelState = () => {
goToLastPage,
goToNextPage,
goToPreviousPage,
- handleUpgradeClick,
hasNextPage,
hasResumeEvents,
hasSignedEvents,
diff --git a/frontend-modern/src/components/Settings/useAuditWebhookPanelState.ts b/frontend-modern/src/components/Settings/useAuditWebhookPanelState.ts
index 903d8c180..14505e261 100644
--- a/frontend-modern/src/components/Settings/useAuditWebhookPanelState.ts
+++ b/frontend-modern/src/components/Settings/useAuditWebhookPanelState.ts
@@ -2,11 +2,10 @@ import { createEffect, createSignal, onMount } from 'solid-js';
import { apiFetchJSON } from '@/utils/apiClient';
import { logger } from '@/utils/logger';
import { showSuccess, showWarning } from '@/utils/toast';
-import { hasFeature, runtimeCapabilitiesLoaded } from '@/stores/license';
+import { hasFeature } from '@/stores/license';
import { getUpgradeActionDestination } from '@/stores/licenseCommercial';
import { presentationPolicyHidesUpgradePrompts } from '@/stores/sessionPresentationPolicy';
import { loadRuntimeCapabilities } from '@/stores/license';
-import { trackPaywallViewed } from '@/utils/upgradeMetrics';
import {
getAuditWebhookDuplicateUrlMessage,
getAuditWebhookInvalidUrlMessage,
@@ -83,14 +82,6 @@ export const useAuditWebhookPanelState = (canManageOverride?: boolean) => {
loadRuntimeCapabilities();
});
- createEffect((wasPaywallVisible: boolean) => {
- const isPaywallVisible = runtimeCapabilitiesLoaded() && !hasFeature('audit_logging');
- if (showUpgradePrompts() && isPaywallVisible && !wasPaywallVisible) {
- trackPaywallViewed('audit_logging', 'settings_audit_webhook_panel');
- }
- return isPaywallVisible;
- }, false);
-
createEffect(() => {
if (hasFeature('audit_logging')) {
void fetchWebhooks();
diff --git a/frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx b/frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx
index 085d55f67..fe776f2cd 100644
--- a/frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx
+++ b/frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx
@@ -26,10 +26,6 @@ import {
resolveInstallerBaseUrl,
} from '@/utils/agentInstallCommand';
import { getUnifiedAgentClipboardCopyErrorMessage } from '@/utils/unifiedAgentInventoryPresentation';
-import {
- trackAgentInstallProfileSelected,
- trackAgentInstallTokenGenerated,
-} from '@/utils/upgradeMetrics';
import {
buildCommandsByPlatform,
buildDefaultTokenName,
@@ -39,7 +35,6 @@ import {
type AgentPlatform,
type InstallProfile,
type SetupHandoffState,
- UNIFIED_AGENT_TELEMETRY_SURFACE,
} from './infrastructureOperationsModel';
export interface InfrastructureInstallStateOptions {
@@ -256,7 +251,6 @@ Pulse prepares the first-host install token from setup so you can move straight
setTokenName('');
setConfirmedNoToken(false);
setSetupHandoffAutoTokenFailed(false);
- trackAgentInstallTokenGenerated(UNIFIED_AGENT_TELEMETRY_SURFACE, source);
if (options.notifySuccess) {
notificationStore.success(
'Token generated with Agent config + reporting, Docker, and Kubernetes permissions.',
@@ -435,7 +429,6 @@ Pulse prepares the first-host install token from setup so you can move straight
const handleInstallProfileChange = (profile: InstallProfile) => {
setInstallProfile(profile);
- trackAgentInstallProfileSelected(UNIFIED_AGENT_TELEMETRY_SURFACE, profile);
};
const commandSections = createMemo(() => {
diff --git a/frontend-modern/src/components/Settings/useProLicensePanelState.ts b/frontend-modern/src/components/Settings/useProLicensePanelState.ts
index 58687360d..cb31b5b82 100644
--- a/frontend-modern/src/components/Settings/useProLicensePanelState.ts
+++ b/frontend-modern/src/components/Settings/useProLicensePanelState.ts
@@ -49,7 +49,6 @@ import {
SELF_HOSTED_PRO_BILLING_PURCHASE_QUERY_PARAM,
SELF_HOSTED_PRO_BILLING_USAGE_HREF,
SELF_HOSTED_PRO_BILLING_USAGE_ROUTE,
- isSelfHostedPurchaseStartDestination,
type SelfHostedBillingPlanIntent,
type SelfHostedBillingSection,
} from '@/utils/pricingHandoff';
@@ -65,7 +64,6 @@ import {
getMonitoredSystemLimitUsageSummary,
resolveMonitoredSystemCapacityStatus,
} from '@/utils/monitoredSystemPresentation';
-import { trackCheckoutClicked, trackPricingViewed } from '@/utils/upgradeMetrics';
import { resolveUpgradeDestination, type UpgradeDestination } from '@/utils/upgradeNavigation';
import { SELF_HOSTED_PRO_BILLING_PRESENTATION } from './selfHostedBillingPresentation';
@@ -228,27 +226,6 @@ export function useProLicensePanelState() {
navigate(SELF_HOSTED_PRO_BILLING_PLAN_ROUTE, { replace: true, scroll: false });
});
- let trackedPlanPricingView = false;
- createEffect(() => {
- const planVisible =
- panelDataSettled() &&
- activeSection() === 'plan' &&
- getSelfHostedBillingPlanIntent(location.search) ===
- SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT;
- if (!planVisible) {
- trackedPlanPricingView = false;
- return;
- }
- if (trackedPlanPricingView) {
- return;
- }
- trackPricingViewed(
- 'settings_self_hosted_billing_plan',
- SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT,
- );
- trackedPlanPricingView = true;
- });
-
const setActiveSection = (section: string) => {
if (section !== 'plan' && section !== 'usage') {
return;
@@ -296,13 +273,6 @@ export function useProLicensePanelState() {
};
});
- const handlePlanSelectionPromptClick = () => {
- trackCheckoutClicked(
- 'settings_self_hosted_billing_compare_prompt',
- SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT,
- );
- };
-
const statusPresentation = createMemo(() =>
getLicenseSubscriptionStatusPresentation(subscriptionState()),
);
@@ -477,17 +447,6 @@ export function useProLicensePanelState() {
}
});
- const handlePurchaseActivationActionClick = () => {
- const action = purchaseActivationAction();
- if (!action || !isSelfHostedPurchaseStartDestination(action.destination.href)) {
- return;
- }
- trackCheckoutClicked(
- 'settings_self_hosted_billing_purchase_return',
- purchaseActivationIntent() ?? SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT,
- );
- };
-
const commercialMigrationNotice = createMemo(() =>
getCommercialMigrationNotice(entitlements()?.commercial_migration),
);
@@ -630,10 +589,8 @@ export function useProLicensePanelState() {
loading,
looksLikeLegacyLicenseKey,
planSelectionPrompt,
- handlePlanSelectionPromptClick,
purchaseActivationNotice,
purchaseActivationAction,
- handlePurchaseActivationActionClick,
setActiveSection,
setLicenseKey,
showUsageSection,
diff --git a/frontend-modern/src/components/Settings/useRBACFeatureGateState.ts b/frontend-modern/src/components/Settings/useRBACFeatureGateState.ts
index ad4dc2ff9..748daf7c6 100644
--- a/frontend-modern/src/components/Settings/useRBACFeatureGateState.ts
+++ b/frontend-modern/src/components/Settings/useRBACFeatureGateState.ts
@@ -1,9 +1,8 @@
-import { Accessor, createEffect, createMemo, onMount } from 'solid-js';
+import { Accessor, createMemo, onMount } from 'solid-js';
import { hasFeature, runtimeCapabilitiesLoaded } from '@/stores/license';
import { presentationPolicyHidesUpgradePrompts } from '@/stores/sessionPresentationPolicy';
import { loadRuntimeCapabilities } from '@/stores/license';
import { getRBACFeatureGateCopy, type RBACFeatureGateCopy } from '@/utils/rbacPresentation';
-import { trackPaywallViewed } from '@/utils/upgradeMetrics';
export type RBACFeatureGateKind = 'roles' | 'user-assignments';
export type RBACFeatureGateLocation = 'settings_roles_panel' | 'settings_user_assignments_panel';
@@ -29,14 +28,6 @@ export function useRBACFeatureGateState(options: UseRBACFeatureGateStateOptions)
void loadRuntimeCapabilities();
});
- createEffect((wasPaywallVisible) => {
- const isPaywallVisible = paywallVisible();
- if (showUpgradePrompts() && isPaywallVisible && !wasPaywallVisible) {
- trackPaywallViewed('rbac', options.paywallLocation);
- }
- return isPaywallVisible;
- }, false);
-
return {
featureGateCopy,
licenseReady,
diff --git a/frontend-modern/src/components/Settings/useRelaySettingsPanelState.ts b/frontend-modern/src/components/Settings/useRelaySettingsPanelState.ts
index 316bee683..124e31dc7 100644
--- a/frontend-modern/src/components/Settings/useRelaySettingsPanelState.ts
+++ b/frontend-modern/src/components/Settings/useRelaySettingsPanelState.ts
@@ -1,9 +1,8 @@
-import { createEffect, createMemo, createSignal, onCleanup, onMount } from 'solid-js';
-import { hasFeature, runtimeCapabilitiesLoaded } from '@/stores/license';
+import { createMemo, createSignal, onCleanup, onMount } from 'solid-js';
+import { hasFeature } from '@/stores/license';
import { getUpgradeActionDestination } from '@/stores/licenseCommercial';
import { presentationPolicyHidesUpgradePrompts } from '@/stores/sessionPresentationPolicy';
import { loadRuntimeCapabilities } from '@/stores/license';
-import { trackPaywallViewed } from '@/utils/upgradeMetrics';
import { showError, showSuccess } from '@/utils/toast';
import { RelayAPI, type RelayConfig, type RelayStatus } from '@/api/relay';
import { OnboardingAPI, type OnboardingQRResponse } from '@/api/onboarding';
@@ -41,14 +40,6 @@ export function useRelaySettingsPanelState(props: RelaySettingsPanelProps) {
const statusErrorMessage = createMemo(() => getRelayStatusErrorMessage(status()));
const canShowPairing = createMemo(() => Boolean(config()?.enabled && status()?.connected));
- createEffect((wasPaywallVisible: boolean) => {
- const isPaywallVisible = runtimeCapabilitiesLoaded() && !relayEnabled();
- if (showUpgradePrompts() && isPaywallVisible && !wasPaywallVisible) {
- trackPaywallViewed('relay', 'settings_relay_panel');
- }
- return isPaywallVisible;
- }, false);
-
let statusInterval: ReturnType | undefined;
const resetPairingState = () => {
diff --git a/frontend-modern/src/components/Settings/useReportingPanelState.ts b/frontend-modern/src/components/Settings/useReportingPanelState.ts
index 5352f355f..9295b2a13 100644
--- a/frontend-modern/src/components/Settings/useReportingPanelState.ts
+++ b/frontend-modern/src/components/Settings/useReportingPanelState.ts
@@ -6,7 +6,6 @@ import { hasFeature, runtimeCapabilitiesLoaded } from '@/stores/license';
import { getUpgradeActionDestination } from '@/stores/licenseCommercial';
import { presentationPolicyHidesUpgradePrompts } from '@/stores/sessionPresentationPolicy';
import { loadRuntimeCapabilities } from '@/stores/license';
-import { trackPaywallViewed } from '@/utils/upgradeMetrics';
import {
getReportingCatalogErrorMessage,
getReportingGenerateErrorMessage,
@@ -58,14 +57,6 @@ export const useReportingPanelState = () => {
loadRuntimeCapabilities();
});
- createEffect((wasVisible: boolean) => {
- const visible = isLocked();
- if (showUpgradePrompts() && visible && !wasVisible) {
- trackPaywallViewed(reportingFeatureId(), 'settings_reporting_panel');
- }
- return visible;
- }, false);
-
const loadReportingCatalog = async () => {
if (reportingCatalogLoading()) {
return;
diff --git a/frontend-modern/src/components/Settings/useSSOProvidersState.ts b/frontend-modern/src/components/Settings/useSSOProvidersState.ts
index 4460017e3..52e4246ab 100644
--- a/frontend-modern/src/components/Settings/useSSOProvidersState.ts
+++ b/frontend-modern/src/components/Settings/useSSOProvidersState.ts
@@ -1,4 +1,4 @@
-import { createEffect, createMemo, createSignal, onMount } from 'solid-js';
+import { createMemo, createSignal, onMount } from 'solid-js';
import { createStore } from 'solid-js/store';
import { notificationStore } from '@/stores/notifications';
import { logger } from '@/utils/logger';
@@ -6,7 +6,6 @@ import { hasFeature, runtimeCapabilitiesLoaded } from '@/stores/license';
import { getUpgradeActionDestination } from '@/stores/licenseCommercial';
import { presentationPolicyHidesUpgradePrompts } from '@/stores/sessionPresentationPolicy';
import { loadRuntimeCapabilities } from '@/stores/license';
-import { trackPaywallViewed, trackUpgradeClicked } from '@/utils/upgradeMetrics';
import {
getSSOCopySuccessMessage,
getSSOConnectionTestErrorMessage,
@@ -66,23 +65,6 @@ export const useSSOProvidersState = (props: SSOProvidersPanelProps) => {
const canManage = () => props.canManage !== false;
const showUpgradePrompts = () => !presentationPolicyHidesUpgradePrompts();
- createEffect((wasBannerVisible) => {
- const isBannerVisible =
- showUpgradePrompts() && runtimeCapabilitiesLoaded() && !hasAdvancedSSO() && !loading();
- if (isBannerVisible && !wasBannerVisible) {
- trackPaywallViewed('advanced_sso', 'settings_sso_providers_banner');
- }
- return isBannerVisible;
- }, false);
-
- createEffect((wasUpsellVisible) => {
- const isUpsellVisible = showUpgradePrompts() && showSamlUpsell();
- if (isUpsellVisible && !wasUpsellVisible) {
- trackPaywallViewed('advanced_sso', 'settings_sso_providers_add_saml_gate');
- }
- return isUpsellVisible;
- }, false);
-
const loadProviders = async () => {
setLoading(true);
try {
@@ -347,6 +329,5 @@ export const useSSOProvidersState = (props: SSOProvidersPanelProps) => {
fetchMetadataPreview,
getUpgradeActionDestination,
runtimeCapabilitiesLoaded,
- trackUpgradeClicked,
};
};
diff --git a/frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx b/frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx
index e7483a0e7..7697cb375 100644
--- a/frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx
+++ b/frontend-modern/src/components/SetupWizard/SetupCompletionPanel.tsx
@@ -6,7 +6,6 @@ import { getPulseBaseUrl } from '@/utils/url';
import type { State } from '@/types/api';
import type { Resource } from '@/types/resource';
import { buildInfrastructureOnboardingPath } from '@/components/Settings/infrastructureWorkspaceModel';
-import { trackAgentFirstConnected } from '@/utils/upgradeMetrics';
import type { WizardState } from '../SetupWizard';
import {
buildSetupCompletionConnectedSystems,
@@ -36,7 +35,6 @@ const SOURCE_STRATEGY_OPTIONS = [
const ADD_INFRASTRUCTURE_PATH = buildInfrastructureOnboardingPath('pick');
const AGENT_INSTALL_PATH = buildInfrastructureOnboardingPath('agent');
-const SETUP_WIZARD_TELEMETRY_SURFACE = 'setup_wizard_complete';
export const SetupCompletionPanel: Component = (props) => {
const [copied, setCopied] = createSignal<'password' | 'admin-token' | null>(null);
@@ -44,7 +42,6 @@ export const SetupCompletionPanel: Component = (props) => {
const [connectedSystems, setConnectedSystems] = createSignal<
ReturnType
>([]);
- let firstAgentConnectionTracked = false;
createEffect(() => {
if (props.connectedResourcesOverride !== undefined) {
@@ -81,14 +78,6 @@ export const SetupCompletionPanel: Component = (props) => {
);
});
- if (
- !firstAgentConnectionTracked &&
- nextConnectedSystems.some((system) => system.connectionPath === 'agent')
- ) {
- trackAgentFirstConnected(SETUP_WIZARD_TELEMETRY_SURFACE, 'first_agent');
- firstAgentConnectionTracked = true;
- }
-
if (hasConnectionChanges) {
setConnectedSystems(nextConnectedSystems);
}
diff --git a/frontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPanel.guardrails.test.ts b/frontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPanel.guardrails.test.ts
index 2a2c328d7..d363cddf4 100644
--- a/frontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPanel.guardrails.test.ts
+++ b/frontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPanel.guardrails.test.ts
@@ -31,6 +31,8 @@ describe('SetupCompletionPanel guardrails', () => {
expect(setupCompletionPanelSource).not.toContain('Monitor from Anywhere');
expect(setupCompletionPanelSource).not.toContain("trackPaywallViewed('relay'");
expect(setupCompletionPanelSource).not.toContain("trackUpgradeClicked('setup_wizard'");
+ expect(setupCompletionPanelSource).not.toContain('infrastructureOnboardingMetrics');
+ expect(setupCompletionPanelSource).not.toContain('trackAgentFirstConnected');
expect(setupCompletionPanelSource).not.toContain('getUpgradeActionUrlOrFallback');
});
diff --git a/frontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPanel.test.tsx b/frontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPanel.test.tsx
index 1a46dee7d..81dd8bc43 100644
--- a/frontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPanel.test.tsx
+++ b/frontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPanel.test.tsx
@@ -7,7 +7,6 @@ const copyToClipboardMock = vi.fn();
const apiFetchJSONMock = vi.fn();
const createObjectURLMock = vi.fn(() => 'blob:mock-url');
const revokeObjectURLMock = vi.fn();
-const trackAgentFirstConnectedMock = vi.fn();
class MockBlob {
readonly parts: string[];
@@ -50,10 +49,6 @@ vi.mock('@/utils/logger', () => ({
},
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackAgentFirstConnected: (...args: unknown[]) => trackAgentFirstConnectedMock(...args),
-}));
-
const baseState: WizardState = {
username: 'admin',
password: 'password',
@@ -213,10 +208,6 @@ describe('SetupCompletionPanel', () => {
expect(screen.getAllByRole('button', { name: 'Open Infrastructure' }).length).toBeGreaterThan(0);
expect(screen.getAllByRole('button', { name: 'Add infrastructure' }).length).toBeGreaterThan(0);
expect(screen.queryByRole('button', { name: 'Install Pulse Agent' })).not.toBeInTheDocument();
- expect(trackAgentFirstConnectedMock).toHaveBeenCalledWith(
- 'setup_wizard_complete',
- 'first_agent',
- );
const nextStepHeading = screen.getByRole('heading', { name: 'Open Infrastructure' });
const nextStepCard = nextStepHeading.closest('[aria-label="Setup next step"]');
@@ -271,7 +262,6 @@ describe('SetupCompletionPanel', () => {
expect(screen.getByRole('button', { name: 'Open Infrastructure' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Add infrastructure' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Install Pulse Agent' })).not.toBeInTheDocument();
- expect(trackAgentFirstConnectedMock).not.toHaveBeenCalled();
const nextStepHeading = screen.getByRole('heading', { name: 'Open Infrastructure' });
const nextStepCard = nextStepHeading.closest('[aria-label="Setup next step"]');
@@ -375,10 +365,6 @@ describe('SetupCompletionPanel', () => {
).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Add infrastructure' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Install Pulse Agent' })).not.toBeInTheDocument();
- expect(trackAgentFirstConnectedMock).toHaveBeenCalledWith(
- 'setup_wizard_complete',
- 'first_agent',
- );
});
it('keeps connected governed infrastructure on local operator identity', async () => {
diff --git a/frontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx b/frontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx
index 46ac53331..66f5f57f7 100644
--- a/frontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx
+++ b/frontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx
@@ -28,7 +28,6 @@ export const MonitoredSystemLimitWarningBanner: Component = () => {
{MONITORED_SYSTEM_LIMIT_INSTALL_COLLECTORS_LABEL}
diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts
index 7ac1662e4..5ee38ed3a 100644
--- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts
+++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts
@@ -897,7 +897,7 @@ describe('shared primitive guardrails', () => {
expect(monitoredSystemLimitWarningBannerStateSource).toContain(
'export function useMonitoredSystemLimitWarningBannerState',
);
- expect(monitoredSystemLimitWarningBannerStateSource).toContain('createEffect');
+ expect(monitoredSystemLimitWarningBannerStateSource).not.toContain('createEffect');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('createMemo');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('loadRuntimeCapabilities');
expect(monitoredSystemLimitWarningBannerStateSource).toContain(
@@ -910,7 +910,7 @@ describe('shared primitive guardrails', () => {
expect(monitoredSystemLimitWarningBannerStateSource).toContain(
'presentationPolicyHidesUpgradePrompts',
);
- expect(monitoredSystemLimitWarningBannerStateSource).toContain('trackUpgradeMetricEvent');
+ expect(monitoredSystemLimitWarningBannerStateSource).not.toContain('trackUpgradeMetricEvent');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('hasMigrationGap');
expect(monitoredSystemLimitWarningBannerStateSource).not.toContain(
'scopeSelfHostedBillingDestination',
diff --git a/frontend-modern/src/components/shared/__tests__/MonitoredSystemLimitWarningBanner.test.tsx b/frontend-modern/src/components/shared/__tests__/MonitoredSystemLimitWarningBanner.test.tsx
index 8742c5cac..73aa7ea5e 100644
--- a/frontend-modern/src/components/shared/__tests__/MonitoredSystemLimitWarningBanner.test.tsx
+++ b/frontend-modern/src/components/shared/__tests__/MonitoredSystemLimitWarningBanner.test.tsx
@@ -32,8 +32,6 @@ const mockLegacyConnections = vi.hoisted(() =>
kubernetes_clusters: 0,
})),
);
-const mockTrackUpgradeMetricEvent = vi.hoisted(() => vi.fn());
-const mockTrackUpgradeClicked = vi.hoisted(() => vi.fn());
const mockLoadRuntimeLicenseStatus = vi.hoisted(() => vi.fn());
const mockPresentationPolicyHidesCommercialSurfaces = vi.hoisted(() => vi.fn(() => false));
const mockPresentationPolicyHidesUpgradePrompts = vi.hoisted(() => vi.fn(() => false));
@@ -60,14 +58,6 @@ vi.mock('@/stores/sessionPresentationPolicy', () => ({
presentationPolicyHidesUpgradePrompts: () => mockPresentationPolicyHidesUpgradePrompts(),
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- UPGRADE_METRIC_EVENTS: {
- LIMIT_WARNING_SHOWN: 'limit_warning_shown',
- },
- trackUpgradeMetricEvent: mockTrackUpgradeMetricEvent,
- trackUpgradeClicked: mockTrackUpgradeClicked,
-}));
-
describe('MonitoredSystemLimitWarningBanner', () => {
beforeEach(() => {
localStorage.clear();
@@ -126,7 +116,7 @@ describe('MonitoredSystemLimitWarningBanner', () => {
expect(monitoredSystemLimitWarningBannerStateSource).toContain(
'export function useMonitoredSystemLimitWarningBannerState',
);
- expect(monitoredSystemLimitWarningBannerStateSource).toContain('createEffect');
+ expect(monitoredSystemLimitWarningBannerStateSource).not.toContain('createEffect');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('createMemo');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('loadRuntimeCapabilities');
expect(monitoredSystemLimitWarningBannerStateSource).not.toContain('loadCommercialPosture');
@@ -137,7 +127,7 @@ describe('MonitoredSystemLimitWarningBanner', () => {
expect(monitoredSystemLimitWarningBannerStateSource).toContain(
'presentationPolicyHidesUpgradePrompts',
);
- expect(monitoredSystemLimitWarningBannerStateSource).toContain('trackUpgradeMetricEvent');
+ expect(monitoredSystemLimitWarningBannerStateSource).not.toContain('trackUpgradeMetricEvent');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('hasMigrationGap');
expect(monitoredSystemLimitWarningBannerStateSource).not.toContain(
'scopeSelfHostedBillingDestination',
@@ -244,7 +234,6 @@ describe('MonitoredSystemLimitWarningBanner', () => {
expect(screen.queryByText(/Monitored systems:/i)).not.toBeInTheDocument();
expect(screen.queryByText('Review options')).not.toBeInTheDocument();
- expect(mockTrackUpgradeMetricEvent).not.toHaveBeenCalled();
});
it('keeps urgent limit warnings visible with migration context', async () => {
@@ -299,7 +288,6 @@ describe('MonitoredSystemLimitWarningBanner', () => {
expect(
screen.queryByText('5 monitored systems.'),
).not.toBeInTheDocument();
- expect(mockTrackUpgradeMetricEvent).not.toHaveBeenCalled();
});
it('stays hidden when self-hosted upgrade prompts are suppressed', async () => {
@@ -322,7 +310,6 @@ describe('MonitoredSystemLimitWarningBanner', () => {
expect(
screen.queryByText('5 monitored systems.'),
).not.toBeInTheDocument();
- expect(mockTrackUpgradeMetricEvent).not.toHaveBeenCalled();
});
it('stays hidden for self-hosted installs even when stale continuity metadata is urgent', async () => {
@@ -343,6 +330,5 @@ describe('MonitoredSystemLimitWarningBanner', () => {
expect(
screen.queryByText('5 monitored systems.'),
).not.toBeInTheDocument();
- expect(mockTrackUpgradeMetricEvent).not.toHaveBeenCalled();
});
});
diff --git a/frontend-modern/src/components/shared/useMonitoredSystemLimitWarningBannerState.ts b/frontend-modern/src/components/shared/useMonitoredSystemLimitWarningBannerState.ts
index 8bd044c7c..d24a53c32 100644
--- a/frontend-modern/src/components/shared/useMonitoredSystemLimitWarningBannerState.ts
+++ b/frontend-modern/src/components/shared/useMonitoredSystemLimitWarningBannerState.ts
@@ -1,4 +1,4 @@
-import { createEffect, createMemo, onMount } from 'solid-js';
+import { createMemo, onMount } from 'solid-js';
import {
presentationPolicyHidesCommercialSurfaces,
presentationPolicyHidesUpgradePrompts,
@@ -11,11 +11,6 @@ import {
} from '@/stores/license';
import { hasMigrationGap } from '@/stores/licenseCommercial';
import { resolveUpgradeDestination } from '@/utils/upgradeNavigation';
-import {
- trackUpgradeClicked,
- trackUpgradeMetricEvent,
- UPGRADE_METRIC_EVENTS,
-} from '@/utils/upgradeMetrics';
import {
getMonitoredSystemBannerToneClass,
getMonitoredSystemSummary,
@@ -54,32 +49,8 @@ export function useMonitoredSystemLimitWarningBannerState() {
const installCollectorsDestination = createMemo(() =>
resolveUpgradeDestination(MONITORED_SYSTEM_LIMIT_INSTALL_COLLECTORS_HREF),
);
- let wasUrgent = false;
- createEffect(() => {
- const urgent = isUrgent();
- const visible = showBanner();
- const limit = monitoredSystemLimit();
- if (visible && 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 = visible && urgent;
- });
-
- const handleInstallCollectorsClick = () => {
- trackUpgradeClicked(
- 'monitored_system_limit_banner_install_v6_collectors',
- MONITORED_SYSTEM_LIMIT_KEY,
- );
- };
return {
- handleInstallCollectorsClick,
installCollectorsDestination,
isUrgent,
reviewPolicyDestination,
diff --git a/frontend-modern/src/pages/CloudPricing.tsx b/frontend-modern/src/pages/CloudPricing.tsx
index f57dca7b1..34a408432 100644
--- a/frontend-modern/src/pages/CloudPricing.tsx
+++ b/frontend-modern/src/pages/CloudPricing.tsx
@@ -2,8 +2,6 @@ import { For, Show } from 'solid-js';
import { A } from '@solidjs/router';
import { Card } from '@/components/shared/Card';
import { PageHeader } from '@/components/shared/PageHeader';
-import { trackPaywallViewed } from '@/utils/upgradeMetrics';
-import { onMount } from 'solid-js';
import {
CLOUD_COMMERCIAL_PRESENTATION,
CLOUD_PLAN_DEFINITIONS,
@@ -81,10 +79,6 @@ function CloudTierCard(props: { tier: CloudPlanDefinition }) {
// ---------------------------------------------------------------------------
export default function CloudPricing() {
- onMount(() => {
- trackPaywallViewed('cloud_pricing', 'cloud_pricing_page');
- });
-
return (
{
- trackPaywallViewed('pricing', 'pricing_handoff');
if (externalDestination() || selfHostedPurchaseStartDestination()) {
handoffToExternalPricing(destination());
}
diff --git a/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx b/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx
index 6beb0f60d..c8fbc918d 100644
--- a/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx
+++ b/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx
@@ -107,8 +107,6 @@ const loadLicenseStatusMock = vi.fn();
const loadCommercialPostureMock = vi.fn();
const getUpgradeActionDestinationMock = vi.fn();
const getUpgradeActionUrlOrFallbackMock = vi.fn();
-const trackPaywallViewedMock = vi.fn();
-const trackUpgradeClickedMock = vi.fn();
const presentationPolicyHidesUpgradePromptsMock = vi.fn();
const notificationSuccessMock = vi.fn();
const notificationErrorMock = vi.fn();
@@ -148,11 +146,6 @@ vi.mock('@/stores/licenseCommercial', () => ({
getUpgradeActionUrlOrFallback: (...args: unknown[]) => getUpgradeActionUrlOrFallbackMock(...args),
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: (...args: unknown[]) => trackPaywallViewedMock(...args),
- trackUpgradeClicked: (...args: unknown[]) => trackUpgradeClickedMock(...args),
-}));
-
vi.mock('@/stores/sessionPresentationPolicy', () => ({
presentationPolicyHidesUpgradePrompts: () => presentationPolicyHidesUpgradePromptsMock(),
}));
@@ -343,8 +336,6 @@ describe('AIIntelligence entitlement gating', () => {
loadCommercialPostureMock.mockReset();
getUpgradeActionDestinationMock.mockReset();
getUpgradeActionUrlOrFallbackMock.mockReset();
- trackPaywallViewedMock.mockReset();
- trackUpgradeClickedMock.mockReset();
presentationPolicyHidesUpgradePromptsMock.mockReset();
notificationSuccessMock.mockReset();
notificationErrorMock.mockReset();
@@ -583,7 +574,6 @@ describe('AIIntelligence entitlement gating', () => {
expect(screen.queryByRole('link', { name: 'Upgrade to Pro' })).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Upgrade' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: /start free trial/i })).not.toBeInTheDocument();
- expect(trackPaywallViewedMock).not.toHaveBeenCalled();
});
it('locks paid patrol controls without upgrade prompts in default self-hosted mode', async () => {
@@ -610,7 +600,6 @@ describe('AIIntelligence entitlement gating', () => {
expect(screen.queryByRole('link', { name: 'Upgrade to Pro' })).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Upgrade' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: /start free trial/i })).not.toBeInTheDocument();
- expect(trackPaywallViewedMock).not.toHaveBeenCalled();
});
it('unlocks paid patrol controls when the entitlement grants the features', async () => {
@@ -633,7 +622,6 @@ describe('AIIntelligence entitlement gating', () => {
expect(screen.getByRole('button', { name: 'Remediate' })).not.toBeDisabled();
expect(screen.queryByRole('link', { name: 'Upgrade to Pro' })).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Upgrade' })).not.toBeInTheDocument();
- expect(trackPaywallViewedMock).not.toHaveBeenCalled();
});
it('renders the canonical intelligence summary card with recent changes', async () => {
diff --git a/frontend-modern/src/pages/__tests__/Alerts.readOnly.test.tsx b/frontend-modern/src/pages/__tests__/Alerts.readOnly.test.tsx
index b731acecd..a845f6a8d 100644
--- a/frontend-modern/src/pages/__tests__/Alerts.readOnly.test.tsx
+++ b/frontend-modern/src/pages/__tests__/Alerts.readOnly.test.tsx
@@ -85,10 +85,6 @@ vi.mock('@/utils/logger', () => ({
logger: { error: vi.fn() },
}));
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: vi.fn(),
-}));
-
vi.mock('@/features/alerts/OverviewTab', () => ({
OverviewTab: overviewTabSpy,
}));
diff --git a/frontend-modern/src/pages/__tests__/CloudPricing.test.tsx b/frontend-modern/src/pages/__tests__/CloudPricing.test.tsx
index ea98e4458..c5818f499 100644
--- a/frontend-modern/src/pages/__tests__/CloudPricing.test.tsx
+++ b/frontend-modern/src/pages/__tests__/CloudPricing.test.tsx
@@ -1,13 +1,9 @@
-import { afterEach, describe, expect, it, vi } from 'vitest';
+import { afterEach, describe, expect, it } from 'vitest';
import { cleanup, render, screen } from '@solidjs/testing-library';
import { Router, Route } from '@solidjs/router';
import CloudPricing from '@/pages/CloudPricing';
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: vi.fn(),
-}));
-
describe('CloudPricing', () => {
afterEach(() => {
cleanup();
diff --git a/frontend-modern/src/pages/__tests__/PricingHandoff.test.tsx b/frontend-modern/src/pages/__tests__/PricingHandoff.test.tsx
index d05c343f2..60289d488 100644
--- a/frontend-modern/src/pages/__tests__/PricingHandoff.test.tsx
+++ b/frontend-modern/src/pages/__tests__/PricingHandoff.test.tsx
@@ -5,13 +5,8 @@ import PricingHandoff from '@/pages/PricingHandoff';
import pricingHandoffSource from '@/pages/PricingHandoff.tsx?raw';
import { getSelfHostedPurchaseStartUrl } from '@/utils/pricingHandoff';
-const trackPaywallViewedMock = vi.fn();
const handoffToExternalPricingMock = vi.fn();
-vi.mock('@/utils/upgradeMetrics', () => ({
- trackPaywallViewed: (...args: unknown[]) => trackPaywallViewedMock(...args),
-}));
-
vi.mock('@/utils/pricingHandoff', async () => {
const actual =
await vi.importActual('@/utils/pricingHandoff');
@@ -23,7 +18,6 @@ vi.mock('@/utils/pricingHandoff', async () => {
describe('PricingHandoff', () => {
beforeEach(() => {
- trackPaywallViewedMock.mockReset();
handoffToExternalPricingMock.mockReset();
window.scrollTo = vi.fn();
});
diff --git a/frontend-modern/src/utils/__tests__/agentProfilesPresentation.test.ts b/frontend-modern/src/utils/__tests__/agentProfilesPresentation.test.ts
index df38c4dbc..5bbe15146 100644
--- a/frontend-modern/src/utils/__tests__/agentProfilesPresentation.test.ts
+++ b/frontend-modern/src/utils/__tests__/agentProfilesPresentation.test.ts
@@ -1,10 +1,19 @@
import { describe, expect, it } from 'vitest';
import {
getAgentProfileAssignmentsEmptyState,
+ getAgentProfilesFeatureGateCopy,
getAgentProfilesEmptyState,
} from '@/utils/agentProfilesPresentation';
describe('agentProfilesPresentation', () => {
+ it('returns capability-focused feature gate copy', () => {
+ expect(getAgentProfilesFeatureGateCopy()).toMatchObject({
+ title: 'Agent Profiles',
+ subtitle: 'Centralized agent configuration',
+ body: expect.not.stringContaining('Pro'),
+ });
+ });
+
it('returns canonical agent profile empty-state copy', () => {
expect(getAgentProfilesEmptyState()).toBe('No profiles yet. Create one to get started.');
expect(getAgentProfileAssignmentsEmptyState()).toBe(
diff --git a/frontend-modern/src/utils/__tests__/aiControlLevelPresentation.test.ts b/frontend-modern/src/utils/__tests__/aiControlLevelPresentation.test.ts
index 6a1101c37..c3fe9aa38 100644
--- a/frontend-modern/src/utils/__tests__/aiControlLevelPresentation.test.ts
+++ b/frontend-modern/src/utils/__tests__/aiControlLevelPresentation.test.ts
@@ -40,7 +40,7 @@ describe('aiControlLevelPresentation', () => {
});
expect(getAIChatControlLevelPresentation('autonomous')).toMatchObject({
label: 'Autonomous',
- description: 'Executes without approval (Pro)',
+ description: 'Executes without approval',
dotClassName: 'bg-red-500',
});
});
diff --git a/frontend-modern/src/utils/__tests__/auditWebhookPresentation.test.ts b/frontend-modern/src/utils/__tests__/auditWebhookPresentation.test.ts
index a0a73ceaa..c1ed5b3e3 100644
--- a/frontend-modern/src/utils/__tests__/auditWebhookPresentation.test.ts
+++ b/frontend-modern/src/utils/__tests__/auditWebhookPresentation.test.ts
@@ -11,9 +11,10 @@ import {
describe('auditWebhookPresentation', () => {
it('returns canonical feature gate copy', () => {
expect(getAuditWebhookFeatureGateCopy()).toMatchObject({
- title: 'Audit Webhooks (Pro)',
- body: expect.stringContaining('require Pro'),
+ title: 'Audit Webhooks',
+ body: expect.stringContaining('paid self-hosted and hosted plans'),
});
+ expect(getAuditWebhookFeatureGateCopy().body).not.toContain('Pro');
});
it('returns neutral feature gate copy when commercial prompts are hidden', () => {
diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts
index b69f65fdd..5e061a84a 100644
--- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts
+++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts
@@ -1650,8 +1650,13 @@ describe('frontend resource type boundaries', () => {
expect(userAssignmentsPanelSource).not.toContain('Users sync on first login');
expect(rbacFeatureGateStateSource).toContain('getRBACFeatureGateCopy');
expect(rbacPresentationSource).toContain('export function getRBACFeatureGateCopy');
+ expect(rbacPresentationSource).not.toContain('(Pro)');
expect(rbacPresentationSource).toContain('export function getRolesEmptyState');
expect(rbacPresentationSource).toContain('export function getUserAssignmentsEmptyStateCopy');
+ expect(agentProfilesPanelSource).not.toContain('Pro feature');
+ expect(agentProfilesPresentationSource).toContain(
+ 'export function getAgentProfilesFeatureGateCopy',
+ );
expect(agentProfilesPresentationSource).toContain('export function getAgentProfilesEmptyState');
expect(agentProfilesPresentationSource).toContain(
'export function getAgentProfileAssignmentsEmptyState',
@@ -2933,10 +2938,10 @@ describe('frontend resource type boundaries', () => {
expect(monitoredSystemLimitWarningBannerSource).not.toContain('loadRuntimeCapabilities');
expect(monitoredSystemLimitWarningBannerSource).not.toContain('trackUpgradeMetricEvent');
expect(monitoredSystemLimitWarningBannerSource).not.toContain('legacyConnections()');
- expect(monitoredSystemLimitWarningBannerStateSource).toContain('createEffect');
+ expect(monitoredSystemLimitWarningBannerStateSource).not.toContain('createEffect');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('createMemo');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('loadRuntimeCapabilities');
- expect(monitoredSystemLimitWarningBannerStateSource).toContain('trackUpgradeMetricEvent');
+ expect(monitoredSystemLimitWarningBannerStateSource).not.toContain('trackUpgradeMetricEvent');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('hasMigrationGap');
expect(monitoredSystemLimitWarningBannerStateSource).toContain('reviewPolicyDestination');
expect(monitoredSystemLimitWarningBannerStateSource).not.toContain('handleUpgradeClick');
@@ -4130,6 +4135,8 @@ describe('frontend resource type boundaries', () => {
expect(ssoProvidersPanelSource).not.toContain('const loadProviders = async () =>');
expect(ssoProvidersPanelSource).not.toContain('const handleSave = async (');
expect(ssoProvidersPanelSource).not.toContain('const testConnection = async () =>');
+ expect(ssoProvidersPanelSource).not.toContain('Pro feature');
+ expect(ssoProvidersPanelSource).not.toContain('requires Pro');
expect(ssoProvidersPanelSource).not.toContain('provider.type.toUpperCase()');
expect(ssoProvidersPanelSource).not.toContain("provider.type === 'oidc' ? (");
expect(ssoProviderPresentationSource).toContain('export function getSSOProviderTypeLabel');
@@ -4143,6 +4150,8 @@ describe('frontend resource type boundaries', () => {
expect(ssoProviderPresentationSource).toContain(
'export function getSSOProviderEmptyStateDescription',
);
+ expect(ssoProviderPresentationSource).toContain('export function getSSOSamlFeatureGateCopy');
+ expect(ssoProviderPresentationSource).not.toContain('(Pro)');
expect(ssoProviderPresentationSource).toContain('export function getSSOProvidersLoadingState');
expect(ssoProviderPresentationSource).toContain('export function getSSOTestResultPresentation');
expect(ssoProviderPresentationSource).toContain(
@@ -4194,6 +4203,8 @@ describe('frontend resource type boundaries', () => {
expect(auditWebhookPresentationSource).toContain(
'export function getAuditWebhookFeatureGateCopy',
);
+ expect(auditWebhookPresentationSource).not.toContain('Audit Webhooks (Pro)');
+ expect(auditWebhookPresentationSource).not.toContain('require Pro');
expect(auditWebhookPresentationSource).toContain(
'export function getAuditWebhookEmptyStateCopy',
);
@@ -4214,7 +4225,7 @@ describe('frontend resource type boundaries', () => {
expect(auditWebhookPresentationSource).toContain('AUDIT_WEBHOOK_SECURITY_NOTE_BODY');
expect(auditWebhookStateSource).toContain('export const useAuditWebhookPanelState =');
expect(auditWebhookStateSource).toContain('loadRuntimeCapabilities();');
- expect(auditWebhookStateSource).toContain('trackPaywallViewed');
+ expect(auditWebhookStateSource).not.toContain('trackPaywallViewed');
expect(auditWebhookStateSource).toContain('const fetchWebhooks = async () =>');
expect(auditWebhookStateSource).toContain('const saveWebhooks = async (urls: string[]) =>');
expect(auditLogPanelSource).toContain('getAuditLogLoadingState');
@@ -4231,7 +4242,7 @@ describe('frontend resource type boundaries', () => {
expect(auditLogStateSource).toContain('createLocalStorageStringSignal');
expect(auditLogStateSource).toContain('const fetchAuditEvents = async (');
expect(auditLogStateSource).toContain('const verifyAllEvents = async (');
- expect(auditLogStateSource).toContain('trackPaywallViewed');
+ expect(auditLogStateSource).not.toContain('trackPaywallViewed');
expect(auditLogPanelSource).toContain('getAuditEventTypeBadgeClass');
expect(auditLogPanelSource).toContain('getAuditVerificationBadgePresentation');
expect(auditLogPanelSource).toContain('getAuditEventStatusPresentation');
diff --git a/frontend-modern/src/utils/__tests__/infrastructureOnboardingMetrics.test.ts b/frontend-modern/src/utils/__tests__/infrastructureOnboardingMetrics.test.ts
deleted file mode 100644
index 1f8b9101c..000000000
--- a/frontend-modern/src/utils/__tests__/infrastructureOnboardingMetrics.test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import {
- clearSharedInfrastructureOnboardingMetricsTracker,
- createInfrastructureOnboardingMetricsTracker,
- getSharedInfrastructureOnboardingMetricsTracker,
-} from '@/utils/infrastructureOnboardingMetrics';
-import infrastructureOnboardingMetricsSource from '@/utils/infrastructureOnboardingMetrics.ts?raw';
-
-describe('infrastructureOnboardingMetrics', () => {
- it('does not bridge infrastructure onboarding to maintainer analytics', () => {
- expect(infrastructureOnboardingMetricsSource).not.toContain('trackUpgradeMetricEvent');
- expect(infrastructureOnboardingMetricsSource).not.toContain('UPGRADE_METRIC_EVENTS');
- expect(infrastructureOnboardingMetricsSource).not.toContain('/api/upgrade-metrics/events');
- expect(infrastructureOnboardingMetricsSource).not.toContain('sessionStorage');
- });
-
- it('keeps the tracker contract callable as a compatibility no-op', () => {
- const tracker = createInfrastructureOnboardingMetricsTracker();
-
- tracker.recordOpened();
- tracker.recordOpened();
- tracker.recordPathSelected('api');
- tracker.recordPathSelected('api');
- tracker.recordProbeResult('no-match');
- tracker.recordProbeResult('error');
- tracker.recordCatalogSelected('truenas');
- tracker.recordCatalogSelected('truenas');
- tracker.recordCredentialsOpened('truenas');
- tracker.recordCredentialsOpened('truenas');
- });
-
- it('returns the same no-op tracker for created and shared flows', () => {
- const firstTracker = createInfrastructureOnboardingMetricsTracker();
- const secondTracker = getSharedInfrastructureOnboardingMetricsTracker();
-
- expect(firstTracker).toBe(secondTracker);
-
- clearSharedInfrastructureOnboardingMetricsTracker();
- const thirdTracker = getSharedInfrastructureOnboardingMetricsTracker();
-
- expect(thirdTracker).toBe(firstTracker);
- });
-});
diff --git a/frontend-modern/src/utils/__tests__/rbacPresentation.test.ts b/frontend-modern/src/utils/__tests__/rbacPresentation.test.ts
index 40e7e4037..6b136c92d 100644
--- a/frontend-modern/src/utils/__tests__/rbacPresentation.test.ts
+++ b/frontend-modern/src/utils/__tests__/rbacPresentation.test.ts
@@ -14,10 +14,12 @@ import {
describe('rbacPresentation', () => {
it('returns canonical feature gate copy', () => {
expect(getRBACFeatureGateCopy('roles')).toMatchObject({
- title: 'Custom Roles (Pro)',
+ title: 'Custom Roles',
+ body: expect.stringContaining('paid self-hosted and hosted plans'),
});
expect(getRBACFeatureGateCopy('user-assignments')).toMatchObject({
- title: 'Centralized Access Control (Pro)',
+ title: 'Centralized Access Control',
+ body: expect.stringContaining('paid self-hosted and hosted plans'),
});
});
diff --git a/frontend-modern/src/utils/__tests__/ssoProviderPresentation.test.ts b/frontend-modern/src/utils/__tests__/ssoProviderPresentation.test.ts
index 1f1bc0097..301823524 100644
--- a/frontend-modern/src/utils/__tests__/ssoProviderPresentation.test.ts
+++ b/frontend-modern/src/utils/__tests__/ssoProviderPresentation.test.ts
@@ -24,6 +24,7 @@ import {
getSSOProviderToggleSuccessMessage,
getSSOProviderTypeBadgeClass,
getSSOProviderTypeLabel,
+ getSSOSamlFeatureGateCopy,
getSSOTestResultPresentation,
} from '../ssoProviderPresentation';
@@ -32,7 +33,7 @@ describe('ssoProviderPresentation', () => {
expect(getSSOProviderTypeLabel('oidc')).toBe('OIDC');
expect(getSSOProviderTypeLabel('saml')).toBe('SAML');
expect(getSSOProviderAddButtonLabel('oidc')).toBe('Add OIDC');
- expect(getSSOProviderAddButtonLabel('saml', true)).toBe('Add SAML (Pro)');
+ expect(getSSOProviderAddButtonLabel('saml')).toBe('Add SAML');
expect(getSSOProviderModalTitle(false, 'saml')).toBe('Add SAML Provider');
expect(getSSOProviderEmptyStateTitle()).toBe('No SSO providers configured');
expect(getSSOProviderEmptyStateDescription()).toBe(
@@ -55,6 +56,15 @@ describe('ssoProviderPresentation', () => {
).toBe('https://idp.example.com/entity');
});
+ it('returns capability-focused SAML feature gate copy', () => {
+ expect(getSSOSamlFeatureGateCopy()).toMatchObject({
+ title: 'Advanced SSO',
+ subtitle: 'Advanced SSO',
+ body: expect.stringContaining('paid self-hosted and hosted plans'),
+ });
+ expect(getSSOSamlFeatureGateCopy().body).not.toContain('Pro');
+ });
+
it('formats provider card and test result tones canonically', () => {
expect(getSSOProviderCardClass(true)).toContain('bg-surface');
expect(getSSOProviderCardClass(false)).toContain('opacity-60');
diff --git a/frontend-modern/src/utils/__tests__/upgradeMetrics.test.ts b/frontend-modern/src/utils/__tests__/upgradeMetrics.test.ts
deleted file mode 100644
index 6c35a4c87..000000000
--- a/frontend-modern/src/utils/__tests__/upgradeMetrics.test.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import {
- trackAgentFirstConnected,
- trackAgentInstallCommandCopied,
- trackAgentInstallProfileSelected,
- trackAgentInstallTokenGenerated,
- trackCheckoutClicked,
- trackPaywallViewed,
- trackPricingViewed,
- trackUpgradeClicked,
- trackUpgradeMetricEvent,
-} from '@/utils/upgradeMetrics';
-import upgradeMetricsSource from '@/utils/upgradeMetrics.ts?raw';
-
-describe('upgradeMetrics customer frontend boundary', () => {
- it('does not carry browser-side ingestion plumbing for maintainer analytics', () => {
- expect(upgradeMetricsSource).not.toContain('/api/upgrade-metrics/events');
- expect(upgradeMetricsSource).not.toContain('@/utils/apiClient');
- expect(upgradeMetricsSource).not.toContain('apiFetch(');
- expect(upgradeMetricsSource).not.toContain('fetch(');
- expect(upgradeMetricsSource).not.toContain('sendBeacon');
- });
-
- it('keeps compatibility wrappers callable without emitting product analytics', () => {
- trackPaywallViewed('rbac', 'settings_roles_panel');
- trackPricingViewed('settings_self_hosted_billing_plan', 'self_hosted_plan');
- trackUpgradeClicked('settings_reporting_panel', 'reporting');
- trackCheckoutClicked('settings_self_hosted_billing_compare_prompt', 'self_hosted_plan');
- trackAgentInstallTokenGenerated('settings_unified_agents', 'manual');
- trackAgentInstallCommandCopied('settings_unified_agents', 'linux:auto:install');
- trackAgentInstallProfileSelected('settings_unified_agents', 'proxmox-pbs');
- trackAgentFirstConnected('setup_wizard_complete', 'first_agent');
- trackUpgradeMetricEvent({
- type: 'agent_install_command_copied',
- surface: 'settings_unified_agents',
- capability: 'linux:auto:install:custom',
- idempotencyKey: 'custom-1',
- });
- });
-});
diff --git a/frontend-modern/src/utils/agentProfilesPresentation.ts b/frontend-modern/src/utils/agentProfilesPresentation.ts
index b67153eb8..a56edddc9 100644
--- a/frontend-modern/src/utils/agentProfilesPresentation.ts
+++ b/frontend-modern/src/utils/agentProfilesPresentation.ts
@@ -1,3 +1,17 @@
+export interface AgentProfilesFeatureGateCopy {
+ title: string;
+ subtitle: string;
+ body: string;
+}
+
+export function getAgentProfilesFeatureGateCopy(): AgentProfilesFeatureGateCopy {
+ return {
+ title: 'Agent Profiles',
+ subtitle: 'Centralized agent configuration',
+ body: 'Create reusable configuration profiles for your agents. Manage Docker monitoring, logging, and reporting intervals from a central location.',
+ };
+}
+
export function getAgentProfilesEmptyState(): string {
return 'No profiles yet. Create one to get started.';
}
diff --git a/frontend-modern/src/utils/aiControlLevelPresentation.ts b/frontend-modern/src/utils/aiControlLevelPresentation.ts
index 3a18c5dec..611ed0941 100644
--- a/frontend-modern/src/utils/aiControlLevelPresentation.ts
+++ b/frontend-modern/src/utils/aiControlLevelPresentation.ts
@@ -53,7 +53,7 @@ export function getAIChatControlLevelPresentation(
case 'autonomous':
return {
label: 'Autonomous',
- description: 'Executes without approval (Pro)',
+ description: 'Executes without approval',
pillClassName:
'border-red-200 text-red-700 bg-red-50 dark:border-red-800 dark:text-red-200 dark:bg-red-900',
dotClassName: 'bg-red-500',
diff --git a/frontend-modern/src/utils/auditWebhookPresentation.ts b/frontend-modern/src/utils/auditWebhookPresentation.ts
index 574497dbc..9abf1fa3d 100644
--- a/frontend-modern/src/utils/auditWebhookPresentation.ts
+++ b/frontend-modern/src/utils/auditWebhookPresentation.ts
@@ -38,8 +38,8 @@ export function getAuditWebhookFeatureGateCopy(
}
return {
- title: 'Audit Webhooks (Pro)',
- body: 'Audit webhooks are part of the audit logging feature set and require Pro.',
+ title: 'Audit Webhooks',
+ body: 'Audit webhook delivery is available on paid self-hosted and hosted plans.',
};
}
diff --git a/frontend-modern/src/utils/conversionEvents.ts b/frontend-modern/src/utils/conversionEvents.ts
deleted file mode 100644
index 8b8cb9582..000000000
--- a/frontend-modern/src/utils/conversionEvents.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-// Compatibility wrapper for retired customer-side commercial analytics exports.
-// Keep these stable while older modules/tests migrate away from the names.
-
-export type { UpgradeMetricEvent as ConversionEvent } from './upgradeMetrics';
-export {
- UPGRADE_METRIC_EVENTS as CONVERSION_EVENTS,
- trackUpgradeMetricEvent as trackConversionEvent,
- trackPaywallViewed,
- trackUpgradeClicked,
- trackAgentInstallTokenGenerated,
- trackAgentInstallCommandCopied,
- trackAgentInstallProfileSelected,
- trackAgentFirstConnected,
-} from './upgradeMetrics';
diff --git a/frontend-modern/src/utils/infrastructureOnboardingMetrics.ts b/frontend-modern/src/utils/infrastructureOnboardingMetrics.ts
deleted file mode 100644
index d94cb9ef6..000000000
--- a/frontend-modern/src/utils/infrastructureOnboardingMetrics.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { ConnectionType } from '@/api/connections';
-
-export type InfrastructureOnboardingPath = 'api' | 'agent';
-export type InfrastructureOnboardingProbeOutcome = 'detected' | 'no-match' | 'error';
-
-export const INFRASTRUCTURE_ONBOARDING_METRICS_SURFACE = 'settings_infrastructure_add';
-
-export interface InfrastructureOnboardingMetricsTracker {
- recordOpened: () => void;
- recordPathSelected: (path: InfrastructureOnboardingPath) => void;
- recordProbeResult: (outcome: InfrastructureOnboardingProbeOutcome) => void;
- recordCatalogSelected: (type: ConnectionType) => void;
- recordCredentialsOpened: (type: ConnectionType) => void;
-}
-
-const NOOP_INFRASTRUCTURE_ONBOARDING_METRICS_TRACKER: InfrastructureOnboardingMetricsTracker = {
- recordOpened() {
- // Compatibility no-op.
- },
- recordPathSelected(_path) {
- // Compatibility no-op.
- },
- recordProbeResult(_outcome) {
- // Compatibility no-op.
- },
- recordCatalogSelected(_type) {
- // Compatibility no-op.
- },
- recordCredentialsOpened(_type) {
- // Compatibility no-op.
- },
-};
-
-export function createInfrastructureOnboardingMetricsTracker(
- _surface = INFRASTRUCTURE_ONBOARDING_METRICS_SURFACE,
- _flowId?: string,
-): InfrastructureOnboardingMetricsTracker {
- return NOOP_INFRASTRUCTURE_ONBOARDING_METRICS_TRACKER;
-}
-
-export function getSharedInfrastructureOnboardingMetricsTracker(
- _surface = INFRASTRUCTURE_ONBOARDING_METRICS_SURFACE,
-): InfrastructureOnboardingMetricsTracker {
- return NOOP_INFRASTRUCTURE_ONBOARDING_METRICS_TRACKER;
-}
-
-export function clearSharedInfrastructureOnboardingMetricsTracker(
- _surface = INFRASTRUCTURE_ONBOARDING_METRICS_SURFACE,
-): void {
- // Compatibility no-op.
-}
diff --git a/frontend-modern/src/utils/rbacPresentation.ts b/frontend-modern/src/utils/rbacPresentation.ts
index 4f5cb17bd..e26bf0c8f 100644
--- a/frontend-modern/src/utils/rbacPresentation.ts
+++ b/frontend-modern/src/utils/rbacPresentation.ts
@@ -21,19 +21,17 @@ export function getRBACFeatureGateCopy(
const showCommercialCopy = options.showCommercialCopy !== false;
if (kind === 'roles') {
return {
- title: showCommercialCopy ? 'Custom Roles (Pro)' : 'Custom Roles',
+ title: 'Custom Roles',
body: showCommercialCopy
- ? 'Define granular permissions and custom access tiers for your team.'
+ ? 'Define granular permissions and custom access tiers on paid self-hosted and hosted plans.'
: 'Define granular permissions and custom access tiers when RBAC is enabled for this instance.',
};
}
return {
- title: showCommercialCopy
- ? 'Centralized Access Control (Pro)'
- : 'Centralized Access Control',
+ title: 'Centralized Access Control',
body: showCommercialCopy
- ? 'Assign multi-tier roles to users and manage infrastructure-wide security policies.'
+ ? 'Assign multi-tier roles to users and manage infrastructure-wide security policies on paid self-hosted and hosted plans.'
: 'Assign roles to users and review access policy when RBAC is enabled for this instance.',
};
}
diff --git a/frontend-modern/src/utils/ssoProviderPresentation.ts b/frontend-modern/src/utils/ssoProviderPresentation.ts
index 61b8aee58..26f062dc4 100644
--- a/frontend-modern/src/utils/ssoProviderPresentation.ts
+++ b/frontend-modern/src/utils/ssoProviderPresentation.ts
@@ -1,5 +1,11 @@
export type SSOProviderType = 'oidc' | 'saml';
+export interface SSOProviderFeatureGateCopy {
+ title: string;
+ subtitle: string;
+ body: string;
+}
+
export interface SSOProviderSummaryLike {
type: SSOProviderType;
oidcIssuerUrl?: string;
@@ -11,10 +17,16 @@ export function getSSOProviderTypeLabel(type: SSOProviderType): string {
return type === 'oidc' ? 'OIDC' : 'SAML';
}
-export function getSSOProviderAddButtonLabel(type: SSOProviderType, gated = false): string {
- return gated
- ? `Add ${getSSOProviderTypeLabel(type)} (Pro)`
- : `Add ${getSSOProviderTypeLabel(type)}`;
+export function getSSOProviderAddButtonLabel(type: SSOProviderType): string {
+ return `Add ${getSSOProviderTypeLabel(type)}`;
+}
+
+export function getSSOSamlFeatureGateCopy(): SSOProviderFeatureGateCopy {
+ return {
+ title: 'Advanced SSO',
+ subtitle: 'Advanced SSO',
+ body: 'SAML 2.0 and multi-provider SSO are available on paid self-hosted and hosted plans. Basic OIDC remains available without a paid plan.',
+ };
}
export function getSSOProviderModalTitle(editing: boolean, type: SSOProviderType): string {
diff --git a/frontend-modern/src/utils/upgradeMetrics.ts b/frontend-modern/src/utils/upgradeMetrics.ts
deleted file mode 100644
index 16ec24aa0..000000000
--- a/frontend-modern/src/utils/upgradeMetrics.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-export interface UpgradeMetricEvent {
- type: string;
- capability?: string;
- surface: string;
- tenant_mode?: string;
- limit_key?: string;
- current_value?: number;
- limit_value?: number;
- timestamp: number;
- idempotency_key: string;
-}
-
-export interface TrackUpgradeMetricEventInput {
- type: string;
- surface: string;
- capability?: string;
- tenant_mode?: string;
- limit_key?: string;
- current_value?: number;
- limit_value?: number;
- idempotencyKey?: string;
-}
-
-export const UPGRADE_METRIC_EVENTS = {
- PRICING_VIEWED: 'pricing_viewed',
- PAYWALL_VIEWED: 'paywall_viewed',
- TRIAL_STARTED: 'trial_started',
- LICENSE_ACTIVATED: 'license_activated',
- UPGRADE_CLICKED: 'upgrade_clicked',
- CHECKOUT_CLICKED: 'checkout_clicked',
- LIMIT_WARNING_SHOWN: 'limit_warning_shown',
- LIMIT_BLOCKED: 'limit_blocked',
- AGENT_INSTALL_TOKEN_GENERATED: 'agent_install_token_generated',
- AGENT_INSTALL_COMMAND_COPIED: 'agent_install_command_copied',
- AGENT_INSTALL_PROFILE_SELECTED: 'agent_install_profile_selected',
- AGENT_FIRST_CONNECTED: 'agent_first_connected',
- INFRASTRUCTURE_ONBOARDING_OPENED: 'infrastructure_onboarding_opened',
- INFRASTRUCTURE_ONBOARDING_PATH_SELECTED: 'infrastructure_onboarding_path_selected',
- INFRASTRUCTURE_ONBOARDING_PROBE_RESULT: 'infrastructure_onboarding_probe_result',
- INFRASTRUCTURE_ONBOARDING_CATALOG_SELECTED: 'infrastructure_onboarding_catalog_selected',
- INFRASTRUCTURE_ONBOARDING_CREDENTIALS_OPENED: 'infrastructure_onboarding_credentials_opened',
-} as const;
-
-export function trackUpgradeMetricEvent(
- _event: TrackUpgradeMetricEventInput,
-): void {
- // Compatibility no-op: customer frontend surfaces must not emit maintainer
- // commercial, funnel, or onboarding analytics.
-}
-
-export function trackPaywallViewed(_capability: string, _surface: string): void {
- // Compatibility no-op.
-}
-
-export function trackPricingViewed(_surface: string, _capability?: string): void {
- // Compatibility no-op.
-}
-
-export function trackUpgradeClicked(_surface: string, _capability?: string): void {
- // Compatibility no-op.
-}
-
-export function trackCheckoutClicked(_surface: string, _capability?: string): void {
- // Compatibility no-op.
-}
-
-export function trackAgentInstallTokenGenerated(_surface: string, _capability?: string): void {
- // Compatibility no-op.
-}
-
-export function trackAgentInstallCommandCopied(_surface: string, _capability?: string): void {
- // Compatibility no-op.
-}
-
-export function trackAgentInstallProfileSelected(_surface: string, _profile: string): void {
- // Compatibility no-op.
-}
-
-export function trackAgentFirstConnected(_surface: string, _capability?: string): void {
- // Compatibility no-op.
-}
diff --git a/internal/api/contract_test.go b/internal/api/contract_test.go
index cacb06fad..75efa0fbc 100644
--- a/internal/api/contract_test.go
+++ b/internal/api/contract_test.go
@@ -2497,8 +2497,8 @@ func TestContract_ReportingCatalogJSONSnapshot(t *testing.T) {
"title":"Detailed Reporting",
"description":"Generate performance reports and current-state exports across infrastructure and workloads.",
"lockedState":{
- "title":"Advanced Reporting (Pro)",
- "description":"Generate PDF and CSV performance reports plus current-state VM inventory exports across infrastructure and workload resources."
+ "title":"Advanced Reporting",
+ "description":"Generate PDF and CSV performance reports plus current-state VM inventory exports across infrastructure and workload resources on paid self-hosted and hosted plans."
},
"guidance":{
"title":"Advanced Insights",
diff --git a/pkg/reporting/catalog.go b/pkg/reporting/catalog.go
index fd3f55425..33d110597 100644
--- a/pkg/reporting/catalog.go
+++ b/pkg/reporting/catalog.go
@@ -180,8 +180,8 @@ func DescribeReportingCatalog() ReportingCatalog {
Title: "Detailed Reporting",
Description: "Generate performance reports and current-state exports across infrastructure and workloads.",
LockedState: ReportingLockedStateDefinition{
- Title: "Advanced Reporting (Pro)",
- Description: "Generate PDF and CSV performance reports plus current-state VM inventory exports across infrastructure and workload resources.",
+ Title: "Advanced Reporting",
+ Description: "Generate PDF and CSV performance reports plus current-state VM inventory exports across infrastructure and workload resources on paid self-hosted and hosted plans.",
},
Guidance: ReportingGuidanceDefinition{
Title: "Advanced Insights",
diff --git a/pkg/reporting/catalog_test.go b/pkg/reporting/catalog_test.go
index e0e41dbfa..1460a0913 100644
--- a/pkg/reporting/catalog_test.go
+++ b/pkg/reporting/catalog_test.go
@@ -14,8 +14,8 @@ func TestDescribeReportingCatalog_DefinesCanonicalSurfaces(t *testing.T) {
if catalog.PerformanceReport.ID != "performance_reports" {
t.Fatalf("performance report ID = %q, want performance_reports", catalog.PerformanceReport.ID)
}
- if catalog.LockedState.Title != "Advanced Reporting (Pro)" {
- t.Fatalf("locked state title = %q, want Advanced Reporting (Pro)", catalog.LockedState.Title)
+ if catalog.LockedState.Title != "Advanced Reporting" {
+ t.Fatalf("locked state title = %q, want Advanced Reporting", catalog.LockedState.Title)
}
if catalog.Guidance.Title != "Advanced Insights" {
t.Fatalf("guidance title = %q, want Advanced Insights", catalog.Guidance.Title)
diff --git a/scripts/release_control/subsystem_lookup_test.py b/scripts/release_control/subsystem_lookup_test.py
index d76470dae..9b52d6564 100644
--- a/scripts/release_control/subsystem_lookup_test.py
+++ b/scripts/release_control/subsystem_lookup_test.py
@@ -3606,7 +3606,7 @@ class SubsystemLookupTest(unittest.TestCase):
{
"heading": "## Shared Boundaries",
"path": "internal/api/access_control_handlers.go",
- "line": 149,
+ "line": 156,
"heading_line": 91,
}
],