diff --git a/packages/cli/src/events/relays/telemetry.event-relay.ts b/packages/cli/src/events/relays/telemetry.event-relay.ts index 8b0fa56a91..b59c23c55c 100644 --- a/packages/cli/src/events/relays/telemetry.event-relay.ts +++ b/packages/cli/src/events/relays/telemetry.event-relay.ts @@ -729,6 +729,7 @@ export class TelemetryEventRelay extends EventRelay { sharing_role: userRole, credential_type: null, is_managed: false, + eval_rows_left: null, ...TelemetryHelpers.resolveAIMetrics(workflow.nodes, this.nodeTypes), }; @@ -739,6 +740,15 @@ export class TelemetryEventRelay extends EventRelay { manualExecEventProperties.node_graph_string = JSON.stringify(nodeGraphResult.nodeGraph); } + nodeGraphResult?.evaluationTriggerNodeNames?.forEach((name: string) => { + const rowsLeft = + runData.data.resultData.runData[name]?.[0]?.data?.main?.[0]?.[0]?.json?._rowsLeft; + + if (typeof rowsLeft === 'number') { + manualExecEventProperties.eval_rows_left = rowsLeft; + } + }); + if (runData.data.startData?.destinationNode) { const credentialsData = TelemetryHelpers.extractLastExecutedNodeCredentialData(runData); if (credentialsData) { diff --git a/packages/workflow/src/Constants.ts b/packages/workflow/src/Constants.ts index 8c9e5f5f90..8b6693dc1e 100644 --- a/packages/workflow/src/Constants.ts +++ b/packages/workflow/src/Constants.ts @@ -28,6 +28,7 @@ export const HTTP_REQUEST_NODE_TYPE = 'n8n-nodes-base.httpRequest'; export const WEBHOOK_NODE_TYPE = 'n8n-nodes-base.webhook'; export const MANUAL_TRIGGER_NODE_TYPE = 'n8n-nodes-base.manualTrigger'; export const EVALUATION_TRIGGER_NODE_TYPE = 'n8n-nodes-base.evaluationTrigger'; +export const EVALUATION_NODE_TYPE = 'n8n-nodes-base.evaluation'; export const ERROR_TRIGGER_NODE_TYPE = 'n8n-nodes-base.errorTrigger'; export const START_NODE_TYPE = 'n8n-nodes-base.start'; export const EXECUTE_WORKFLOW_NODE_TYPE = 'n8n-nodes-base.executeWorkflow'; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 7fdbc59ba7..08c146a1cc 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -2587,6 +2587,7 @@ export interface INodeGraphItem { workflow_id?: string; //@n8n/n8n-nodes-langchain.toolWorkflow and n8n-nodes-base.executeWorkflow runs?: number; items_total?: number; + metric_names?: string[]; } export interface INodeNameIndex { @@ -2597,6 +2598,7 @@ export interface INodesGraphResult { nodeGraph: INodesGraph; nameIndices: INodeNameIndex; webhookNodeNames: string[]; + evaluationTriggerNodeNames: string[]; } export interface FeatureFlags { diff --git a/packages/workflow/src/TelemetryHelpers.ts b/packages/workflow/src/TelemetryHelpers.ts index 5ec289c960..9177c42129 100644 --- a/packages/workflow/src/TelemetryHelpers.ts +++ b/packages/workflow/src/TelemetryHelpers.ts @@ -3,6 +3,8 @@ import { AI_TRANSFORM_NODE_TYPE, CHAIN_LLM_LANGCHAIN_NODE_TYPE, CHAIN_SUMMARIZATION_LANGCHAIN_NODE_TYPE, + EVALUATION_NODE_TYPE, + EVALUATION_TRIGGER_NODE_TYPE, EXECUTE_WORKFLOW_NODE_TYPE, FREE_AI_CREDITS_ERROR_TYPE, FREE_AI_CREDITS_USED_ALL_CREDITS_ERROR_CODE, @@ -178,11 +180,12 @@ export function generateNodesGraph( }; const nameIndices: INodeNameIndex = {}; const webhookNodeNames: string[] = []; + const evaluationTriggerNodeNames: string[] = []; - const notes = (workflow.nodes ?? []).filter((node) => node.type === STICKY_NODE_TYPE); + const nodes = (workflow.nodes ?? []).filter((node) => node.type === STICKY_NODE_TYPE); const otherNodes = (workflow.nodes ?? []).filter((node) => node.type !== STICKY_NODE_TYPE); - notes.forEach((stickyNote: INode, index: number) => { + nodes.forEach((stickyNote: INode, index: number) => { const stickyType = nodeTypes.getByNameAndVersion(STICKY_NODE_TYPE, stickyNote.typeVersion); if (!stickyType) { return; @@ -367,6 +370,18 @@ export function generateNodesGraph( if (node.parameters?.workflowId) { nodeItem.workflow_id = node.parameters?.workflowId as string; } + } else if (node.type === EVALUATION_TRIGGER_NODE_TYPE) { + evaluationTriggerNodeNames.push(node.name); + } else if ( + node.type === EVALUATION_NODE_TYPE && + options?.isCloudDeployment && + node.parameters?.operation === 'setMetrics' + ) { + const metrics = node.parameters?.metrics as IDataObject; + + nodeItem.metric_names = (metrics.assignments as Array<{ name: string }> | undefined)?.map( + (metric: { name: string }) => metric.name, + ); } else { try { const nodeType = nodeTypes.getByNameAndVersion(node.type, node.typeVersion); @@ -484,7 +499,7 @@ export function generateNodesGraph( }); }); - return { nodeGraph, nameIndices, webhookNodeNames }; + return { nodeGraph, nameIndices, webhookNodeNames, evaluationTriggerNodeNames }; } export function extractLastExecutedNodeCredentialData( diff --git a/packages/workflow/test/TelemetryHelpers.test.ts b/packages/workflow/test/TelemetryHelpers.test.ts index 37c7b51841..320b2ff232 100644 --- a/packages/workflow/test/TelemetryHelpers.test.ts +++ b/packages/workflow/test/TelemetryHelpers.test.ts @@ -148,6 +148,7 @@ describe('generateNodesGraph', () => { }, nameIndices: { 'When clicking "Execute Workflow"': '0', 'Google Sheets': '1' }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); @@ -175,6 +176,7 @@ describe('generateNodesGraph', () => { }, nameIndices: {}, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); @@ -190,6 +192,7 @@ describe('generateNodesGraph', () => { }, nameIndices: {}, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); @@ -256,6 +259,7 @@ describe('generateNodesGraph', () => { }, nameIndices: { 'When clicking "Execute Workflow"': '0', 'Google Sheets': '1' }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); @@ -333,6 +337,7 @@ describe('generateNodesGraph', () => { }, nameIndices: { 'When clicking "Execute Workflow"': '0', 'Google Sheets': '1' }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); @@ -388,6 +393,7 @@ describe('generateNodesGraph', () => { versionId: '70b92d94-0e9a-4b41-9976-a654df420af5', }; expect(generateNodesGraph(workflow, nodeTypes)).toEqual({ + evaluationTriggerNodeNames: [], nodeGraph: { node_types: ['n8n-nodes-base.manualTrigger', 'test.googleSheets'], node_connections: [{ start: '0', end: '1' }], @@ -451,6 +457,7 @@ describe('generateNodesGraph', () => { notes: {}, }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); @@ -474,6 +481,7 @@ describe('generateNodesGraph', () => { pinData: {}, }; expect(generateNodesGraph(workflow, nodeTypes)).toEqual({ + evaluationTriggerNodeNames: [], nodeGraph: { node_types: ['n8n-nodes-base.webhook'], node_connections: [], @@ -520,6 +528,7 @@ describe('generateNodesGraph', () => { pinData: {}, }; expect(generateNodesGraph(workflow, nodeTypes)).toEqual({ + evaluationTriggerNodeNames: [], nodeGraph: { node_types: ['n8n-nodes-base.httpRequest'], node_connections: [], @@ -574,6 +583,7 @@ describe('generateNodesGraph', () => { pinData: {}, }; expect(generateNodesGraph(workflow, nodeTypes)).toEqual({ + evaluationTriggerNodeNames: [], nodeGraph: { node_types: ['n8n-nodes-base.httpRequest'], node_connections: [], @@ -635,6 +645,7 @@ describe('generateNodesGraph', () => { }, nameIndices: { 'Merge Node V3': '0' }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }, }, { @@ -673,6 +684,7 @@ describe('generateNodesGraph', () => { }, nameIndices: { 'Merge Node V3': '0' }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }, }, { @@ -713,6 +725,7 @@ describe('generateNodesGraph', () => { }, nameIndices: { 'Merge Node V3': '0' }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }, }, ])('should return graph with merge v3 node', ({ workflow, expected, isCloudDeployment }) => { @@ -755,6 +768,7 @@ describe('generateNodesGraph', () => { }, nameIndices: { 'HTTP Request V1': '0' }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); @@ -795,6 +809,7 @@ describe('generateNodesGraph', () => { }, nameIndices: { 'HTTP Request v4 with defaults': '0' }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); @@ -900,6 +915,7 @@ describe('generateNodesGraph', () => { Model: '2', }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); @@ -1012,6 +1028,7 @@ describe('generateNodesGraph', () => { notes: {}, }, webhookNodeNames: [], + evaluationTriggerNodeNames: [], }); }); });