2024-07-17 02:56:27 -07:00
|
|
|
import { Service } from 'typedi';
|
2024-07-19 03:55:38 -07:00
|
|
|
import { EventService } from '@/eventbus/event.service';
|
2024-07-17 02:56:27 -07:00
|
|
|
import type { Event } from '@/eventbus/event.types';
|
|
|
|
import { Telemetry } from '.';
|
|
|
|
import config from '@/config';
|
2024-07-29 02:41:28 -07:00
|
|
|
import os from 'node:os';
|
|
|
|
import { License } from '@/License';
|
|
|
|
import { GlobalConfig } from '@n8n/config';
|
|
|
|
import { N8N_VERSION } from '@/constants';
|
|
|
|
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
2024-08-02 03:05:06 -07:00
|
|
|
import type { ExecutionStatus, INodesGraphResult, ITelemetryTrackProperties } from 'n8n-workflow';
|
|
|
|
import { get as pslGet } from 'psl';
|
2024-08-01 04:44:23 -07:00
|
|
|
import { TelemetryHelpers } from 'n8n-workflow';
|
|
|
|
import { NodeTypes } from '@/NodeTypes';
|
|
|
|
import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflow.repository';
|
|
|
|
import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository';
|
2024-08-02 03:05:06 -07:00
|
|
|
import type { IExecutionTrackProperties } from '@/Interfaces';
|
|
|
|
import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions';
|
2024-07-17 02:56:27 -07:00
|
|
|
|
|
|
|
@Service()
|
|
|
|
export class TelemetryEventRelay {
|
|
|
|
constructor(
|
2024-07-19 03:55:38 -07:00
|
|
|
private readonly eventService: EventService,
|
2024-07-17 02:56:27 -07:00
|
|
|
private readonly telemetry: Telemetry,
|
2024-07-29 02:41:28 -07:00
|
|
|
private readonly license: License,
|
|
|
|
private readonly globalConfig: GlobalConfig,
|
|
|
|
private readonly workflowRepository: WorkflowRepository,
|
2024-08-01 04:44:23 -07:00
|
|
|
private readonly nodeTypes: NodeTypes,
|
|
|
|
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
|
|
|
private readonly projectRelationRepository: ProjectRelationRepository,
|
2024-07-17 02:56:27 -07:00
|
|
|
) {}
|
|
|
|
|
|
|
|
async init() {
|
|
|
|
if (!config.getEnv('diagnostics.enabled')) return;
|
|
|
|
|
|
|
|
await this.telemetry.init();
|
|
|
|
|
|
|
|
this.setupHandlers();
|
|
|
|
}
|
|
|
|
|
|
|
|
private setupHandlers() {
|
2024-07-29 02:41:28 -07:00
|
|
|
this.eventService.on('server-started', async () => await this.serverStarted());
|
|
|
|
|
2024-07-19 03:55:38 -07:00
|
|
|
this.eventService.on('team-project-updated', (event) => this.teamProjectUpdated(event));
|
|
|
|
this.eventService.on('team-project-deleted', (event) => this.teamProjectDeleted(event));
|
|
|
|
this.eventService.on('team-project-created', (event) => this.teamProjectCreated(event));
|
|
|
|
this.eventService.on('source-control-settings-updated', (event) =>
|
2024-07-18 06:00:24 -07:00
|
|
|
this.sourceControlSettingsUpdated(event),
|
|
|
|
);
|
2024-07-19 03:55:38 -07:00
|
|
|
this.eventService.on('source-control-user-started-pull-ui', (event) =>
|
2024-07-18 06:00:24 -07:00
|
|
|
this.sourceControlUserStartedPullUi(event),
|
|
|
|
);
|
2024-07-19 03:55:38 -07:00
|
|
|
this.eventService.on('source-control-user-finished-pull-ui', (event) =>
|
2024-07-18 06:00:24 -07:00
|
|
|
this.sourceControlUserFinishedPullUi(event),
|
|
|
|
);
|
2024-07-19 03:55:38 -07:00
|
|
|
this.eventService.on('source-control-user-pulled-api', (event) =>
|
2024-07-18 06:00:24 -07:00
|
|
|
this.sourceControlUserPulledApi(event),
|
|
|
|
);
|
2024-07-19 03:55:38 -07:00
|
|
|
this.eventService.on('source-control-user-started-push-ui', (event) =>
|
2024-07-18 06:00:24 -07:00
|
|
|
this.sourceControlUserStartedPushUi(event),
|
|
|
|
);
|
2024-07-19 03:55:38 -07:00
|
|
|
this.eventService.on('source-control-user-finished-push-ui', (event) =>
|
2024-07-18 06:00:24 -07:00
|
|
|
this.sourceControlUserFinishedPushUi(event),
|
|
|
|
);
|
2024-07-19 03:55:38 -07:00
|
|
|
this.eventService.on('license-renewal-attempted', (event) => {
|
2024-07-19 01:33:13 -07:00
|
|
|
this.licenseRenewalAttempted(event);
|
|
|
|
});
|
2024-07-23 07:42:25 -07:00
|
|
|
this.eventService.on('variable-created', () => this.variableCreated());
|
2024-07-19 03:55:38 -07:00
|
|
|
this.eventService.on('external-secrets-provider-settings-saved', (event) => {
|
2024-07-19 01:33:13 -07:00
|
|
|
this.externalSecretsProviderSettingsSaved(event);
|
|
|
|
});
|
2024-07-22 01:09:02 -07:00
|
|
|
this.eventService.on('public-api-invoked', (event) => {
|
|
|
|
this.publicApiInvoked(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('public-api-key-created', (event) => {
|
|
|
|
this.publicApiKeyCreated(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('public-api-key-deleted', (event) => {
|
|
|
|
this.publicApiKeyDeleted(event);
|
|
|
|
});
|
2024-07-23 01:47:22 -07:00
|
|
|
this.eventService.on('community-package-installed', (event) => {
|
|
|
|
this.communityPackageInstalled(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('community-package-updated', (event) => {
|
|
|
|
this.communityPackageUpdated(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('community-package-deleted', (event) => {
|
|
|
|
this.communityPackageDeleted(event);
|
|
|
|
});
|
2024-07-25 02:56:00 -07:00
|
|
|
|
|
|
|
this.eventService.on('credentials-created', (event) => {
|
|
|
|
this.credentialsCreated(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('credentials-shared', (event) => {
|
|
|
|
this.credentialsShared(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('credentials-updated', (event) => {
|
|
|
|
this.credentialsUpdated(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('credentials-deleted', (event) => {
|
|
|
|
this.credentialsDeleted(event);
|
|
|
|
});
|
2024-07-24 00:49:06 -07:00
|
|
|
this.eventService.on('ldap-general-sync-finished', (event) => {
|
|
|
|
this.ldapGeneralSyncFinished(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('ldap-settings-updated', (event) => {
|
|
|
|
this.ldapSettingsUpdated(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('ldap-login-sync-failed', (event) => {
|
|
|
|
this.ldapLoginSyncFailed(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('login-failed-due-to-ldap-disabled', (event) => {
|
|
|
|
this.loginFailedDueToLdapDisabled(event);
|
|
|
|
});
|
2024-08-01 04:44:23 -07:00
|
|
|
|
|
|
|
this.eventService.on('workflow-created', (event) => {
|
|
|
|
this.workflowCreated(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('workflow-deleted', (event) => {
|
|
|
|
this.workflowDeleted(event);
|
|
|
|
});
|
|
|
|
this.eventService.on('workflow-saved', async (event) => {
|
|
|
|
await this.workflowSaved(event);
|
|
|
|
});
|
2024-08-02 03:05:06 -07:00
|
|
|
this.eventService.on('workflow-post-execute', async (event) => {
|
|
|
|
await this.workflowPostExecute(event);
|
|
|
|
});
|
2024-07-17 02:56:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private teamProjectUpdated({ userId, role, members, projectId }: Event['team-project-updated']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('Project settings updated', {
|
2024-07-17 02:56:27 -07:00
|
|
|
user_id: userId,
|
|
|
|
role,
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
|
|
members: members.map(({ userId: user_id, role }) => ({ user_id, role })),
|
|
|
|
project_id: projectId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private teamProjectDeleted({
|
|
|
|
userId,
|
|
|
|
role,
|
|
|
|
projectId,
|
|
|
|
removalType,
|
|
|
|
targetProjectId,
|
|
|
|
}: Event['team-project-deleted']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User deleted project', {
|
2024-07-17 02:56:27 -07:00
|
|
|
user_id: userId,
|
|
|
|
role,
|
|
|
|
project_id: projectId,
|
|
|
|
removal_type: removalType,
|
|
|
|
target_project_id: targetProjectId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private teamProjectCreated({ userId, role }: Event['team-project-created']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User created project', {
|
2024-07-17 02:56:27 -07:00
|
|
|
user_id: userId,
|
|
|
|
role,
|
|
|
|
});
|
|
|
|
}
|
2024-07-18 06:00:24 -07:00
|
|
|
|
|
|
|
private sourceControlSettingsUpdated({
|
|
|
|
branchName,
|
|
|
|
readOnlyInstance,
|
|
|
|
repoType,
|
|
|
|
connected,
|
|
|
|
}: Event['source-control-settings-updated']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User updated source control settings', {
|
2024-07-18 06:00:24 -07:00
|
|
|
branch_name: branchName,
|
|
|
|
read_only_instance: readOnlyInstance,
|
|
|
|
repo_type: repoType,
|
|
|
|
connected,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private sourceControlUserStartedPullUi({
|
|
|
|
workflowUpdates,
|
|
|
|
workflowConflicts,
|
|
|
|
credConflicts,
|
|
|
|
}: Event['source-control-user-started-pull-ui']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User started pull via UI', {
|
2024-07-18 06:00:24 -07:00
|
|
|
workflow_updates: workflowUpdates,
|
|
|
|
workflow_conflicts: workflowConflicts,
|
|
|
|
cred_conflicts: credConflicts,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private sourceControlUserFinishedPullUi({
|
|
|
|
workflowUpdates,
|
|
|
|
}: Event['source-control-user-finished-pull-ui']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User finished pull via UI', {
|
2024-07-18 06:00:24 -07:00
|
|
|
workflow_updates: workflowUpdates,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private sourceControlUserPulledApi({
|
|
|
|
workflowUpdates,
|
|
|
|
forced,
|
|
|
|
}: Event['source-control-user-pulled-api']) {
|
|
|
|
console.log('source-control-user-pulled-api', {
|
|
|
|
workflow_updates: workflowUpdates,
|
|
|
|
forced,
|
|
|
|
});
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User pulled via API', {
|
2024-07-18 06:00:24 -07:00
|
|
|
workflow_updates: workflowUpdates,
|
|
|
|
forced,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private sourceControlUserStartedPushUi({
|
|
|
|
workflowsEligible,
|
|
|
|
workflowsEligibleWithConflicts,
|
|
|
|
credsEligible,
|
|
|
|
credsEligibleWithConflicts,
|
|
|
|
variablesEligible,
|
|
|
|
}: Event['source-control-user-started-push-ui']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User started push via UI', {
|
2024-07-18 06:00:24 -07:00
|
|
|
workflows_eligible: workflowsEligible,
|
|
|
|
workflows_eligible_with_conflicts: workflowsEligibleWithConflicts,
|
|
|
|
creds_eligible: credsEligible,
|
|
|
|
creds_eligible_with_conflicts: credsEligibleWithConflicts,
|
|
|
|
variables_eligible: variablesEligible,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private sourceControlUserFinishedPushUi({
|
|
|
|
workflowsEligible,
|
|
|
|
workflowsPushed,
|
|
|
|
credsPushed,
|
|
|
|
variablesPushed,
|
|
|
|
}: Event['source-control-user-finished-push-ui']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User finished push via UI', {
|
2024-07-18 06:00:24 -07:00
|
|
|
workflows_eligible: workflowsEligible,
|
|
|
|
workflows_pushed: workflowsPushed,
|
|
|
|
creds_pushed: credsPushed,
|
|
|
|
variables_pushed: variablesPushed,
|
|
|
|
});
|
|
|
|
}
|
2024-07-19 01:33:13 -07:00
|
|
|
|
|
|
|
private licenseRenewalAttempted({ success }: Event['license-renewal-attempted']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('Instance attempted to refresh license', {
|
2024-07-19 01:33:13 -07:00
|
|
|
success,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-07-23 07:42:25 -07:00
|
|
|
private variableCreated() {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User created variable');
|
2024-07-19 01:33:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private externalSecretsProviderSettingsSaved({
|
|
|
|
userId,
|
|
|
|
vaultType,
|
|
|
|
isValid,
|
|
|
|
isNew,
|
|
|
|
errorMessage,
|
|
|
|
}: Event['external-secrets-provider-settings-saved']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User updated external secrets settings', {
|
2024-07-19 01:33:13 -07:00
|
|
|
user_id: userId,
|
|
|
|
vault_type: vaultType,
|
|
|
|
is_valid: isValid,
|
|
|
|
is_new: isNew,
|
|
|
|
error_message: errorMessage,
|
|
|
|
});
|
|
|
|
}
|
2024-07-22 01:09:02 -07:00
|
|
|
|
|
|
|
private publicApiInvoked({ userId, path, method, apiVersion }: Event['public-api-invoked']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User invoked API', {
|
2024-07-22 01:09:02 -07:00
|
|
|
user_id: userId,
|
|
|
|
path,
|
|
|
|
method,
|
|
|
|
api_version: apiVersion,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private publicApiKeyCreated(event: Event['public-api-key-created']) {
|
|
|
|
const { user, publicApi } = event;
|
|
|
|
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('API key created', {
|
2024-07-22 01:09:02 -07:00
|
|
|
user_id: user.id,
|
|
|
|
public_api: publicApi,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private publicApiKeyDeleted(event: Event['public-api-key-deleted']) {
|
|
|
|
const { user, publicApi } = event;
|
|
|
|
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('API key deleted', {
|
2024-07-22 01:09:02 -07:00
|
|
|
user_id: user.id,
|
|
|
|
public_api: publicApi,
|
|
|
|
});
|
|
|
|
}
|
2024-07-23 01:47:22 -07:00
|
|
|
|
|
|
|
private communityPackageInstalled({
|
|
|
|
user,
|
|
|
|
inputString,
|
|
|
|
packageName,
|
|
|
|
success,
|
|
|
|
packageVersion,
|
|
|
|
packageNodeNames,
|
|
|
|
packageAuthor,
|
|
|
|
packageAuthorEmail,
|
|
|
|
failureReason,
|
|
|
|
}: Event['community-package-installed']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('cnr package install finished', {
|
2024-07-23 01:47:22 -07:00
|
|
|
user_id: user.id,
|
|
|
|
input_string: inputString,
|
|
|
|
package_name: packageName,
|
|
|
|
success,
|
|
|
|
package_version: packageVersion,
|
|
|
|
package_node_names: packageNodeNames,
|
|
|
|
package_author: packageAuthor,
|
|
|
|
package_author_email: packageAuthorEmail,
|
|
|
|
failure_reason: failureReason,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private communityPackageUpdated({
|
|
|
|
user,
|
|
|
|
packageName,
|
|
|
|
packageVersionCurrent,
|
|
|
|
packageVersionNew,
|
|
|
|
packageNodeNames,
|
|
|
|
packageAuthor,
|
|
|
|
packageAuthorEmail,
|
|
|
|
}: Event['community-package-updated']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('cnr package updated', {
|
2024-07-23 01:47:22 -07:00
|
|
|
user_id: user.id,
|
|
|
|
package_name: packageName,
|
|
|
|
package_version_current: packageVersionCurrent,
|
|
|
|
package_version_new: packageVersionNew,
|
|
|
|
package_node_names: packageNodeNames,
|
|
|
|
package_author: packageAuthor,
|
|
|
|
package_author_email: packageAuthorEmail,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private communityPackageDeleted({
|
|
|
|
user,
|
|
|
|
packageName,
|
|
|
|
packageVersion,
|
|
|
|
packageNodeNames,
|
|
|
|
packageAuthor,
|
|
|
|
packageAuthorEmail,
|
|
|
|
}: Event['community-package-deleted']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('cnr package deleted', {
|
2024-07-23 01:47:22 -07:00
|
|
|
user_id: user.id,
|
|
|
|
package_name: packageName,
|
|
|
|
package_version: packageVersion,
|
|
|
|
package_node_names: packageNodeNames,
|
|
|
|
package_author: packageAuthor,
|
|
|
|
package_author_email: packageAuthorEmail,
|
|
|
|
});
|
|
|
|
}
|
2024-07-24 00:49:06 -07:00
|
|
|
|
2024-07-25 02:56:00 -07:00
|
|
|
private credentialsCreated({
|
|
|
|
user,
|
|
|
|
credentialType,
|
|
|
|
credentialId,
|
|
|
|
projectId,
|
|
|
|
projectType,
|
|
|
|
}: Event['credentials-created']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User created credentials', {
|
2024-07-25 02:56:00 -07:00
|
|
|
user_id: user.id,
|
|
|
|
credential_type: credentialType,
|
|
|
|
credential_id: credentialId,
|
|
|
|
project_id: projectId,
|
|
|
|
project_type: projectType,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private credentialsShared({
|
|
|
|
user,
|
|
|
|
credentialType,
|
|
|
|
credentialId,
|
|
|
|
userIdSharer,
|
|
|
|
userIdsShareesAdded,
|
|
|
|
shareesRemoved,
|
|
|
|
}: Event['credentials-shared']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User updated cred sharing', {
|
2024-07-25 02:56:00 -07:00
|
|
|
user_id: user.id,
|
|
|
|
credential_type: credentialType,
|
|
|
|
credential_id: credentialId,
|
|
|
|
user_id_sharer: userIdSharer,
|
|
|
|
user_ids_sharees_added: userIdsShareesAdded,
|
|
|
|
sharees_removed: shareesRemoved,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private credentialsUpdated({ user, credentialId, credentialType }: Event['credentials-updated']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User updated credentials', {
|
2024-07-25 02:56:00 -07:00
|
|
|
user_id: user.id,
|
|
|
|
credential_type: credentialType,
|
|
|
|
credential_id: credentialId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private credentialsDeleted({ user, credentialId, credentialType }: Event['credentials-deleted']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User deleted credentials', {
|
2024-07-25 02:56:00 -07:00
|
|
|
user_id: user.id,
|
|
|
|
credential_type: credentialType,
|
|
|
|
credential_id: credentialId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-07-24 00:49:06 -07:00
|
|
|
private ldapGeneralSyncFinished({
|
|
|
|
type,
|
|
|
|
succeeded,
|
|
|
|
usersSynced,
|
|
|
|
error,
|
|
|
|
}: Event['ldap-general-sync-finished']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('Ldap general sync finished', {
|
2024-07-24 00:49:06 -07:00
|
|
|
type,
|
|
|
|
succeeded,
|
|
|
|
users_synced: usersSynced,
|
|
|
|
error,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private ldapSettingsUpdated({
|
|
|
|
userId,
|
|
|
|
loginIdAttribute,
|
|
|
|
firstNameAttribute,
|
|
|
|
lastNameAttribute,
|
|
|
|
emailAttribute,
|
|
|
|
ldapIdAttribute,
|
|
|
|
searchPageSize,
|
|
|
|
searchTimeout,
|
|
|
|
synchronizationEnabled,
|
|
|
|
synchronizationInterval,
|
|
|
|
loginLabel,
|
|
|
|
loginEnabled,
|
|
|
|
}: Event['ldap-settings-updated']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User updated Ldap settings', {
|
2024-07-24 00:49:06 -07:00
|
|
|
user_id: userId,
|
|
|
|
loginIdAttribute,
|
|
|
|
firstNameAttribute,
|
|
|
|
lastNameAttribute,
|
|
|
|
emailAttribute,
|
|
|
|
ldapIdAttribute,
|
|
|
|
searchPageSize,
|
|
|
|
searchTimeout,
|
|
|
|
synchronizationEnabled,
|
|
|
|
synchronizationInterval,
|
|
|
|
loginLabel,
|
|
|
|
loginEnabled,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private ldapLoginSyncFailed({ error }: Event['ldap-login-sync-failed']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('Ldap login sync failed', { error });
|
2024-07-24 00:49:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private loginFailedDueToLdapDisabled({ userId }: Event['login-failed-due-to-ldap-disabled']) {
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.track('User login failed since ldap disabled', { user_ud: userId });
|
2024-07-24 00:49:06 -07:00
|
|
|
}
|
2024-07-29 02:41:28 -07:00
|
|
|
|
2024-08-01 04:44:23 -07:00
|
|
|
private workflowCreated({
|
|
|
|
user,
|
|
|
|
workflow,
|
|
|
|
publicApi,
|
|
|
|
projectId,
|
|
|
|
projectType,
|
|
|
|
}: Event['workflow-created']) {
|
|
|
|
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes);
|
|
|
|
|
|
|
|
this.telemetry.track('User created workflow', {
|
|
|
|
user_id: user.id,
|
|
|
|
workflow_id: workflow.id,
|
|
|
|
node_graph_string: JSON.stringify(nodeGraph),
|
|
|
|
public_api: publicApi,
|
|
|
|
project_id: projectId,
|
|
|
|
project_type: projectType,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private workflowDeleted({ user, workflowId, publicApi }: Event['workflow-deleted']) {
|
|
|
|
this.telemetry.track('User deleted workflow', {
|
|
|
|
user_id: user.id,
|
|
|
|
workflow_id: workflowId,
|
|
|
|
public_api: publicApi,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private async workflowSaved({ user, workflow, publicApi }: Event['workflow-saved']) {
|
|
|
|
const isCloudDeployment = config.getEnv('deployment.type') === 'cloud';
|
|
|
|
|
|
|
|
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes, {
|
|
|
|
isCloudDeployment,
|
|
|
|
});
|
|
|
|
|
|
|
|
let userRole: 'owner' | 'sharee' | 'member' | undefined = undefined;
|
|
|
|
const role = await this.sharedWorkflowRepository.findSharingRole(user.id, workflow.id);
|
|
|
|
if (role) {
|
|
|
|
userRole = role === 'workflow:owner' ? 'owner' : 'sharee';
|
|
|
|
} else {
|
|
|
|
const workflowOwner = await this.sharedWorkflowRepository.getWorkflowOwningProject(
|
|
|
|
workflow.id,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (workflowOwner) {
|
|
|
|
const projectRole = await this.projectRelationRepository.findProjectRole({
|
|
|
|
userId: user.id,
|
|
|
|
projectId: workflowOwner.id,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (projectRole && projectRole !== 'project:personalOwner') {
|
|
|
|
userRole = 'member';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const notesCount = Object.keys(nodeGraph.notes).length;
|
|
|
|
const overlappingCount = Object.values(nodeGraph.notes).filter(
|
|
|
|
(note) => note.overlapping,
|
|
|
|
).length;
|
|
|
|
|
|
|
|
this.telemetry.track('User saved workflow', {
|
|
|
|
user_id: user.id,
|
|
|
|
workflow_id: workflow.id,
|
|
|
|
node_graph_string: JSON.stringify(nodeGraph),
|
|
|
|
notes_count_overlapping: overlappingCount,
|
|
|
|
notes_count_non_overlapping: notesCount - overlappingCount,
|
|
|
|
version_cli: N8N_VERSION,
|
|
|
|
num_tags: workflow.tags?.length ?? 0,
|
|
|
|
public_api: publicApi,
|
|
|
|
sharing_role: userRole,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-07-29 02:41:28 -07:00
|
|
|
private async serverStarted() {
|
|
|
|
const cpus = os.cpus();
|
|
|
|
const binaryDataConfig = config.getEnv('binaryDataManager');
|
|
|
|
|
|
|
|
const isS3Selected = config.getEnv('binaryDataManager.mode') === 's3';
|
|
|
|
const isS3Available = config.getEnv('binaryDataManager.availableModes').includes('s3');
|
|
|
|
const isS3Licensed = this.license.isBinaryDataS3Licensed();
|
|
|
|
const authenticationMethod = config.getEnv('userManagement.authenticationMethod');
|
|
|
|
|
|
|
|
const info = {
|
|
|
|
version_cli: N8N_VERSION,
|
|
|
|
db_type: this.globalConfig.database.type,
|
|
|
|
n8n_version_notifications_enabled: this.globalConfig.versionNotifications.enabled,
|
2024-07-31 08:45:11 -07:00
|
|
|
n8n_disable_production_main_process:
|
|
|
|
this.globalConfig.endpoints.disableProductionWebhooksOnMainProcess,
|
2024-07-29 02:41:28 -07:00
|
|
|
system_info: {
|
|
|
|
os: {
|
|
|
|
type: os.type(),
|
|
|
|
version: os.version(),
|
|
|
|
},
|
|
|
|
memory: os.totalmem() / 1024,
|
|
|
|
cpus: {
|
|
|
|
count: cpus.length,
|
|
|
|
model: cpus[0].model,
|
|
|
|
speed: cpus[0].speed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
execution_variables: {
|
|
|
|
executions_mode: config.getEnv('executions.mode'),
|
|
|
|
executions_timeout: config.getEnv('executions.timeout'),
|
|
|
|
executions_timeout_max: config.getEnv('executions.maxTimeout'),
|
|
|
|
executions_data_save_on_error: config.getEnv('executions.saveDataOnError'),
|
|
|
|
executions_data_save_on_success: config.getEnv('executions.saveDataOnSuccess'),
|
|
|
|
executions_data_save_on_progress: config.getEnv('executions.saveExecutionProgress'),
|
|
|
|
executions_data_save_manual_executions: config.getEnv(
|
|
|
|
'executions.saveDataManualExecutions',
|
|
|
|
),
|
|
|
|
executions_data_prune: config.getEnv('executions.pruneData'),
|
|
|
|
executions_data_max_age: config.getEnv('executions.pruneDataMaxAge'),
|
|
|
|
},
|
|
|
|
n8n_deployment_type: config.getEnv('deployment.type'),
|
|
|
|
n8n_binary_data_mode: binaryDataConfig.mode,
|
|
|
|
smtp_set_up: this.globalConfig.userManagement.emails.mode === 'smtp',
|
|
|
|
ldap_allowed: authenticationMethod === 'ldap',
|
|
|
|
saml_enabled: authenticationMethod === 'saml',
|
|
|
|
license_plan_name: this.license.getPlanName(),
|
|
|
|
license_tenant_id: config.getEnv('license.tenantId'),
|
|
|
|
binary_data_s3: isS3Available && isS3Selected && isS3Licensed,
|
|
|
|
multi_main_setup_enabled: config.getEnv('multiMainSetup.enabled'),
|
|
|
|
};
|
|
|
|
|
|
|
|
const firstWorkflow = await this.workflowRepository.findOne({
|
|
|
|
select: ['createdAt'],
|
|
|
|
order: { createdAt: 'ASC' },
|
|
|
|
where: {},
|
|
|
|
});
|
|
|
|
|
2024-07-30 04:49:41 -07:00
|
|
|
this.telemetry.identify(info);
|
|
|
|
this.telemetry.track('Instance started', {
|
|
|
|
...info,
|
|
|
|
earliest_workflow_created: firstWorkflow?.createdAt,
|
|
|
|
});
|
2024-07-29 02:41:28 -07:00
|
|
|
}
|
2024-08-02 03:05:06 -07:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
2024-07-17 02:56:27 -07:00
|
|
|
}
|