From d4970410e1ba328b05ddc23abcbf33c719de5624 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 29 Nov 2023 09:09:21 +0100 Subject: [PATCH] fix(editor): Add telemetry to workflow history (#7811) --- packages/editor-ui/src/Interface.ts | 6 +- .../editor-ui/src/views/WorkflowHistory.vue | 14 ++++ .../views/__tests__/WorkflowHistory.test.ts | 78 +++++++++++++++---- 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index e54a4b54d42e0..415c752ffbb63 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -1754,7 +1754,8 @@ export type CloudUpdateLinkSourceType = | 'usage_page' | 'settings-users' | 'variables' - | 'community-nodes'; + | 'community-nodes' + | 'workflow-history'; export type UTMCampaign = | 'upgrade-custom-data-filter' @@ -1770,7 +1771,8 @@ export type UTMCampaign = | 'open' | 'upgrade-users' | 'upgrade-variables' - | 'upgrade-community-nodes'; + | 'upgrade-community-nodes' + | 'upgrade-workflow-history'; export type N8nBanners = { [key in BannerName]: { diff --git a/packages/editor-ui/src/views/WorkflowHistory.vue b/packages/editor-ui/src/views/WorkflowHistory.vue index 7977bb760fa7c..1e947d6795168 100644 --- a/packages/editor-ui/src/views/WorkflowHistory.vue +++ b/packages/editor-ui/src/views/WorkflowHistory.vue @@ -17,6 +17,7 @@ import WorkflowHistoryContent from '@/components/WorkflowHistory/WorkflowHistory import { useWorkflowHistoryStore } from '@/stores/workflowHistory.store'; import { useUIStore } from '@/stores/ui.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; +import { telemetry } from '@/plugins/telemetry'; type WorkflowHistoryActionRecord = { [K in Uppercase]: Lowercase; @@ -73,6 +74,12 @@ const isFirstItemShown = computed( ); const evaluatedPruneTime = computed(() => Math.floor(workflowHistoryStore.evaluatedPruneTime / 24)); +const sendTelemetry = (event: string) => { + telemetry.track(event, { + workflow_id: route.params.workflowId, + }); +}; + const loadMore = async (queryParams: WorkflowHistoryRequestParams) => { const history = await workflowHistoryStore.getWorkflowHistory( route.params.workflowId, @@ -83,6 +90,7 @@ const loadMore = async (queryParams: WorkflowHistoryRequestParams) => { }; onBeforeMount(async () => { + sendTelemetry('User opened workflow history'); try { const [workflow] = await Promise.all([ workflowsStore.fetchWorkflow(route.params.workflowId), @@ -233,15 +241,19 @@ const onAction = async ({ switch (action) { case WORKFLOW_HISTORY_ACTIONS.OPEN: openInNewTab(id); + sendTelemetry('User opened version in new tab'); break; case WORKFLOW_HISTORY_ACTIONS.DOWNLOAD: await workflowHistoryStore.downloadVersion(route.params.workflowId, id, data); + sendTelemetry('User downloaded version'); break; case WORKFLOW_HISTORY_ACTIONS.CLONE: await cloneWorkflowVersion(id, data); + sendTelemetry('User cloned version'); break; case WORKFLOW_HISTORY_ACTIONS.RESTORE: await restoreWorkflowVersion(id, data); + sendTelemetry('User restored version'); break; } } catch (error) { @@ -259,6 +271,7 @@ const onAction = async ({ const onPreview = async ({ event, id }: { event: MouseEvent; id: WorkflowVersionId }) => { if (event.metaKey || event.ctrlKey) { openInNewTab(id); + sendTelemetry('User opened version in new tab'); } else { await router.push({ name: VIEWS.WORKFLOW_HISTORY, @@ -283,6 +296,7 @@ watchEffect(async () => { route.params.workflowId, route.params.versionId, ); + sendTelemetry('User selected version'); } catch (error) { toast.showError( new Error(`${error.message} "${route.params.versionId}" `), diff --git a/packages/editor-ui/src/views/__tests__/WorkflowHistory.test.ts b/packages/editor-ui/src/views/__tests__/WorkflowHistory.test.ts index 2710274b18aeb..23a0ce13eee33 100644 --- a/packages/editor-ui/src/views/__tests__/WorkflowHistory.test.ts +++ b/packages/editor-ui/src/views/__tests__/WorkflowHistory.test.ts @@ -17,6 +17,7 @@ import { } from '@/stores/__tests__/utils/workflowHistoryTestUtils'; import type { WorkflowVersion } from '@/types/workflowHistory'; import type { IWorkflowDb } from '@/Interface'; +import { telemetry } from '@/plugins/telemetry'; vi.mock('vue-router', () => { const params = {}; @@ -56,7 +57,9 @@ const renderComponent = createComponentRenderer(WorkflowHistoryPage, { }, template: `
`, }), }, @@ -85,6 +88,7 @@ describe('WorkflowHistory', () => { vi.spyOn(workflowsStore, 'fetchWorkflow').mockResolvedValue({} as IWorkflowDb); vi.spyOn(workflowHistoryStore, 'getWorkflowHistory').mockResolvedValue(historyData); vi.spyOn(workflowHistoryStore, 'getWorkflowVersion').mockResolvedValue(versionData); + vi.spyOn(telemetry, 'track').mockImplementation(() => {}); windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null); }); @@ -97,12 +101,15 @@ describe('WorkflowHistory', () => { renderComponent({ pinia }); - await waitFor(() => + await waitFor(() => { expect(router.replace).toHaveBeenCalledWith({ name: VIEWS.WORKFLOW_HISTORY, params: { workflowId, versionId: versionData.versionId }, - }), - ); + }); + expect(telemetry.track).toHaveBeenCalledWith('User opened workflow history', { + workflow_id: workflowId, + }); + }); }); it('should load version data if path contains /:versionId', async () => { @@ -113,8 +120,13 @@ describe('WorkflowHistory', () => { renderComponent({ pinia }); - await waitFor(() => expect(router.replace).not.toHaveBeenCalled()); expect(getWorkflowVersionSpy).toHaveBeenCalledWith(workflowId, versionData.versionId); + await waitFor(() => { + expect(router.replace).not.toHaveBeenCalled(); + expect(telemetry.track).toHaveBeenCalledWith('User selected version', { + workflow_id: workflowId, + }); + }); }); it('should change path on preview', async () => { @@ -124,12 +136,33 @@ describe('WorkflowHistory', () => { await userEvent.click(getByTestId('stub-preview-button')); - await waitFor(() => + await waitFor(() => { expect(router.push).toHaveBeenCalledWith({ name: VIEWS.WORKFLOW_HISTORY, params: { workflowId, versionId }, - }), - ); + }); + expect(telemetry.track).toHaveBeenCalledWith('User selected version', { + workflow_id: workflowId, + }); + }); + }); + + it('should open preview in new tab if open action is dispatched', async () => { + route.params.workflowId = workflowId; + const { getByTestId } = renderComponent({ pinia }); + + await userEvent.click(getByTestId('stub-open-button')); + + await waitFor(() => { + expect(router.resolve).toHaveBeenCalledWith({ + name: VIEWS.WORKFLOW_HISTORY, + params: { workflowId, versionId }, + }); + expect(telemetry.track).toHaveBeenCalledWith('User opened version in new tab', { + workflow_id: workflowId, + }); + }); + expect(windowOpenSpy).toHaveBeenCalled(); }); it('should open preview in new tab if meta key used', async () => { @@ -141,12 +174,15 @@ describe('WorkflowHistory', () => { await user.keyboard('[ControlLeft>]'); await user.click(getByTestId('stub-preview-button')); - await waitFor(() => + await waitFor(() => { expect(router.resolve).toHaveBeenCalledWith({ name: VIEWS.WORKFLOW_HISTORY, params: { workflowId, versionId }, - }), - ); + }); + expect(telemetry.track).toHaveBeenCalledWith('User opened version in new tab', { + workflow_id: workflowId, + }); + }); expect(windowOpenSpy).toHaveBeenCalled(); }); @@ -160,13 +196,29 @@ describe('WorkflowHistory', () => { const { getByTestId, getByRole } = renderComponent({ pinia }); await userEvent.click(getByTestId('stub-clone-button')); - await waitFor(() => + await waitFor(() => { expect(router.resolve).toHaveBeenCalledWith({ name: VIEWS.WORKFLOW, params: { name: newWorkflowId }, - }), - ); + }); + expect(telemetry.track).toHaveBeenCalledWith('User cloned version', { + workflow_id: workflowId, + }); + }); expect(within(getByRole('alert')).getByRole('link')).toBeInTheDocument(); }); + + it('should download workflow version', async () => { + route.params.workflowId = workflowId; + + const { getByTestId } = renderComponent({ pinia }); + await userEvent.click(getByTestId('stub-download-button')); + + await waitFor(() => { + expect(telemetry.track).toHaveBeenCalledWith('User downloaded version', { + workflow_id: workflowId, + }); + }); + }); });