diff --git a/src/components/custom/widget/WorkflowTemplateSelectorDialog.vue b/src/components/custom/widget/WorkflowTemplateSelectorDialog.vue index a60b2808f..857356a42 100644 --- a/src/components/custom/widget/WorkflowTemplateSelectorDialog.vue +++ b/src/components/custom/widget/WorkflowTemplateSelectorDialog.vue @@ -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('all') +const selectedNavItem = ref(initialCategory) // Filter templates based on selected navigation item const navigationFilteredTemplates = computed(() => { diff --git a/src/composables/useWorkflowTemplateSelectorDialog.test.ts b/src/composables/useWorkflowTemplateSelectorDialog.test.ts new file mode 100644 index 000000000..6772a7808 --- /dev/null +++ b/src/composables/useWorkflowTemplateSelectorDialog.test.ts @@ -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' + }) + }) + }) +}) diff --git a/src/composables/useWorkflowTemplateSelectorDialog.ts b/src/composables/useWorkflowTemplateSelectorDialog.ts index db2993698..080067a28 100644 --- a/src/composables/useWorkflowTemplateSelectorDialog.ts +++ b/src/composables/useWorkflowTemplateSelectorDialog.ts @@ -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: {