refactor(core): Decouple post workflow execute event from internal hooks (no-changelog) (#10280)
This commit is contained in:
@@ -8,10 +8,14 @@ import { License } from '@/License';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { N8N_VERSION } from '@/constants';
|
||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||
import type { ExecutionStatus, INodesGraphResult, ITelemetryTrackProperties } from 'n8n-workflow';
|
||||
import { get as pslGet } from 'psl';
|
||||
import { TelemetryHelpers } from 'n8n-workflow';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflow.repository';
|
||||
import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository';
|
||||
import type { IExecutionTrackProperties } from '@/Interfaces';
|
||||
import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions';
|
||||
|
||||
@Service()
|
||||
export class TelemetryEventRelay {
|
||||
@@ -118,6 +122,9 @@ export class TelemetryEventRelay {
|
||||
this.eventService.on('workflow-saved', async (event) => {
|
||||
await this.workflowSaved(event);
|
||||
});
|
||||
this.eventService.on('workflow-post-execute', async (event) => {
|
||||
await this.workflowPostExecute(event);
|
||||
});
|
||||
}
|
||||
|
||||
private teamProjectUpdated({ userId, role, members, projectId }: Event['team-project-updated']) {
|
||||
@@ -584,4 +591,138 @@ export class TelemetryEventRelay {
|
||||
earliest_workflow_created: firstWorkflow?.createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
private async workflowPostExecute({ workflow, runData, userId }: Event['workflow-post-execute']) {
|
||||
if (!workflow.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (runData?.status === 'waiting') {
|
||||
// No need to send telemetry or logs when the workflow hasn't finished yet.
|
||||
return;
|
||||
}
|
||||
|
||||
const telemetryProperties: IExecutionTrackProperties = {
|
||||
workflow_id: workflow.id,
|
||||
is_manual: false,
|
||||
version_cli: N8N_VERSION,
|
||||
success: false,
|
||||
};
|
||||
|
||||
if (userId) {
|
||||
telemetryProperties.user_id = userId;
|
||||
}
|
||||
|
||||
if (runData?.data.resultData.error?.message?.includes('canceled')) {
|
||||
runData.status = 'canceled';
|
||||
}
|
||||
|
||||
telemetryProperties.success = !!runData?.finished;
|
||||
|
||||
// const executionStatus: ExecutionStatus = runData?.status ?? 'unknown';
|
||||
const executionStatus: ExecutionStatus = runData
|
||||
? determineFinalExecutionStatus(runData)
|
||||
: 'unknown';
|
||||
|
||||
if (runData !== undefined) {
|
||||
telemetryProperties.execution_mode = runData.mode;
|
||||
telemetryProperties.is_manual = runData.mode === 'manual';
|
||||
|
||||
let nodeGraphResult: INodesGraphResult | null = null;
|
||||
|
||||
if (!telemetryProperties.success && runData?.data.resultData.error) {
|
||||
telemetryProperties.error_message = runData?.data.resultData.error.message;
|
||||
let errorNodeName =
|
||||
'node' in runData?.data.resultData.error
|
||||
? runData?.data.resultData.error.node?.name
|
||||
: undefined;
|
||||
telemetryProperties.error_node_type =
|
||||
'node' in runData?.data.resultData.error
|
||||
? runData?.data.resultData.error.node?.type
|
||||
: undefined;
|
||||
|
||||
if (runData.data.resultData.lastNodeExecuted) {
|
||||
const lastNode = TelemetryHelpers.getNodeTypeForName(
|
||||
workflow,
|
||||
runData.data.resultData.lastNodeExecuted,
|
||||
);
|
||||
|
||||
if (lastNode !== undefined) {
|
||||
telemetryProperties.error_node_type = lastNode.type;
|
||||
errorNodeName = lastNode.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (telemetryProperties.is_manual) {
|
||||
nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes);
|
||||
telemetryProperties.node_graph = nodeGraphResult.nodeGraph;
|
||||
telemetryProperties.node_graph_string = JSON.stringify(nodeGraphResult.nodeGraph);
|
||||
|
||||
if (errorNodeName) {
|
||||
telemetryProperties.error_node_id = nodeGraphResult.nameIndices[errorNodeName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (telemetryProperties.is_manual) {
|
||||
if (!nodeGraphResult) {
|
||||
nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes);
|
||||
}
|
||||
|
||||
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
||||
if (userId) {
|
||||
const role = await this.sharedWorkflowRepository.findSharingRole(userId, workflow.id);
|
||||
if (role) {
|
||||
userRole = role === 'workflow:owner' ? 'owner' : 'sharee';
|
||||
}
|
||||
}
|
||||
|
||||
const manualExecEventProperties: ITelemetryTrackProperties = {
|
||||
user_id: userId,
|
||||
workflow_id: workflow.id,
|
||||
status: executionStatus,
|
||||
executionStatus: runData?.status ?? 'unknown',
|
||||
error_message: telemetryProperties.error_message as string,
|
||||
error_node_type: telemetryProperties.error_node_type,
|
||||
node_graph_string: telemetryProperties.node_graph_string as string,
|
||||
error_node_id: telemetryProperties.error_node_id as string,
|
||||
webhook_domain: null,
|
||||
sharing_role: userRole,
|
||||
};
|
||||
|
||||
if (!manualExecEventProperties.node_graph_string) {
|
||||
nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes);
|
||||
manualExecEventProperties.node_graph_string = JSON.stringify(nodeGraphResult.nodeGraph);
|
||||
}
|
||||
|
||||
if (runData.data.startData?.destinationNode) {
|
||||
const telemetryPayload = {
|
||||
...manualExecEventProperties,
|
||||
node_type: TelemetryHelpers.getNodeTypeForName(
|
||||
workflow,
|
||||
runData.data.startData?.destinationNode,
|
||||
)?.type,
|
||||
node_id: nodeGraphResult.nameIndices[runData.data.startData?.destinationNode],
|
||||
};
|
||||
|
||||
this.telemetry.track('Manual node exec finished', telemetryPayload);
|
||||
} else {
|
||||
nodeGraphResult.webhookNodeNames.forEach((name: string) => {
|
||||
const execJson = runData.data.resultData.runData[name]?.[0]?.data?.main?.[0]?.[0]
|
||||
?.json as { headers?: { origin?: string } };
|
||||
if (execJson?.headers?.origin && execJson.headers.origin !== '') {
|
||||
manualExecEventProperties.webhook_domain = pslGet(
|
||||
execJson.headers.origin.replace(/^https?:\/\//, ''),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.telemetry.track('Manual workflow exec finished', manualExecEventProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.telemetry.trackWorkflowExecution(telemetryProperties);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user