feat(editor): Add telemetry for schema preview (no-changelog) (#13901)

This commit is contained in:
Elias Meire
2025-03-13 18:50:19 +01:00
committed by GitHub
parent dedcdbd314
commit c9be48ea20
5 changed files with 131 additions and 32 deletions

View File

@@ -31,6 +31,7 @@ const mockNode1 = createTestNode({
type: MANUAL_TRIGGER_NODE_TYPE,
typeVersion: 1,
disabled: false,
credentials: undefined,
});
const mockNode2 = createTestNode({
@@ -419,15 +420,28 @@ describe('VirtualSchema.vue', () => {
});
});
it('should handle drop event', async () => {
describe('telemetry', () => {
function dragDropPill(pill: HTMLElement) {
const ndvStore = useNDVStore();
const reset = vi.spyOn(ndvStore, 'resetMappingTelemetry');
fireEvent(pill, new MouseEvent('mousedown', { bubbles: true }));
fireEvent(window, new MouseEvent('mousemove', { bubbles: true }));
expect(reset).toHaveBeenCalled();
vi.useFakeTimers({ toFake: ['setTimeout'] });
fireEvent(window, new MouseEvent('mouseup', { bubbles: true }));
vi.advanceTimersByTime(250);
vi.useRealTimers();
}
it('should track data pill drag and drop', async () => {
useWorkflowsStore().pinData({
node: mockNode1,
data: [{ json: { name: 'John', age: 22, hobbies: ['surfing', 'traveling'] } }],
});
const telemetry = useTelemetry();
const trackSpy = vi.spyOn(telemetry, 'track');
const reset = vi.spyOn(ndvStore, 'resetMappingTelemetry');
const { getAllByTestId } = renderComponent();
await waitFor(() => {
@@ -438,23 +452,81 @@ describe('VirtualSchema.vue', () => {
expect(items[0].className).toBe('schema-item draggable');
expect(items[0]).toHaveTextContent('nameJohn');
const pill = items[0].querySelector('.pill') as Element;
fireEvent(pill, new MouseEvent('mousedown', { bubbles: true }));
fireEvent(window, new MouseEvent('mousemove', { bubbles: true }));
expect(reset).toHaveBeenCalled();
fireEvent(window, new MouseEvent('mouseup', { bubbles: true }));
const pill = items[0].querySelector('.pill') as HTMLElement;
dragDropPill(pill);
await waitFor(() =>
expect(trackSpy).toHaveBeenCalledWith(
'User dragged data for mapping',
expect.any(Object),
expect.any(Object),
expect.objectContaining({
src_view: 'schema',
src_field_name: 'name',
src_field_nest_level: 0,
src_node_type: 'n8n-nodes-base.manualTrigger',
src_nodes_back: '1',
src_has_credential: false,
}),
{ withPostHog: true },
),
);
});
it('should track data pill drag and drop for schema preview', async () => {
useWorkflowsStore().pinData({
node: {
...mockNode2,
credentials: { myCredential: { id: 'myCredential', name: 'myCredential' } },
},
data: [],
});
const telemetry = useTelemetry();
const trackSpy = vi.spyOn(telemetry, 'track');
const posthogStore = usePostHog();
vi.spyOn(posthogStore, 'isFeatureEnabled').mockReturnValue(true);
const schemaPreviewStore = useSchemaPreviewStore();
vi.spyOn(schemaPreviewStore, 'getSchemaPreview').mockResolvedValue(
createResultOk({
type: 'object',
properties: {
account: {
type: 'object',
properties: {
id: {
type: 'string',
},
},
},
},
}),
);
const { getAllByTestId } = renderComponent({
props: {
nodes: [{ name: mockNode2.name, indicies: [], depth: 1 }],
},
});
await waitFor(() => {
expect(getAllByTestId('run-data-schema-item')).toHaveLength(2);
});
const pill = getAllByTestId('run-data-schema-item')[0].querySelector('.pill') as HTMLElement;
dragDropPill(pill);
await waitFor(() =>
expect(trackSpy).toHaveBeenCalledWith(
'User dragged data for mapping',
expect.objectContaining({
src_view: 'schema_preview',
src_has_credential: true,
}),
{ withPostHog: true },
),
);
});
});
it('should expand all nodes when searching', async () => {
useWorkflowsStore().pinData({
node: mockNode1,

View File

@@ -32,6 +32,7 @@ import { useSchemaPreviewStore } from '@/stores/schemaPreview.store';
import { asyncComputed } from '@vueuse/core';
import { usePostHog } from '@/stores/posthog.store';
import { SCHEMA_PREVIEW_EXPERIMENT } from '@/constants';
import { isEmpty } from '@/utils/typesUtils';
type Props = {
nodes?: IConnectedNode[];
@@ -244,6 +245,11 @@ const onDragStart = () => {
const onDragEnd = (el: HTMLElement) => {
setTimeout(() => {
const mappingTelemetry = ndvStore.mappingTelemetry;
const parentNode = nodesSchemas.value.find(({ node }) => node.name === el.dataset.nodeName);
const isPreview = parentNode?.preview ?? false;
const hasCredential = !isEmpty(parentNode?.node.credentials);
const telemetryPayload = {
src_node_type: el.dataset.nodeType,
src_field_name: el.dataset.name ?? '',
@@ -251,7 +257,8 @@ const onDragEnd = (el: HTMLElement) => {
src_run_index: props.runIndex,
src_runs_total: props.totalRuns,
src_field_nest_level: el.dataset.level ?? 0,
src_view: 'schema',
src_view: isPreview ? 'schema_preview' : 'schema',
src_has_credential: hasCredential,
src_element: el,
success: false,
...mappingTelemetry,

View File

@@ -12,6 +12,7 @@ type Props = {
id: string;
icon: string;
collapsable?: boolean;
nodeName?: string;
nodeType?: string;
highlight?: boolean;
draggable?: boolean;
@@ -21,6 +22,7 @@ type Props = {
};
const props = defineProps<Props>();
const emit = defineEmits<{
click: [];
}>();
@@ -41,6 +43,7 @@ const emit = defineEmits<{
:data-nest-level="level"
:data-value="expression"
:data-node-type="nodeType"
:data-node-name="nodeName"
data-target="mappable"
class="pill"
:class="{

View File

@@ -200,6 +200,7 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
data-depth="1"
data-name="account"
data-nest-level="1"
data-node-name="Manual Trigger"
data-node-type="n8n-nodes-base.manualTrigger"
data-path=".account"
data-target="mappable"
@@ -272,6 +273,7 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
data-depth="1"
data-name="id"
data-nest-level="2"
data-node-name="Manual Trigger"
data-node-type="n8n-nodes-base.manualTrigger"
data-path=".account.id"
data-target="mappable"
@@ -525,6 +527,7 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
data-depth="2"
data-name="account"
data-nest-level="1"
data-node-name="Set2"
data-node-type="n8n-nodes-base.set"
data-path=".account"
data-target="mappable"
@@ -597,6 +600,7 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
data-depth="2"
data-name="id"
data-nest-level="2"
data-node-name="Set2"
data-node-type="n8n-nodes-base.set"
data-path=".account.id"
data-target="mappable"
@@ -882,6 +886,7 @@ exports[`VirtualSchema.vue > renders schema in output pane 1`] = `
data-depth="0"
data-name="name"
data-nest-level="0"
data-node-name=""
data-node-type=""
data-path=".name"
data-target="mappable"
@@ -959,6 +964,7 @@ exports[`VirtualSchema.vue > renders schema in output pane 1`] = `
data-depth="0"
data-name="age"
data-nest-level="0"
data-node-name=""
data-node-type=""
data-path=".age"
data-target="mappable"
@@ -1062,6 +1068,7 @@ exports[`VirtualSchema.vue > renders schema in output pane 1`] = `
data-depth="0"
data-name="hobbies"
data-nest-level="0"
data-node-name=""
data-node-type=""
data-path=".hobbies"
data-target="mappable"
@@ -1134,6 +1141,7 @@ exports[`VirtualSchema.vue > renders schema in output pane 1`] = `
data-depth="0"
data-name="hobbies[0]"
data-nest-level="1"
data-node-name=""
data-node-type=""
data-path=".hobbies[0]"
data-target="mappable"
@@ -1211,6 +1219,7 @@ exports[`VirtualSchema.vue > renders schema in output pane 1`] = `
data-depth="0"
data-name="hobbies[1]"
data-nest-level="1"
data-node-name=""
data-node-type=""
data-path=".hobbies[1]"
data-target="mappable"
@@ -1446,6 +1455,7 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
data-depth="1"
data-name="hello world"
data-nest-level="1"
data-node-name="Manual Trigger"
data-node-type="n8n-nodes-base.manualTrigger"
data-path="['hello world']"
data-target="mappable"
@@ -1544,6 +1554,7 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
data-depth="1"
data-name="hello world[0]"
data-nest-level="2"
data-node-name="Manual Trigger"
data-node-type="n8n-nodes-base.manualTrigger"
data-path="['hello world'][0]"
data-target="mappable"
@@ -1642,6 +1653,7 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
data-depth="1"
data-name="test"
data-nest-level="3"
data-node-name="Manual Trigger"
data-node-type="n8n-nodes-base.manualTrigger"
data-path="['hello world'][0].test"
data-target="mappable"
@@ -1714,6 +1726,7 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
data-depth="1"
data-name="more to think about"
data-nest-level="4"
data-node-name="Manual Trigger"
data-node-type="n8n-nodes-base.manualTrigger"
data-path="['hello world'][0].test['more to think about']"
data-target="mappable"
@@ -1791,6 +1804,7 @@ exports[`VirtualSchema.vue > renders schema with spaces and dots 1`] = `
data-depth="1"
data-name="test.how"
data-nest-level="3"
data-node-name="Manual Trigger"
data-node-type="n8n-nodes-base.manualTrigger"
data-path="['hello world'][0]['test.how']"
data-target="mappable"

View File

@@ -245,6 +245,7 @@ export type RenderItem = {
id: string;
icon: string;
collapsable?: boolean;
nodeName?: string;
nodeType?: INodeUi['type'];
preview?: boolean;
type: 'item';
@@ -371,6 +372,7 @@ export const useFlattenSchema = () => {
icon: getIconBySchemaType(schema.type),
id,
collapsable: true,
nodeName: node.name,
nodeType: node.type,
type: 'item',
preview,
@@ -409,6 +411,7 @@ export const useFlattenSchema = () => {
icon: getIconBySchemaType(schema.type),
collapsable: false,
nodeType: node.type,
nodeName: node.name,
type: 'item',
preview,
},