mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Add telemetry to source control feature (#13016)
This commit is contained in:
parent
cc907fbca9
commit
18eaa5423d
|
@ -110,8 +110,7 @@ async function pullWorkfolder() {
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<N8nText tag="div" class="mb-xs">
|
<N8nText tag="div" class="mb-xs">
|
||||||
These resources will be updated or deleted, and any local changes to them will be lost. To
|
{{ i18n.baseText('settings.sourceControl.modals.pull.description') }}
|
||||||
keep the local version, push it before pulling.
|
|
||||||
<br />
|
<br />
|
||||||
<N8nLink :to="i18n.baseText('settings.sourceControl.docs.using.pushPull.url')">
|
<N8nLink :to="i18n.baseText('settings.sourceControl.docs.using.pushPull.url')">
|
||||||
{{ i18n.baseText('settings.sourceControl.modals.push.description.learnMore') }}
|
{{ i18n.baseText('settings.sourceControl.modals.push.description.learnMore') }}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import type { SourceControlledFile } from '@n8n/api-types';
|
||||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||||
import { mockedStore } from '@/__tests__/utils';
|
import { mockedStore } from '@/__tests__/utils';
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
|
||||||
const eventBus = createEventBus();
|
const eventBus = createEventBus();
|
||||||
|
|
||||||
|
@ -22,7 +23,19 @@ vi.mock('vue-router', () => ({
|
||||||
useRouter: vi.fn(),
|
useRouter: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/composables/useTelemetry', () => {
|
||||||
|
const track = vi.fn();
|
||||||
|
return {
|
||||||
|
useTelemetry: () => {
|
||||||
|
return {
|
||||||
|
track,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
let route: ReturnType<typeof useRoute>;
|
let route: ReturnType<typeof useRoute>;
|
||||||
|
let telemetry: ReturnType<typeof useTelemetry>;
|
||||||
|
|
||||||
const DynamicScrollerStub = {
|
const DynamicScrollerStub = {
|
||||||
props: {
|
props: {
|
||||||
|
@ -59,7 +72,9 @@ const renderModal = createComponentRenderer(SourceControlPushModal, {
|
||||||
|
|
||||||
describe('SourceControlPushModal', () => {
|
describe('SourceControlPushModal', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
route = useRoute();
|
route = useRoute();
|
||||||
|
telemetry = useTelemetry();
|
||||||
createTestingPinia();
|
createTestingPinia();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -319,9 +334,12 @@ describe('SourceControlPushModal', () => {
|
||||||
expect(getAllByTestId('source-control-push-modal-file-checkbox')).toHaveLength(2);
|
expect(getAllByTestId('source-control-push-modal-file-checkbox')).toHaveLength(2);
|
||||||
|
|
||||||
await userEvent.type(getByTestId('source-control-push-search'), '1');
|
await userEvent.type(getByTestId('source-control-push-search'), '1');
|
||||||
await waitFor(() =>
|
await waitFor(() => {
|
||||||
expect(getAllByTestId('source-control-push-modal-file-checkbox')).toHaveLength(1),
|
expect(getAllByTestId('source-control-push-modal-file-checkbox')).toHaveLength(1);
|
||||||
);
|
expect(telemetry.track).toHaveBeenCalledWith('User searched workflows in commit modal', {
|
||||||
|
search: '1',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter by status', async () => {
|
it('should filter by status', async () => {
|
||||||
|
@ -379,6 +397,9 @@ describe('SourceControlPushModal', () => {
|
||||||
const items = getAllByTestId('source-control-push-modal-file-checkbox');
|
const items = getAllByTestId('source-control-push-modal-file-checkbox');
|
||||||
expect(items).toHaveLength(1);
|
expect(items).toHaveLength(1);
|
||||||
expect(items[0]).toHaveTextContent('Created Workflow');
|
expect(items[0]).toHaveTextContent('Created Workflow');
|
||||||
|
expect(telemetry.track).toHaveBeenCalledWith('User filtered by status in commit modal', {
|
||||||
|
status: 'created',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Modal from './Modal.vue';
|
import Modal from './Modal.vue';
|
||||||
import { SOURCE_CONTROL_PUSH_MODAL_KEY, VIEWS } from '@/constants';
|
import { SOURCE_CONTROL_PUSH_MODAL_KEY, VIEWS } from '@/constants';
|
||||||
import { computed, onMounted, ref, toRaw } from 'vue';
|
import { computed, onMounted, ref, toRaw, watch } from 'vue';
|
||||||
import type { EventBus } from 'n8n-design-system/utils';
|
import type { EventBus } from 'n8n-design-system/utils';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useLoadingService } from '@/composables/useLoadingService';
|
import { useLoadingService } from '@/composables/useLoadingService';
|
||||||
|
@ -37,6 +37,7 @@ import {
|
||||||
} from '@n8n/api-types';
|
} from '@n8n/api-types';
|
||||||
import { orderBy, groupBy } from 'lodash-es';
|
import { orderBy, groupBy } from 'lodash-es';
|
||||||
import { getStatusText, getStatusTheme, getPushPriorityByStatus } from '@/utils/sourceControlUtils';
|
import { getStatusText, getStatusTheme, getPushPriorityByStatus } from '@/utils/sourceControlUtils';
|
||||||
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: { eventBus: EventBus; status: SourceControlledFile[] };
|
data: { eventBus: EventBus; status: SourceControlledFile[] };
|
||||||
|
@ -48,6 +49,7 @@ const toast = useToast();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const sourceControlStore = useSourceControlStore();
|
const sourceControlStore = useSourceControlStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
const concatenateWithAnd = (messages: string[]) =>
|
const concatenateWithAnd = (messages: string[]) =>
|
||||||
new Intl.ListFormat(i18n.locale, { style: 'long', type: 'conjunction' }).format(messages);
|
new Intl.ListFormat(i18n.locale, { style: 'long', type: 'conjunction' }).format(messages);
|
||||||
|
@ -191,16 +193,12 @@ const filteredWorkflows = computed(() => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.value.status && filters.value.status !== workflow.status) {
|
return !(filters.value.status && filters.value.status !== workflow.status);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedWorkflows = computed(() => {
|
const sortedWorkflows = computed(() =>
|
||||||
const sorted = orderBy(
|
orderBy(
|
||||||
filteredWorkflows.value,
|
filteredWorkflows.value,
|
||||||
[
|
[
|
||||||
// keep the current workflow at the top of the list
|
// keep the current workflow at the top of the list
|
||||||
|
@ -209,10 +207,8 @@ const sortedWorkflows = computed(() => {
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
],
|
],
|
||||||
['desc', 'asc', 'desc'],
|
['desc', 'asc', 'desc'],
|
||||||
);
|
),
|
||||||
|
);
|
||||||
return sorted;
|
|
||||||
});
|
|
||||||
|
|
||||||
const commitMessage = ref('');
|
const commitMessage = ref('');
|
||||||
const isSubmitDisabled = computed(() => {
|
const isSubmitDisabled = computed(() => {
|
||||||
|
@ -225,11 +221,8 @@ const isSubmitDisabled = computed(() => {
|
||||||
changes.value.tags.length +
|
changes.value.tags.length +
|
||||||
changes.value.variables.length +
|
changes.value.variables.length +
|
||||||
selectedChanges.value.size;
|
selectedChanges.value.size;
|
||||||
if (toBePushed <= 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return toBePushed <= 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedWorkflowsSet = computed(() => new Set(sortedWorkflows.value.map(({ id }) => id)));
|
const sortedWorkflowsSet = computed(() => new Set(sortedWorkflows.value.map(({ id }) => id)));
|
||||||
|
@ -354,6 +347,16 @@ async function commitAndPush() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const modalHeight = computed(() => (changes.value.workflows.length ? 'min(80vh, 850px)' : 'auto'));
|
const modalHeight = computed(() => (changes.value.workflows.length ? 'min(80vh, 850px)' : 'auto'));
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => filters.value.status,
|
||||||
|
(status) => {
|
||||||
|
telemetry.track('User filtered by status in commit modal', { status });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
watch(refDebounced(search, 500), (term) => {
|
||||||
|
telemetry.track('User searched workflows in commit modal', { search: term });
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -308,6 +308,20 @@ watch(
|
||||||
() => callDebounced(sendFiltersTelemetry, { debounceTime: 1000, trailing: true }, 'search'),
|
() => callDebounced(sendFiltersTelemetry, { debounceTime: 1000, trailing: true }, 'search'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => filtersModel.value.setupNeeded,
|
||||||
|
() => {
|
||||||
|
sendFiltersTelemetry('setupNeeded');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => filtersModel.value.incomplete,
|
||||||
|
() => {
|
||||||
|
sendFiltersTelemetry('incomplete');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => sortBy.value,
|
() => sortBy.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import type { BaseTextKey } from '@/plugins/i18n';
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
import { groupBy } from 'lodash-es';
|
import { groupBy } from 'lodash-es';
|
||||||
import type { useToast } from '@/composables/useToast';
|
import type { useToast } from '@/composables/useToast';
|
||||||
|
import { telemetry } from '@/plugins/telemetry';
|
||||||
|
|
||||||
type SourceControlledFileStatus = SourceControlledFile['status'];
|
type SourceControlledFileStatus = SourceControlledFile['status'];
|
||||||
|
|
||||||
|
@ -47,8 +48,15 @@ export const getPushPriorityByStatus = (status: SourceControlledFileStatus) =>
|
||||||
|
|
||||||
const variablesToast = {
|
const variablesToast = {
|
||||||
title: i18n.baseText('settings.sourceControl.pull.upToDate.variables.title'),
|
title: i18n.baseText('settings.sourceControl.pull.upToDate.variables.title'),
|
||||||
message: h(RouterLink, { to: { name: VIEWS.VARIABLES }, query: { incomplete: 'true' } }, () =>
|
message: h(
|
||||||
i18n.baseText('settings.sourceControl.pull.upToDate.variables.description'),
|
RouterLink,
|
||||||
|
{
|
||||||
|
to: { name: VIEWS.VARIABLES, query: { incomplete: 'true' } },
|
||||||
|
onClick: () => {
|
||||||
|
telemetry.track('User clicked review variables');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
() => i18n.baseText('settings.sourceControl.pull.upToDate.variables.description'),
|
||||||
),
|
),
|
||||||
type: 'info' as const,
|
type: 'info' as const,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
|
@ -56,8 +64,15 @@ const variablesToast = {
|
||||||
|
|
||||||
const credentialsToast = {
|
const credentialsToast = {
|
||||||
title: i18n.baseText('settings.sourceControl.pull.upToDate.credentials.title'),
|
title: i18n.baseText('settings.sourceControl.pull.upToDate.credentials.title'),
|
||||||
message: h(RouterLink, { to: { name: VIEWS.CREDENTIALS, query: { setupNeeded: 'true' } } }, () =>
|
message: h(
|
||||||
i18n.baseText('settings.sourceControl.pull.upToDate.credentials.description'),
|
RouterLink,
|
||||||
|
{
|
||||||
|
to: { name: VIEWS.CREDENTIALS, query: { setupNeeded: 'true' } },
|
||||||
|
onClick: () => {
|
||||||
|
telemetry.track('User clicked review credentials');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
() => i18n.baseText('settings.sourceControl.pull.upToDate.credentials.description'),
|
||||||
),
|
),
|
||||||
type: 'info' as const,
|
type: 'info' as const,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
|
|
Loading…
Reference in a new issue