diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index a0c62a805f..8c240864d9 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -1485,6 +1485,7 @@ "nodeView.cantExecuteNoTrigger": "Cannot execute workflow", "nodeView.canvasAddButton.addATriggerNodeBeforeExecuting": "Add a Trigger Node before executing the workflow", "nodeView.canvasAddButton.addFirstStep": "Add first step…", + "nodeView.templateLink": "or start from a template", "nodeView.confirmMessage.onClipboardPasteEvent.cancelButtonText": "", "nodeView.confirmMessage.onClipboardPasteEvent.confirmButtonText": "Yes, import", "nodeView.confirmMessage.onClipboardPasteEvent.headline": "Import Workflow?", @@ -2628,6 +2629,7 @@ "workflows.empty.description.readOnlyEnv": "No workflows here yet", "workflows.empty.description.noPermission": "There are currently no workflows to view", "workflows.empty.startFromScratch": "Start from scratch", + "workflows.empty.startWithTemplate": "Start with a template", "workflows.empty.browseTemplates": "Explore workflow templates", "workflows.empty.learnN8n": "Learn n8n", "workflows.empty.button.disabled.tooltip": "Your current role in the project does not allow you to create workflows", diff --git a/packages/frontend/editor-ui/src/components/MainSidebar.vue b/packages/frontend/editor-ui/src/components/MainSidebar.vue index 19eced7d77..9b53c53797 100644 --- a/packages/frontend/editor-ui/src/components/MainSidebar.vue +++ b/packages/frontend/editor-ui/src/components/MainSidebar.vue @@ -27,6 +27,7 @@ import { useGlobalEntityCreation } from '@/composables/useGlobalEntityCreation'; import { useBecomeTemplateCreatorStore } from '@/components/BecomeTemplateCreatorCta/becomeTemplateCreatorStore'; import Logo from '@/components/Logo/Logo.vue'; import VersionUpdateCTA from '@/components/VersionUpdateCTA.vue'; +import { TemplateClickSource, trackTemplatesClick } from '@/utils/experiments'; const becomeTemplateCreatorStore = useBecomeTemplateCreatorStore(); const cloudPlanStore = useCloudPlanStore(); @@ -250,13 +251,6 @@ onBeforeUnmount(() => { window.removeEventListener('resize', onResize); }); -const trackTemplatesClick = () => { - telemetry.track('User clicked on templates', { - role: cloudPlanStore.currentUserCloudInfo?.role, - active_workflow_count: workflowsStore.activeWorkflows.length, - }); -}; - const trackHelpItemClick = (itemType: string) => { telemetry.track('User clicked help resource', { type: itemType, @@ -297,7 +291,7 @@ const handleSelect = (key: string) => { switch (key) { case 'templates': if (settingsStore.isTemplatesEnabled && !templatesStore.hasCustomTemplatesHost) { - trackTemplatesClick(); + trackTemplatesClick(TemplateClickSource.sidebarButton); } break; case 'about': { diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeAddNodes.test.ts b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeAddNodes.test.ts new file mode 100644 index 0000000000..48c33ed3bd --- /dev/null +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeAddNodes.test.ts @@ -0,0 +1,113 @@ +import { createCanvasNodeProvide, createCanvasProvide } from '@/__tests__/data'; +import { createComponentRenderer } from '@/__tests__/render'; +import { TEMPLATES_URLS } from '@/constants'; +import { useSettingsStore } from '@/stores/settings.store'; +import { TemplateClickSource } from '@/utils/experiments'; +import { createTestingPinia } from '@pinia/testing'; +import userEvent from '@testing-library/user-event'; +import { setActivePinia } from 'pinia'; +import CanvasNodeAddNodes from './CanvasNodeAddNodes.vue'; + +vi.mock('@/stores/posthog.store', () => ({ + usePostHog: vi.fn(() => ({ + getVariant: vi.fn(() => 'variant'), + })), +})); + +vi.mock('@/utils/experiments', async (importOriginal) => { + const actual = await importOriginal(); + + return { + ...actual, + isExtraTemplateLinksExperimentEnabled: vi.fn(() => true), + }; +}); + +const mockTrack = vi.fn(); +vi.mock('@/composables/useTelemetry', () => ({ + useTelemetry: vi.fn(() => ({ + track: mockTrack, + })), +})); + +let settingsStore: ReturnType; + +const renderComponent = createComponentRenderer(CanvasNodeAddNodes, { + global: { + provide: { + ...createCanvasProvide(), + }, + }, +}); + +describe('CanvasNodeAddNodes', () => { + beforeEach(() => { + const pinia = createTestingPinia(); + setActivePinia(pinia); + + settingsStore = useSettingsStore(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should render node correctly', () => { + const { getByTestId } = renderComponent({ + global: { + provide: { + ...createCanvasNodeProvide(), + }, + }, + }); + + expect(getByTestId('canvas-add-button')).toMatchSnapshot(); + }); + + describe('template link', () => { + it.each([ + { + host: 'https://example.com', + type: 'custom', + }, + { + host: TEMPLATES_URLS.DEFAULT_API_HOST, + type: 'default', + }, + ])('should render with $type template store', ({ host }) => { + settingsStore.settings.templates = { enabled: true, host }; + + const { getByTestId } = renderComponent({ + global: { + provide: { + ...createCanvasNodeProvide(), + }, + }, + }); + + expect(getByTestId('canvas-template-link')).toBeDefined(); + }); + + it('should track user click', async () => { + settingsStore.settings.templates = { enabled: true, host: '' }; + + const { getByTestId } = renderComponent({ + global: { + provide: { + ...createCanvasNodeProvide(), + }, + }, + }); + + const link = getByTestId('canvas-template-link'); + await userEvent.click(link); + + expect(mockTrack).toHaveBeenCalledWith( + 'User clicked on templates', + expect.objectContaining({ + source: TemplateClickSource.emptyWorkflowLink, + }), + ); + }); + }); +}); diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeAddNodes.vue b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeAddNodes.vue index b53f287e35..0af8796bba 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeAddNodes.vue +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeAddNodes.vue @@ -1,15 +1,41 @@