mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-27 17:52:16 +00:00
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 <action@github.com>
This commit is contained in:
@@ -48,13 +48,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HelpCenterPopups :is-small="isSmall" />
|
<HelpCenterPopups :is-small="isSmall" />
|
||||||
|
<Suspense v-if="NightlySurveyController">
|
||||||
|
<component :is="NightlySurveyController" />
|
||||||
|
</Suspense>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useResizeObserver } from '@vueuse/core'
|
import { useResizeObserver } from '@vueuse/core'
|
||||||
import { debounce } from 'es-toolkit/compat'
|
import { debounce } from 'es-toolkit/compat'
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import {
|
||||||
|
computed,
|
||||||
|
defineAsyncComponent,
|
||||||
|
nextTick,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
ref,
|
||||||
|
watch
|
||||||
|
} from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import HelpCenterPopups from '@/components/helpcenter/HelpCenterPopups.vue'
|
import HelpCenterPopups from '@/components/helpcenter/HelpCenterPopups.vue'
|
||||||
@@ -62,7 +73,7 @@ import ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue'
|
|||||||
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
|
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
|
||||||
import SidebarSettingsButton from '@/components/sidebar/SidebarSettingsButton.vue'
|
import SidebarSettingsButton from '@/components/sidebar/SidebarSettingsButton.vue'
|
||||||
import SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'
|
import SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'
|
||||||
import { isCloud } from '@/platform/distribution/types'
|
import { isCloud, isDesktop, isNightly } from '@/platform/distribution/types'
|
||||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||||
import { useTelemetry } from '@/platform/telemetry'
|
import { useTelemetry } from '@/platform/telemetry'
|
||||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||||
@@ -78,6 +89,13 @@ import SidebarIcon from './SidebarIcon.vue'
|
|||||||
import SidebarLogoutIcon from './SidebarLogoutIcon.vue'
|
import SidebarLogoutIcon from './SidebarLogoutIcon.vue'
|
||||||
import SidebarTemplatesButton from './SidebarTemplatesButton.vue'
|
import SidebarTemplatesButton from './SidebarTemplatesButton.vue'
|
||||||
|
|
||||||
|
const NightlySurveyController =
|
||||||
|
isNightly && !isCloud && !isDesktop
|
||||||
|
? defineAsyncComponent(
|
||||||
|
() => import('@/platform/surveys/NightlySurveyController.vue')
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const workspaceStore = useWorkspaceStore()
|
const workspaceStore = useWorkspaceStore()
|
||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
|
|||||||
14
src/platform/surveys/NightlySurveyController.vue
Normal file
14
src/platform/surveys/NightlySurveyController.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import NightlySurveyPopover from './NightlySurveyPopover.vue'
|
||||||
|
import { getEnabledSurveys } from './surveyRegistry'
|
||||||
|
|
||||||
|
const enabledSurveys = computed(() => getEnabledSurveys())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<template v-for="config in enabledSurveys" :key="config.featureId">
|
||||||
|
<NightlySurveyPopover :config="config" />
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
77
src/platform/surveys/useSurveyFeatureTracking.test.ts
Normal file
77
src/platform/surveys/useSurveyFeatureTracking.test.ts
Normal file
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
35
src/platform/surveys/useSurveyFeatureTracking.ts
Normal file
35
src/platform/surveys/useSurveyFeatureTracking.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user