Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/components/sidebar/SideToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,32 @@
</div>
</div>
<HelpCenterPopups :is-small="isSmall" />
<Suspense v-if="NightlySurveyController">
<component :is="NightlySurveyController" />
</Suspense>
</nav>
</template>

<script setup lang="ts">
import { useResizeObserver } from '@vueuse/core'
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 HelpCenterPopups from '@/components/helpcenter/HelpCenterPopups.vue'
import ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue'
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
import SidebarSettingsButton from '@/components/sidebar/SidebarSettingsButton.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 { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
Expand All @@ -78,6 +89,13 @@ import SidebarIcon from './SidebarIcon.vue'
import SidebarLogoutIcon from './SidebarLogoutIcon.vue'
import SidebarTemplatesButton from './SidebarTemplatesButton.vue'
const NightlySurveyController =
isNightly && !isCloud && !isDesktop
? defineAsyncComponent(
() => import('@/platform/surveys/NightlySurveyController.vue')
)
: undefined
const { t } = useI18n()
const workspaceStore = useWorkspaceStore()
const settingStore = useSettingStore()
Expand Down
14 changes: 14 additions & 0 deletions src/platform/surveys/NightlySurveyController.vue
Original file line number Diff line number Diff line change
@@ -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>
63 changes: 63 additions & 0 deletions src/platform/surveys/useSurveyFeatureTracking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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('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 changes: 35 additions & 0 deletions src/platform/surveys/useSurveyFeatureTracking.ts
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useSurveyFeatureTracking treats enabled as required-true, while the rest of the survey system defaults missing enabled to true, so a survey without enabled set can render and pass eligibility but won’t record usage, potentially never reaching its threshold.

see

(config) => config.enabled !== false

return {
trackFeatureUsed: () => {},
useCount: computed(() => 0)
}
}

const { trackUsage, useCount } = useFeatureUsageTracker(featureId)

return {
trackFeatureUsed: trackUsage,
useCount
}
}
Loading