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:
Christian Byrne
2026-02-07 20:30:10 -08:00
committed by GitHub
parent 1b73b5b31e
commit 3eccf3ec61
3 changed files with 148 additions and 4 deletions

View File

@@ -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(() => {

View 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'
})
})
})
})

View File

@@ -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: {