From c602dce375f27014b0ec1bf3dbca63b9417e5f41 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Thu, 12 Mar 2026 09:06:26 -0700 Subject: [PATCH] feat: integrate nightly survey system into app (#8480) ## Summary Wires the nightly survey system into the app by adding a controller component and a convenience composable for feature-site usage tracking. ## Changes - **What**: NightlySurveyController iterates enabled surveys from the registry and renders a NightlySurveyPopover for each. useSurveyFeatureTracking wraps useFeatureUsageTracker with a config-enabled guard for use at feature call sites. - **Tree-shaking**: Controller is loaded via defineAsyncComponent behind a compile-time isNightly/isCloud/isDesktop guard in SideToolbar.vue, so the entire survey module subtree is eliminated from cloud/desktop/stable builds. ## Review Focus - DCE pattern: controller imported conditionally via defineAsyncComponent + distribution guard (same pattern as ComfyRunButton/index.ts) - useSurveyFeatureTracking short-circuits early when config is absent/disabled (avoids initializing tracker storage) - No user-facing behavior change: FEATURE_SURVEYS registry is still empty ## Part of Nightly Survey System This is part 5 of a stacked PR chain: 1. feat/feature-usage-tracker - useFeatureUsageTracker (merged in #8189) 2. feat/survey-eligibility - useSurveyEligibility (#8189, merged) 3. feat/survey-config - surveyRegistry.ts (#8355, merged) 4. feat/survey-popover - NightlySurveyPopover.vue (#9083, merged) 5. **feat/survey-integration** - NightlySurveyController.vue (this PR) --------- Co-authored-by: GitHub Action --- src/components/sidebar/SideToolbar.vue | 22 +++++- .../surveys/NightlySurveyController.vue | 14 ++++ .../surveys/useSurveyFeatureTracking.test.ts | 77 +++++++++++++++++++ .../surveys/useSurveyFeatureTracking.ts | 35 +++++++++ 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 src/platform/surveys/NightlySurveyController.vue create mode 100644 src/platform/surveys/useSurveyFeatureTracking.test.ts create mode 100644 src/platform/surveys/useSurveyFeatureTracking.ts diff --git a/src/components/sidebar/SideToolbar.vue b/src/components/sidebar/SideToolbar.vue index 93721d4f57..4265c2f1f4 100644 --- a/src/components/sidebar/SideToolbar.vue +++ b/src/components/sidebar/SideToolbar.vue @@ -48,13 +48,24 @@ + + + + + diff --git a/src/platform/surveys/useSurveyFeatureTracking.test.ts b/src/platform/surveys/useSurveyFeatureTracking.test.ts new file mode 100644 index 0000000000..25acb49aa4 --- /dev/null +++ b/src/platform/surveys/useSurveyFeatureTracking.test.ts @@ -0,0 +1,77 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const getSurveyConfig = vi.hoisted(() => + vi.fn<(featureId: string) => { enabled: boolean } | undefined>() +) + +vi.mock('./surveyRegistry', () => ({ + getSurveyConfig +})) + +describe('useSurveyFeatureTracking', () => { + beforeEach(() => { + localStorage.clear() + vi.resetModules() + getSurveyConfig.mockReset() + }) + + afterEach(() => { + localStorage.clear() + }) + + it('tracks usage when config is enabled', async () => { + getSurveyConfig.mockReturnValue({ enabled: true }) + + const { useSurveyFeatureTracking } = + await import('./useSurveyFeatureTracking') + const { trackFeatureUsed, useCount } = + useSurveyFeatureTracking('test-feature') + + expect(useCount.value).toBe(0) + + trackFeatureUsed() + + expect(useCount.value).toBe(1) + }) + + it('does not track when config is disabled', async () => { + getSurveyConfig.mockReturnValue({ enabled: false }) + + const { useSurveyFeatureTracking } = + await import('./useSurveyFeatureTracking') + const { trackFeatureUsed, useCount } = + useSurveyFeatureTracking('disabled-feature') + + trackFeatureUsed() + + expect(useCount.value).toBe(0) + }) + + it('tracks usage when config exists without enabled field', async () => { + getSurveyConfig.mockReturnValue({} as { enabled: boolean }) + + const { useSurveyFeatureTracking } = + await import('./useSurveyFeatureTracking') + const { trackFeatureUsed, useCount } = useSurveyFeatureTracking( + 'implicit-enabled-feature' + ) + + trackFeatureUsed() + + expect(useCount.value).toBe(1) + }) + + it('does not track when config does not exist', async () => { + getSurveyConfig.mockReturnValue(undefined) + + const { useSurveyFeatureTracking } = + await import('./useSurveyFeatureTracking') + const { trackFeatureUsed, useCount } = useSurveyFeatureTracking( + 'nonexistent-feature' + ) + + trackFeatureUsed() + + expect(useCount.value).toBe(0) + }) +}) diff --git a/src/platform/surveys/useSurveyFeatureTracking.ts b/src/platform/surveys/useSurveyFeatureTracking.ts new file mode 100644 index 0000000000..43d0cbee39 --- /dev/null +++ b/src/platform/surveys/useSurveyFeatureTracking.ts @@ -0,0 +1,35 @@ +import { computed } from 'vue' + +import { getSurveyConfig } from './surveyRegistry' +import { useFeatureUsageTracker } from './useFeatureUsageTracker' + +/** + * Convenience composable for tracking feature usage for surveys. + * Use this at the feature site to track when a feature is used. + * + * @example + * ```typescript + * const { trackFeatureUsed } = useSurveyFeatureTracking('simple-mode') + * + * function onFeatureAction() { + * trackFeatureUsed() + * } + * ``` + */ +export function useSurveyFeatureTracking(featureId: string) { + const config = getSurveyConfig(featureId) + + if (config?.enabled === false || !config) { + return { + trackFeatureUsed: () => {}, + useCount: computed(() => 0) + } + } + + const { trackUsage, useCount } = useFeatureUsageTracker(featureId) + + return { + trackFeatureUsed: trackUsage, + useCount + } +}