mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 12:44:07 -08:00
fix(editor): Add telemetry to workflow history (#7811)
This commit is contained in:
parent
c0633987bf
commit
d4970410e1
|
@ -1754,7 +1754,8 @@ export type CloudUpdateLinkSourceType =
|
||||||
| 'usage_page'
|
| 'usage_page'
|
||||||
| 'settings-users'
|
| 'settings-users'
|
||||||
| 'variables'
|
| 'variables'
|
||||||
| 'community-nodes';
|
| 'community-nodes'
|
||||||
|
| 'workflow-history';
|
||||||
|
|
||||||
export type UTMCampaign =
|
export type UTMCampaign =
|
||||||
| 'upgrade-custom-data-filter'
|
| 'upgrade-custom-data-filter'
|
||||||
|
@ -1770,7 +1771,8 @@ export type UTMCampaign =
|
||||||
| 'open'
|
| 'open'
|
||||||
| 'upgrade-users'
|
| 'upgrade-users'
|
||||||
| 'upgrade-variables'
|
| 'upgrade-variables'
|
||||||
| 'upgrade-community-nodes';
|
| 'upgrade-community-nodes'
|
||||||
|
| 'upgrade-workflow-history';
|
||||||
|
|
||||||
export type N8nBanners = {
|
export type N8nBanners = {
|
||||||
[key in BannerName]: {
|
[key in BannerName]: {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import WorkflowHistoryContent from '@/components/WorkflowHistory/WorkflowHistory
|
||||||
import { useWorkflowHistoryStore } from '@/stores/workflowHistory.store';
|
import { useWorkflowHistoryStore } from '@/stores/workflowHistory.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { telemetry } from '@/plugins/telemetry';
|
||||||
|
|
||||||
type WorkflowHistoryActionRecord = {
|
type WorkflowHistoryActionRecord = {
|
||||||
[K in Uppercase<WorkflowHistoryActionTypes[number]>]: Lowercase<K>;
|
[K in Uppercase<WorkflowHistoryActionTypes[number]>]: Lowercase<K>;
|
||||||
|
@ -73,6 +74,12 @@ const isFirstItemShown = computed(
|
||||||
);
|
);
|
||||||
const evaluatedPruneTime = computed(() => Math.floor(workflowHistoryStore.evaluatedPruneTime / 24));
|
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 loadMore = async (queryParams: WorkflowHistoryRequestParams) => {
|
||||||
const history = await workflowHistoryStore.getWorkflowHistory(
|
const history = await workflowHistoryStore.getWorkflowHistory(
|
||||||
route.params.workflowId,
|
route.params.workflowId,
|
||||||
|
@ -83,6 +90,7 @@ const loadMore = async (queryParams: WorkflowHistoryRequestParams) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
|
sendTelemetry('User opened workflow history');
|
||||||
try {
|
try {
|
||||||
const [workflow] = await Promise.all([
|
const [workflow] = await Promise.all([
|
||||||
workflowsStore.fetchWorkflow(route.params.workflowId),
|
workflowsStore.fetchWorkflow(route.params.workflowId),
|
||||||
|
@ -233,15 +241,19 @@ const onAction = async ({
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case WORKFLOW_HISTORY_ACTIONS.OPEN:
|
case WORKFLOW_HISTORY_ACTIONS.OPEN:
|
||||||
openInNewTab(id);
|
openInNewTab(id);
|
||||||
|
sendTelemetry('User opened version in new tab');
|
||||||
break;
|
break;
|
||||||
case WORKFLOW_HISTORY_ACTIONS.DOWNLOAD:
|
case WORKFLOW_HISTORY_ACTIONS.DOWNLOAD:
|
||||||
await workflowHistoryStore.downloadVersion(route.params.workflowId, id, data);
|
await workflowHistoryStore.downloadVersion(route.params.workflowId, id, data);
|
||||||
|
sendTelemetry('User downloaded version');
|
||||||
break;
|
break;
|
||||||
case WORKFLOW_HISTORY_ACTIONS.CLONE:
|
case WORKFLOW_HISTORY_ACTIONS.CLONE:
|
||||||
await cloneWorkflowVersion(id, data);
|
await cloneWorkflowVersion(id, data);
|
||||||
|
sendTelemetry('User cloned version');
|
||||||
break;
|
break;
|
||||||
case WORKFLOW_HISTORY_ACTIONS.RESTORE:
|
case WORKFLOW_HISTORY_ACTIONS.RESTORE:
|
||||||
await restoreWorkflowVersion(id, data);
|
await restoreWorkflowVersion(id, data);
|
||||||
|
sendTelemetry('User restored version');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -259,6 +271,7 @@ const onAction = async ({
|
||||||
const onPreview = async ({ event, id }: { event: MouseEvent; id: WorkflowVersionId }) => {
|
const onPreview = async ({ event, id }: { event: MouseEvent; id: WorkflowVersionId }) => {
|
||||||
if (event.metaKey || event.ctrlKey) {
|
if (event.metaKey || event.ctrlKey) {
|
||||||
openInNewTab(id);
|
openInNewTab(id);
|
||||||
|
sendTelemetry('User opened version in new tab');
|
||||||
} else {
|
} else {
|
||||||
await router.push({
|
await router.push({
|
||||||
name: VIEWS.WORKFLOW_HISTORY,
|
name: VIEWS.WORKFLOW_HISTORY,
|
||||||
|
@ -283,6 +296,7 @@ watchEffect(async () => {
|
||||||
route.params.workflowId,
|
route.params.workflowId,
|
||||||
route.params.versionId,
|
route.params.versionId,
|
||||||
);
|
);
|
||||||
|
sendTelemetry('User selected version');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.showError(
|
toast.showError(
|
||||||
new Error(`${error.message} "${route.params.versionId}" `),
|
new Error(`${error.message} "${route.params.versionId}" `),
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
} from '@/stores/__tests__/utils/workflowHistoryTestUtils';
|
} from '@/stores/__tests__/utils/workflowHistoryTestUtils';
|
||||||
import type { WorkflowVersion } from '@/types/workflowHistory';
|
import type { WorkflowVersion } from '@/types/workflowHistory';
|
||||||
import type { IWorkflowDb } from '@/Interface';
|
import type { IWorkflowDb } from '@/Interface';
|
||||||
|
import { telemetry } from '@/plugins/telemetry';
|
||||||
|
|
||||||
vi.mock('vue-router', () => {
|
vi.mock('vue-router', () => {
|
||||||
const params = {};
|
const params = {};
|
||||||
|
@ -56,7 +57,9 @@ const renderComponent = createComponentRenderer(WorkflowHistoryPage, {
|
||||||
},
|
},
|
||||||
template: `<div>
|
template: `<div>
|
||||||
<button data-test-id="stub-preview-button" @click="event => $emit('preview', {id, event})" />
|
<button data-test-id="stub-preview-button" @click="event => $emit('preview', {id, event})" />
|
||||||
|
<button data-test-id="stub-open-button" @click="() => $emit('action', { action: 'open', id })" />
|
||||||
<button data-test-id="stub-clone-button" @click="() => $emit('action', { action: 'clone', id })" />
|
<button data-test-id="stub-clone-button" @click="() => $emit('action', { action: 'clone', id })" />
|
||||||
|
<button data-test-id="stub-download-button" @click="() => $emit('action', { action: 'download', id })" />
|
||||||
</div>`,
|
</div>`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -85,6 +88,7 @@ describe('WorkflowHistory', () => {
|
||||||
vi.spyOn(workflowsStore, 'fetchWorkflow').mockResolvedValue({} as IWorkflowDb);
|
vi.spyOn(workflowsStore, 'fetchWorkflow').mockResolvedValue({} as IWorkflowDb);
|
||||||
vi.spyOn(workflowHistoryStore, 'getWorkflowHistory').mockResolvedValue(historyData);
|
vi.spyOn(workflowHistoryStore, 'getWorkflowHistory').mockResolvedValue(historyData);
|
||||||
vi.spyOn(workflowHistoryStore, 'getWorkflowVersion').mockResolvedValue(versionData);
|
vi.spyOn(workflowHistoryStore, 'getWorkflowVersion').mockResolvedValue(versionData);
|
||||||
|
vi.spyOn(telemetry, 'track').mockImplementation(() => {});
|
||||||
windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
|
windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,12 +101,15 @@ describe('WorkflowHistory', () => {
|
||||||
|
|
||||||
renderComponent({ pinia });
|
renderComponent({ pinia });
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() => {
|
||||||
expect(router.replace).toHaveBeenCalledWith({
|
expect(router.replace).toHaveBeenCalledWith({
|
||||||
name: VIEWS.WORKFLOW_HISTORY,
|
name: VIEWS.WORKFLOW_HISTORY,
|
||||||
params: { workflowId, versionId: versionData.versionId },
|
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 () => {
|
it('should load version data if path contains /:versionId', async () => {
|
||||||
|
@ -113,8 +120,13 @@ describe('WorkflowHistory', () => {
|
||||||
|
|
||||||
renderComponent({ pinia });
|
renderComponent({ pinia });
|
||||||
|
|
||||||
await waitFor(() => expect(router.replace).not.toHaveBeenCalled());
|
|
||||||
expect(getWorkflowVersionSpy).toHaveBeenCalledWith(workflowId, versionData.versionId);
|
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 () => {
|
it('should change path on preview', async () => {
|
||||||
|
@ -124,12 +136,33 @@ describe('WorkflowHistory', () => {
|
||||||
|
|
||||||
await userEvent.click(getByTestId('stub-preview-button'));
|
await userEvent.click(getByTestId('stub-preview-button'));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() => {
|
||||||
expect(router.push).toHaveBeenCalledWith({
|
expect(router.push).toHaveBeenCalledWith({
|
||||||
name: VIEWS.WORKFLOW_HISTORY,
|
name: VIEWS.WORKFLOW_HISTORY,
|
||||||
params: { workflowId, versionId },
|
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 () => {
|
it('should open preview in new tab if meta key used', async () => {
|
||||||
|
@ -141,12 +174,15 @@ describe('WorkflowHistory', () => {
|
||||||
await user.keyboard('[ControlLeft>]');
|
await user.keyboard('[ControlLeft>]');
|
||||||
await user.click(getByTestId('stub-preview-button'));
|
await user.click(getByTestId('stub-preview-button'));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() => {
|
||||||
expect(router.resolve).toHaveBeenCalledWith({
|
expect(router.resolve).toHaveBeenCalledWith({
|
||||||
name: VIEWS.WORKFLOW_HISTORY,
|
name: VIEWS.WORKFLOW_HISTORY,
|
||||||
params: { workflowId, versionId },
|
params: { workflowId, versionId },
|
||||||
}),
|
});
|
||||||
);
|
expect(telemetry.track).toHaveBeenCalledWith('User opened version in new tab', {
|
||||||
|
workflow_id: workflowId,
|
||||||
|
});
|
||||||
|
});
|
||||||
expect(windowOpenSpy).toHaveBeenCalled();
|
expect(windowOpenSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -160,13 +196,29 @@ describe('WorkflowHistory', () => {
|
||||||
const { getByTestId, getByRole } = renderComponent({ pinia });
|
const { getByTestId, getByRole } = renderComponent({ pinia });
|
||||||
await userEvent.click(getByTestId('stub-clone-button'));
|
await userEvent.click(getByTestId('stub-clone-button'));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() => {
|
||||||
expect(router.resolve).toHaveBeenCalledWith({
|
expect(router.resolve).toHaveBeenCalledWith({
|
||||||
name: VIEWS.WORKFLOW,
|
name: VIEWS.WORKFLOW,
|
||||||
params: { name: newWorkflowId },
|
params: { name: newWorkflowId },
|
||||||
}),
|
});
|
||||||
);
|
expect(telemetry.track).toHaveBeenCalledWith('User cloned version', {
|
||||||
|
workflow_id: workflowId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
expect(within(getByRole('alert')).getByRole('link')).toBeInTheDocument();
|
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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue