mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-10 01:50:08 +00:00
feat: default to Getting Started category for new users in templates modal (#8599)
## Summary Updates the templates modal to default to the "Getting Started" category for new users. ## Changes - Add `initialCategory` prop to `WorkflowTemplateSelectorDialog` component - Integrate `useNewUserService` in the dialog composable to detect first-time users - New users automatically see the "basics-getting-started" category - Existing users continue to see "all" templates as default - Allow explicit category override via options parameter ## Testing - Added unit tests covering all scenarios (new user, non-new user, undetermined, explicit override) - 6 tests pass Fixes COM-9146 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8599-feat-default-to-Getting-Started-category-for-new-users-in-templates-modal-2fd6d73d365081d4a5fad2abdb768269) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -422,8 +422,9 @@ import { createGridStyle } from '@/utils/gridUtil'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { onClose: originalOnClose } = defineProps<{
|
||||
const { onClose: originalOnClose, initialCategory = 'all' } = defineProps<{
|
||||
onClose: () => void
|
||||
initialCategory?: string
|
||||
}>()
|
||||
|
||||
// Track session time for telemetry
|
||||
@@ -547,7 +548,7 @@ const allTemplates = computed(() => {
|
||||
})
|
||||
|
||||
// Navigation
|
||||
const selectedNavItem = ref<string | null>('all')
|
||||
const selectedNavItem = ref<string | null>(initialCategory)
|
||||
|
||||
// Filter templates based on selected navigation item
|
||||
const navigationFilteredTemplates = computed(() => {
|
||||
|
||||
132
src/composables/useWorkflowTemplateSelectorDialog.test.ts
Normal file
132
src/composables/useWorkflowTemplateSelectorDialog.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mockDialogService = vi.hoisted(() => ({
|
||||
showLayoutDialog: vi.fn()
|
||||
}))
|
||||
|
||||
const mockDialogStore = vi.hoisted(() => ({
|
||||
closeDialog: vi.fn()
|
||||
}))
|
||||
|
||||
const mockNewUserService = vi.hoisted(() => ({
|
||||
isNewUser: vi.fn()
|
||||
}))
|
||||
|
||||
const mockTelemetry = vi.hoisted(() => ({
|
||||
trackTemplateLibraryOpened: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/services/dialogService', () => ({
|
||||
useDialogService: () => mockDialogService
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/dialogStore', () => ({
|
||||
useDialogStore: () => mockDialogStore
|
||||
}))
|
||||
|
||||
vi.mock('@/services/useNewUserService', () => ({
|
||||
useNewUserService: () => mockNewUserService
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/telemetry', () => ({
|
||||
useTelemetry: () => mockTelemetry
|
||||
}))
|
||||
|
||||
vi.mock(
|
||||
'@/components/custom/widget/WorkflowTemplateSelectorDialog.vue',
|
||||
() => ({
|
||||
default: { name: 'MockWorkflowTemplateSelectorDialog' }
|
||||
})
|
||||
)
|
||||
|
||||
import { useWorkflowTemplateSelectorDialog } from './useWorkflowTemplateSelectorDialog'
|
||||
|
||||
describe('useWorkflowTemplateSelectorDialog', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('show', () => {
|
||||
it('defaults to "all" category for non-new users', () => {
|
||||
mockNewUserService.isNewUser.mockReturnValue(false)
|
||||
|
||||
const dialog = useWorkflowTemplateSelectorDialog()
|
||||
dialog.show()
|
||||
|
||||
expect(mockDialogService.showLayoutDialog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
props: expect.objectContaining({
|
||||
initialCategory: 'all'
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('defaults to "basics-getting-started" category for new users', () => {
|
||||
mockNewUserService.isNewUser.mockReturnValue(true)
|
||||
|
||||
const dialog = useWorkflowTemplateSelectorDialog()
|
||||
dialog.show()
|
||||
|
||||
expect(mockDialogService.showLayoutDialog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
props: expect.objectContaining({
|
||||
initialCategory: 'basics-getting-started'
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('defaults to "all" when new user status is undetermined', () => {
|
||||
mockNewUserService.isNewUser.mockReturnValue(null)
|
||||
|
||||
const dialog = useWorkflowTemplateSelectorDialog()
|
||||
dialog.show()
|
||||
|
||||
expect(mockDialogService.showLayoutDialog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
props: expect.objectContaining({
|
||||
initialCategory: 'all'
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('uses explicit initialCategory when provided', () => {
|
||||
mockNewUserService.isNewUser.mockReturnValue(true)
|
||||
|
||||
const dialog = useWorkflowTemplateSelectorDialog()
|
||||
dialog.show('command', { initialCategory: 'custom-category' })
|
||||
|
||||
expect(mockDialogService.showLayoutDialog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
props: expect.objectContaining({
|
||||
initialCategory: 'custom-category'
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('tracks telemetry with source', () => {
|
||||
mockNewUserService.isNewUser.mockReturnValue(false)
|
||||
|
||||
const dialog = useWorkflowTemplateSelectorDialog()
|
||||
dialog.show('sidebar')
|
||||
|
||||
expect(mockTelemetry.trackTemplateLibraryOpened).toHaveBeenCalledWith({
|
||||
source: 'sidebar'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('hide', () => {
|
||||
it('closes the dialog', () => {
|
||||
const dialog = useWorkflowTemplateSelectorDialog()
|
||||
dialog.hide()
|
||||
|
||||
expect(mockDialogStore.closeDialog).toHaveBeenCalledWith({
|
||||
key: 'global-workflow-template-selector'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,26 +1,37 @@
|
||||
import WorkflowTemplateSelectorDialog from '@/components/custom/widget/WorkflowTemplateSelectorDialog.vue'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useNewUserService } from '@/services/useNewUserService'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const DIALOG_KEY = 'global-workflow-template-selector'
|
||||
const GETTING_STARTED_CATEGORY_ID = 'basics-getting-started'
|
||||
|
||||
export const useWorkflowTemplateSelectorDialog = () => {
|
||||
const dialogService = useDialogService()
|
||||
const dialogStore = useDialogStore()
|
||||
const newUserService = useNewUserService()
|
||||
|
||||
function hide() {
|
||||
dialogStore.closeDialog({ key: DIALOG_KEY })
|
||||
}
|
||||
|
||||
function show(source: 'sidebar' | 'menu' | 'command' = 'command') {
|
||||
function show(
|
||||
source: 'sidebar' | 'menu' | 'command' = 'command',
|
||||
options?: { initialCategory?: string }
|
||||
) {
|
||||
useTelemetry()?.trackTemplateLibraryOpened({ source })
|
||||
|
||||
const initialCategory =
|
||||
options?.initialCategory ??
|
||||
(newUserService.isNewUser() ? GETTING_STARTED_CATEGORY_ID : 'all')
|
||||
|
||||
dialogService.showLayoutDialog({
|
||||
key: DIALOG_KEY,
|
||||
component: WorkflowTemplateSelectorDialog,
|
||||
props: {
|
||||
onClose: hide
|
||||
onClose: hide,
|
||||
initialCategory
|
||||
},
|
||||
dialogComponentProps: {
|
||||
pt: {
|
||||
|
||||
Reference in New Issue
Block a user