import { deepCopy } from 'n8n-workflow'; import type { ExecutionError, GenericValue, INodeParameters, INodeProperties, ITelemetryTrackProperties, NodeParameterValue, INode, } from 'n8n-workflow'; import { useNDVStore } from '@/stores/ndv.store'; import type { TelemetryEventData } from '@/hooks/types'; import type { INodeUi } from '@/Interface'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useRootStore } from '@/stores/n8nRoot.store'; export interface UserSavedCredentialsEventData { credential_type: string; credential_id: string; is_new: boolean; } export const getUserSavedCredentialsEventData = (meta: UserSavedCredentialsEventData) => { const rootStore = useRootStore(); const workflowsStore = useWorkflowsStore(); return { eventName: 'User saved credentials', properties: { instance_id: rootStore.instanceId, credential_type: meta.credential_type, credential_id: meta.credential_id, workflow_id: workflowsStore.workflowId, node_type: workflowsStore.activeNode?.name, is_new: meta.is_new, // is_complete: true, // is_valid: true, // error_message: '' }, }; }; export const getOpenWorkflowSettingsEventData = (): TelemetryEventData => { const workflowsStore = useWorkflowsStore(); return { eventName: 'User opened workflow settings', properties: { workflow_id: workflowsStore.workflowId, workflow_name: workflowsStore.workflowName, current_settings: deepCopy(workflowsStore.workflowSettings), }, }; }; export interface UpdatedWorkflowSettingsEventData { oldSettings: Record; } export const getUpdatedWorkflowSettingsEventData = ( meta: UpdatedWorkflowSettingsEventData, ): TelemetryEventData => { const workflowsStore = useWorkflowsStore(); return { eventName: 'User updated workflow settings', properties: { workflow_id: workflowsStore.workflowId, workflow_name: workflowsStore.workflowName, new_settings: deepCopy(workflowsStore.workflowSettings), old_settings: meta.oldSettings, }, }; }; export interface NodeTypeChangedEventData { nodeSubtitle?: string; } export const getNodeTypeChangedEventData = (meta: NodeTypeChangedEventData): TelemetryEventData => { const store = useNDVStore(); return { eventName: 'User opened node modal', properties: { node_name: store.activeNode?.name, node_subtitle: meta.nodeSubtitle, }, }; }; export interface InsertedItemFromExpEditorEventData { parameter: { displayName: string; }; value: string; selectedItem: { variable: string; }; } export const getInsertedItemFromExpEditorEventData = ( meta: InsertedItemFromExpEditorEventData, ): TelemetryEventData => { const store = useNDVStore(); return { eventName: 'User inserted item from Expression Editor variable selector', properties: { node_name: store.activeNode?.name, node_type: store.activeNode?.type.split('.')[1], parameter_name: meta.parameter.displayName, variable_expression: meta.selectedItem.variable, } as ITelemetryTrackProperties, }; }; export interface ExpressionEditorEventsData { dialogVisible: boolean; value: string; resolvedExpressionValue: string; parameter: INodeParameters; } export const getExpressionEditorEventsData = ( meta: ExpressionEditorEventsData, isValueDefault: boolean, ): TelemetryEventData => { const store = useNDVStore(); const eventData: TelemetryEventData = { eventName: '', properties: {}, }; if (!meta.dialogVisible) { eventData.eventName = 'User closed Expression Editor'; eventData.properties = { empty_expression: isValueDefault, expression_value: meta.value, expression_result: meta.resolvedExpressionValue.slice(1), }; } else { eventData.eventName = 'User opened Expression Editor'; eventData.properties = { node_name: store.activeNode?.name, node_type: store.activeNode?.type.split('.')[1], parameter_name: meta.parameter.displayName, parameter_field_type: meta.parameter.type, new_expression: isValueDefault, }; } return eventData; }; export interface AuthenticationModalEventData { parameterPath: string; oldNodeParameters: Record; parameters: INodeProperties[]; newValue: NodeParameterValue; } export const getAuthenticationModalEventData = ( meta: AuthenticationModalEventData, ): TelemetryEventData => { const store = useNDVStore(); return { eventName: 'User changed Authentication type from node modal', properties: { node_name: store.activeNode?.name, node_type: store.activeNode?.type.split('.')[1], old_mode: meta.oldNodeParameters.authentication || ( meta.parameters.find((param) => param.name === 'authentication') || { default: 'default', } ).default, new_mode: meta.newValue, }, }; }; export interface OutputModeChangedEventData { oldValue: string; newValue: string; } export const getOutputModeChangedEventData = ( meta: OutputModeChangedEventData, ): TelemetryEventData => { const store = useNDVStore(); return { eventName: 'User changed node output view mode', properties: { old_mode: meta.oldValue, new_mode: meta.newValue, node_name: store.activeNode?.name, node_type: store.activeNode?.type.split('.')[1], }, }; }; export interface ExecutionFinishedEventData { runDataExecutedStartData: | { destinationNode?: string | undefined; runNodeFilter?: string[] | undefined } | undefined; nodeName?: string; errorMessage: string; resultDataError: ExecutionError | undefined; itemsCount: number; } export const getExecutionFinishedEventData = ( meta: ExecutionFinishedEventData, ): TelemetryEventData => { const store = useWorkflowsStore(); const eventData: TelemetryEventData = { eventName: '', properties: { execution_id: store.activeExecutionId, }, }; if (meta.runDataExecutedStartData?.destinationNode) { eventData.eventName = 'Node execution finished'; eventData.properties!.node_type = store.getNodeByName(meta.nodeName || '')?.type.split('.')[1]; eventData.properties!.node_name = meta.nodeName; } else { eventData.eventName = 'Manual workflow execution finished'; eventData.properties!.workflow_id = store.workflowId; eventData.properties!.workflow_name = store.workflowName; } if (meta.errorMessage || meta.resultDataError) { eventData.properties!.status = 'failed'; eventData.properties!.error_message = meta.resultDataError?.message || ''; eventData.properties!.error_stack = meta.resultDataError?.stack || ''; eventData.properties!.error_ui_message = meta.errorMessage || ''; eventData.properties!.error_timestamp = new Date(); if (meta.resultDataError && (meta.resultDataError as unknown as { node: INodeUi })?.node) { eventData.properties!.error_node = typeof (meta.resultDataError as unknown as { node: string })?.node === 'string' ? (meta.resultDataError as unknown as { node: string })?.node : (meta.resultDataError as unknown as { node: INodeUi })?.node?.name; } else { eventData.properties!.error_node = meta.nodeName; } } else { eventData.properties!.status = 'success'; if (meta.runDataExecutedStartData?.destinationNode) { // Node execution finished eventData.properties!.items_count = meta.itemsCount || 0; } } return eventData; }; export interface NodeRemovedEventData { node: INodeUi; } export const getNodeRemovedEventData = (meta: NodeRemovedEventData): TelemetryEventData => { const workflowsStore = useWorkflowsStore(); return { eventName: 'User removed node from workflow canvas', properties: { node_name: meta.node.name, node_type: meta.node.type, node_disabled: meta.node.disabled, workflow_id: workflowsStore.workflowId, }, }; }; export const getNodeEditingFinishedEventData = ( activeNode: INode | null, ): TelemetryEventData | undefined => { switch (activeNode?.type) { case 'n8n-nodes-base.httpRequest': const domain = (activeNode.parameters.url as string).split('/')[2]; return { eventName: 'User finished httpRequest node editing', properties: { method: activeNode.parameters.method, domain, }, }; case 'n8n-nodes-base.function': return { eventName: 'User finished function node editing', properties: { node_name: activeNode.name, code: activeNode.parameters.functionCode, }, }; case 'n8n-nodes-base.functionItem': return { eventName: 'User finished functionItem node editing', properties: { node_name: activeNode.name, code: activeNode.parameters.functionCode, }, }; default: return; } }; export interface ExecutionStartedEventData { nodeName?: string; source?: string; } export const getExecutionStartedEventData = ( meta: ExecutionStartedEventData, ): TelemetryEventData => { const store = useWorkflowsStore(); const eventData: TelemetryEventData = { eventName: '', properties: { execution_id: store.activeExecutionId, }, }; // node execution if (meta.nodeName) { eventData.eventName = 'User started node execution'; eventData.properties!.source = 'unknown'; eventData.properties!.node_type = store.getNodeByName(meta.nodeName)?.type.split('.')[1]; eventData.properties!.node_name = meta.nodeName; if (meta.source === 'RunData.ExecuteNodeButton') { eventData.properties!.source = 'node_modal'; } else if (meta.source === 'Node.executeNode') { eventData.properties!.source = 'workflow_canvas'; } } else { // workflow execution eventData.eventName = 'User started manual workflow execution'; eventData.properties!.workflow_id = store.workflowId; eventData.properties!.workflow_name = store.workflowName; } return eventData; };