From 3eccf3ec61b7ba8c16ca2b9d68e88a35e03ea437 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Sat, 7 Feb 2026 20:30:10 -0800 Subject: [PATCH] feat: default to Getting Started category for new users in templates modal (#8599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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) --- .../widget/WorkflowTemplateSelectorDialog.vue | 5 +- .../useWorkflowTemplateSelectorDialog.test.ts | 132 ++++++++++++++++++ .../useWorkflowTemplateSelectorDialog.ts | 15 +- 3 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 src/composables/useWorkflowTemplateSelectorDialog.test.ts 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: {