From 0866f644b1d8f34d9d5c1ae19b79b6c9ca8da41c Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Thu, 4 Dec 2025 12:54:36 +0000 Subject: [PATCH] chore(ai-builder): Telemetry updates for pinned data (#22625) --- .../src/telemetry/__tests__/telemetry.test.ts | 58 +++++++++++++++++++ packages/cli/src/telemetry/index.ts | 1 + .../src/app/composables/usePinnedData.test.ts | 27 +++++++-- .../src/app/composables/usePinnedData.ts | 2 + .../ndv/runData/components/RunData.vue | 12 +++- 5 files changed, 93 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/telemetry/__tests__/telemetry.test.ts b/packages/cli/src/telemetry/__tests__/telemetry.test.ts index 6252a0c237d..1523d3a569b 100644 --- a/packages/cli/src/telemetry/__tests__/telemetry.test.ts +++ b/packages/cli/src/telemetry/__tests__/telemetry.test.ts @@ -384,6 +384,64 @@ describe('Telemetry', () => { }), ); }); + + test('should include instance_id, version_cli, and user_id in track properties', () => { + const eventName = 'Test Event'; + const properties = { user_id: '1234', custom_prop: 'value' }; + + telemetry.track(eventName, properties); + + expect(mockRudderStack.track).toHaveBeenCalledWith( + expect.objectContaining({ + event: eventName, + properties: expect.objectContaining({ + instance_id: instanceId, + user_id: '1234', + version_cli: expect.any(String), + custom_prop: 'value', + }), + }), + ); + }); + + test('should format userId with user_id when provided', () => { + const eventName = 'Test Event'; + const properties = { user_id: '5678' }; + + telemetry.track(eventName, properties); + + expect(mockRudderStack.track).toHaveBeenCalledWith( + expect.objectContaining({ + userId: `${instanceId}#5678`, + }), + ); + }); + + test('should format userId without user_id when not provided', () => { + const eventName = 'Test Event'; + + telemetry.track(eventName, {}); + + expect(mockRudderStack.track).toHaveBeenCalledWith( + expect.objectContaining({ + userId: instanceId, + }), + ); + }); + + test('should set user_id to undefined when not provided in properties', () => { + const eventName = 'Test Event'; + + telemetry.track(eventName, {}); + + expect(mockRudderStack.track).toHaveBeenCalledWith( + expect.objectContaining({ + properties: expect.objectContaining({ + user_id: undefined, + }), + }), + ); + }); }); }); diff --git a/packages/cli/src/telemetry/index.ts b/packages/cli/src/telemetry/index.ts index 35899bea707..f4a1f929d78 100644 --- a/packages/cli/src/telemetry/index.ts +++ b/packages/cli/src/telemetry/index.ts @@ -225,6 +225,7 @@ export class Telemetry { const updatedProperties = { ...properties, instance_id: instanceId, + user_id: user_id ?? undefined, version_cli: N8N_VERSION, }; diff --git a/packages/frontend/editor-ui/src/app/composables/usePinnedData.test.ts b/packages/frontend/editor-ui/src/app/composables/usePinnedData.test.ts index f5860c6488d..d284d118430 100644 --- a/packages/frontend/editor-ui/src/app/composables/usePinnedData.test.ts +++ b/packages/frontend/editor-ui/src/app/composables/usePinnedData.test.ts @@ -102,16 +102,35 @@ describe('usePinnedData', () => { }); describe('onSetDataSuccess()', () => { - it('should trigger telemetry on successful data setting', async () => { + it('should trigger telemetry on successful data setting with correct payload values', async () => { + const workflowsStore = useWorkflowsStore(); + workflowsStore.workflow.id = 'test-workflow-id'; + const telemetry = useTelemetry(); const spy = vi.spyOn(telemetry, 'track'); - const pinnedData = usePinnedData(ref({ name: 'testNode', type: 'someType' } as INodeUi), { + const node = ref({ + name: 'testNode', + type: 'n8n-nodes-base.httpRequest', + id: 'test-node-id', + } as INodeUi); + const pinnedData = usePinnedData(node, { displayMode: ref('json'), - runIndex: ref(0), + runIndex: ref(2), }); pinnedData.onSetDataSuccess({ source: 'pin-icon-click' }); - expect(spy).toHaveBeenCalled(); + + expect(spy).toHaveBeenCalledWith( + 'Ndv data pinning success', + expect.objectContaining({ + pinning_source: 'pin-icon-click', + node_type: 'n8n-nodes-base.httpRequest', + view: 'json', + run_index: 2, + workflow_id: 'test-workflow-id', + node_id: 'test-node-id', + }), + ); }); }); diff --git a/packages/frontend/editor-ui/src/app/composables/usePinnedData.ts b/packages/frontend/editor-ui/src/app/composables/usePinnedData.ts index a99e733bbd8..34b27e4b7fb 100644 --- a/packages/frontend/editor-ui/src/app/composables/usePinnedData.ts +++ b/packages/frontend/editor-ui/src/app/composables/usePinnedData.ts @@ -215,6 +215,8 @@ export function usePinnedData( data_size: stringSizeInBytes(data.value), view: displayMode, run_index: runIndex, + workflow_id: workflowsStore.workflowId, + node_id: targetNode?.id, }; void externalHooks.run('runData.onDataPinningSuccess', telemetryPayload); diff --git a/packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue b/packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue index bba43572c89..2dc97144287 100644 --- a/packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue +++ b/packages/frontend/editor-ui/src/features/ndv/runData/components/RunData.vue @@ -95,6 +95,7 @@ import { N8nTooltip, } from '@n8n/design-system'; import { injectWorkflowState } from '@/app/composables/useWorkflowState'; + const LazyRunDataTable = defineAsyncComponent(async () => await import('./RunDataTable.vue')); const LazyRunDataJson = defineAsyncComponent(async () => await import('./RunDataJson.vue')); @@ -807,6 +808,7 @@ function getResolvedNodeOutputs() { } return []; } + function shouldHintBeDisplayed(hint: NodeHint): boolean { const { location, whenToDisplay } = hint; @@ -963,6 +965,8 @@ function enterEditMode({ origin }: EnterEditModeArgs) { is_output_present: hasNodeRun.value || pinnedData.hasData.value, view: !hasNodeRun.value && !pinnedData.hasData.value ? 'undefined' : props.displayMode, is_data_pinned: pinnedData.hasData.value, + workflow_id: workflowsStore.workflowId, + node_id: activeNode.value?.id, }); } @@ -1038,6 +1042,8 @@ async function onTogglePinData({ source }: { source: PinDataSource | UnpinDataSo push_ref: props.pushRef, run_index: props.runIndex, view: !hasNodeRun.value && !pinnedData.hasData.value ? 'none' : props.displayMode, + workflow_id: workflowsStore.workflowId, + node_id: activeNode.value?.id, }; void externalHooks.run('runData.onTogglePinData', telemetryPayload); @@ -1725,9 +1731,9 @@ defineExpose({ enterEditMode });
- {{ - i18n.baseText('runData.pinData.insertedExecutionData') - }} + {{ i18n.baseText('runData.pinData.insertedExecutionData') }} +