mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-01 22:52:54 -08:00
fix(core): Flush instance stopped event immediately (#10238)
This commit is contained in:
parent
a2d08846d0
commit
d6770b5fca
|
@ -43,13 +43,11 @@ export class InternalHooks {
|
|||
private readonly projectRelationRepository: ProjectRelationRepository,
|
||||
private readonly _eventBus: MessageEventBus, // needed until we decouple telemetry
|
||||
) {
|
||||
workflowStatisticsService.on(
|
||||
'telemetry.onFirstProductionWorkflowSuccess',
|
||||
async (metrics) => await this.onFirstProductionWorkflowSuccess(metrics),
|
||||
workflowStatisticsService.on('telemetry.onFirstProductionWorkflowSuccess', (metrics) =>
|
||||
this.onFirstProductionWorkflowSuccess(metrics),
|
||||
);
|
||||
workflowStatisticsService.on(
|
||||
'telemetry.onFirstWorkflowDataLoad',
|
||||
async (metrics) => await this.onFirstWorkflowDataLoad(metrics),
|
||||
workflowStatisticsService.on('telemetry.onFirstWorkflowDataLoad', (metrics) =>
|
||||
this.onFirstWorkflowDataLoad(metrics),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -57,35 +55,29 @@ export class InternalHooks {
|
|||
await this.telemetry.init();
|
||||
}
|
||||
|
||||
async onFrontendSettingsAPI(pushRef?: string): Promise<void> {
|
||||
return await this.telemetry.track('Session started', { session_id: pushRef });
|
||||
onFrontendSettingsAPI(pushRef?: string): void {
|
||||
this.telemetry.track('Session started', { session_id: pushRef });
|
||||
}
|
||||
|
||||
async onPersonalizationSurveySubmitted(
|
||||
userId: string,
|
||||
answers: Record<string, string>,
|
||||
): Promise<void> {
|
||||
onPersonalizationSurveySubmitted(userId: string, answers: Record<string, string>): void {
|
||||
const camelCaseKeys = Object.keys(answers);
|
||||
const personalizationSurveyData = { user_id: userId } as Record<string, string | string[]>;
|
||||
camelCaseKeys.forEach((camelCaseKey) => {
|
||||
personalizationSurveyData[snakeCase(camelCaseKey)] = answers[camelCaseKey];
|
||||
});
|
||||
|
||||
return await this.telemetry.track(
|
||||
'User responded to personalization questions',
|
||||
personalizationSurveyData,
|
||||
);
|
||||
this.telemetry.track('User responded to personalization questions', personalizationSurveyData);
|
||||
}
|
||||
|
||||
async onWorkflowCreated(
|
||||
onWorkflowCreated(
|
||||
user: User,
|
||||
workflow: IWorkflowBase,
|
||||
project: Project,
|
||||
publicApi: boolean,
|
||||
): Promise<void> {
|
||||
): void {
|
||||
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes);
|
||||
|
||||
void this.telemetry.track('User created workflow', {
|
||||
this.telemetry.track('User created workflow', {
|
||||
user_id: user.id,
|
||||
workflow_id: workflow.id,
|
||||
node_graph_string: JSON.stringify(nodeGraph),
|
||||
|
@ -95,8 +87,8 @@ export class InternalHooks {
|
|||
});
|
||||
}
|
||||
|
||||
async onWorkflowDeleted(user: User, workflowId: string, publicApi: boolean): Promise<void> {
|
||||
void this.telemetry.track('User deleted workflow', {
|
||||
onWorkflowDeleted(user: User, workflowId: string, publicApi: boolean): void {
|
||||
this.telemetry.track('User deleted workflow', {
|
||||
user_id: user.id,
|
||||
workflow_id: workflowId,
|
||||
public_api: publicApi,
|
||||
|
@ -136,7 +128,7 @@ export class InternalHooks {
|
|||
(note) => note.overlapping,
|
||||
).length;
|
||||
|
||||
void this.telemetry.track('User saved workflow', {
|
||||
this.telemetry.track('User saved workflow', {
|
||||
user_id: user.id,
|
||||
workflow_id: workflow.id,
|
||||
node_graph_string: JSON.stringify(nodeGraph),
|
||||
|
@ -155,7 +147,7 @@ export class InternalHooks {
|
|||
workflow: IWorkflowBase,
|
||||
runData?: IRun,
|
||||
userId?: string,
|
||||
): Promise<void> {
|
||||
) {
|
||||
if (!workflow.id) {
|
||||
return;
|
||||
}
|
||||
|
@ -165,8 +157,6 @@ export class InternalHooks {
|
|||
return;
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
|
||||
const telemetryProperties: IExecutionTrackProperties = {
|
||||
workflow_id: workflow.id,
|
||||
is_manual: false,
|
||||
|
@ -270,7 +260,7 @@ export class InternalHooks {
|
|||
node_id: nodeGraphResult.nameIndices[runData.data.startData?.destinationNode],
|
||||
};
|
||||
|
||||
promises.push(this.telemetry.track('Manual node exec finished', telemetryPayload));
|
||||
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]
|
||||
|
@ -282,56 +272,52 @@ export class InternalHooks {
|
|||
}
|
||||
});
|
||||
|
||||
promises.push(
|
||||
this.telemetry.track('Manual workflow exec finished', manualExecEventProperties),
|
||||
);
|
||||
this.telemetry.track('Manual workflow exec finished', manualExecEventProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Promise.all([...promises, this.telemetry.trackWorkflowExecution(telemetryProperties)]);
|
||||
this.telemetry.trackWorkflowExecution(telemetryProperties);
|
||||
}
|
||||
|
||||
async onWorkflowSharingUpdate(workflowId: string, userId: string, userList: string[]) {
|
||||
onWorkflowSharingUpdate(workflowId: string, userId: string, userList: string[]) {
|
||||
const properties: ITelemetryTrackProperties = {
|
||||
workflow_id: workflowId,
|
||||
user_id_sharer: userId,
|
||||
user_id_list: userList,
|
||||
};
|
||||
|
||||
return await this.telemetry.track('User updated workflow sharing', properties);
|
||||
this.telemetry.track('User updated workflow sharing', properties);
|
||||
}
|
||||
|
||||
async onN8nStop(): Promise<void> {
|
||||
const timeoutPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 3000);
|
||||
setTimeout(resolve, 3000);
|
||||
});
|
||||
|
||||
return await Promise.race([timeoutPromise, this.telemetry.trackN8nStop()]);
|
||||
}
|
||||
|
||||
async onUserDeletion(userDeletionData: {
|
||||
onUserDeletion(userDeletionData: {
|
||||
user: User;
|
||||
telemetryData: ITelemetryUserDeletionData;
|
||||
publicApi: boolean;
|
||||
}): Promise<void> {
|
||||
void this.telemetry.track('User deleted user', {
|
||||
}) {
|
||||
this.telemetry.track('User deleted user', {
|
||||
...userDeletionData.telemetryData,
|
||||
user_id: userDeletionData.user.id,
|
||||
public_api: userDeletionData.publicApi,
|
||||
});
|
||||
}
|
||||
|
||||
async onUserInvite(userInviteData: {
|
||||
onUserInvite(userInviteData: {
|
||||
user: User;
|
||||
target_user_id: string[];
|
||||
public_api: boolean;
|
||||
email_sent: boolean;
|
||||
invitee_role: string;
|
||||
}): Promise<void> {
|
||||
void this.telemetry.track('User invited new user', {
|
||||
}) {
|
||||
this.telemetry.track('User invited new user', {
|
||||
user_id: userInviteData.user.id,
|
||||
target_user_id: userInviteData.target_user_id,
|
||||
public_api: userInviteData.public_api,
|
||||
|
@ -340,7 +326,7 @@ export class InternalHooks {
|
|||
});
|
||||
}
|
||||
|
||||
async onUserRoleChange(userRoleChangeData: {
|
||||
onUserRoleChange(userRoleChangeData: {
|
||||
user: User;
|
||||
target_user_id: string;
|
||||
public_api: boolean;
|
||||
|
@ -348,74 +334,53 @@ export class InternalHooks {
|
|||
}) {
|
||||
const { user, ...rest } = userRoleChangeData;
|
||||
|
||||
void this.telemetry.track('User changed role', { user_id: user.id, ...rest });
|
||||
this.telemetry.track('User changed role', { user_id: user.id, ...rest });
|
||||
}
|
||||
|
||||
async onUserRetrievedUser(userRetrievedData: {
|
||||
user_id: string;
|
||||
public_api: boolean;
|
||||
}): Promise<void> {
|
||||
return await this.telemetry.track('User retrieved user', userRetrievedData);
|
||||
onUserRetrievedUser(userRetrievedData: { user_id: string; public_api: boolean }) {
|
||||
this.telemetry.track('User retrieved user', userRetrievedData);
|
||||
}
|
||||
|
||||
async onUserRetrievedAllUsers(userRetrievedData: {
|
||||
user_id: string;
|
||||
public_api: boolean;
|
||||
}): Promise<void> {
|
||||
return await this.telemetry.track('User retrieved all users', userRetrievedData);
|
||||
onUserRetrievedAllUsers(userRetrievedData: { user_id: string; public_api: boolean }) {
|
||||
this.telemetry.track('User retrieved all users', userRetrievedData);
|
||||
}
|
||||
|
||||
async onUserRetrievedExecution(userRetrievedData: {
|
||||
user_id: string;
|
||||
public_api: boolean;
|
||||
}): Promise<void> {
|
||||
return await this.telemetry.track('User retrieved execution', userRetrievedData);
|
||||
onUserRetrievedExecution(userRetrievedData: { user_id: string; public_api: boolean }) {
|
||||
this.telemetry.track('User retrieved execution', userRetrievedData);
|
||||
}
|
||||
|
||||
async onUserRetrievedAllExecutions(userRetrievedData: {
|
||||
user_id: string;
|
||||
public_api: boolean;
|
||||
}): Promise<void> {
|
||||
return await this.telemetry.track('User retrieved all executions', userRetrievedData);
|
||||
onUserRetrievedAllExecutions(userRetrievedData: { user_id: string; public_api: boolean }) {
|
||||
this.telemetry.track('User retrieved all executions', userRetrievedData);
|
||||
}
|
||||
|
||||
async onUserRetrievedWorkflow(userRetrievedData: {
|
||||
user_id: string;
|
||||
public_api: boolean;
|
||||
}): Promise<void> {
|
||||
return await this.telemetry.track('User retrieved workflow', userRetrievedData);
|
||||
onUserRetrievedWorkflow(userRetrievedData: { user_id: string; public_api: boolean }) {
|
||||
this.telemetry.track('User retrieved workflow', userRetrievedData);
|
||||
}
|
||||
|
||||
async onUserRetrievedAllWorkflows(userRetrievedData: {
|
||||
user_id: string;
|
||||
public_api: boolean;
|
||||
}): Promise<void> {
|
||||
return await this.telemetry.track('User retrieved all workflows', userRetrievedData);
|
||||
onUserRetrievedAllWorkflows(userRetrievedData: { user_id: string; public_api: boolean }) {
|
||||
this.telemetry.track('User retrieved all workflows', userRetrievedData);
|
||||
}
|
||||
|
||||
async onUserUpdate(userUpdateData: { user: User; fields_changed: string[] }): Promise<void> {
|
||||
void this.telemetry.track('User changed personal settings', {
|
||||
onUserUpdate(userUpdateData: { user: User; fields_changed: string[] }) {
|
||||
this.telemetry.track('User changed personal settings', {
|
||||
user_id: userUpdateData.user.id,
|
||||
fields_changed: userUpdateData.fields_changed,
|
||||
});
|
||||
}
|
||||
|
||||
async onUserInviteEmailClick(userInviteClickData: {
|
||||
inviter: User;
|
||||
invitee: User;
|
||||
}): Promise<void> {
|
||||
void this.telemetry.track('User clicked invite link from email', {
|
||||
onUserInviteEmailClick(userInviteClickData: { inviter: User; invitee: User }) {
|
||||
this.telemetry.track('User clicked invite link from email', {
|
||||
user_id: userInviteClickData.invitee.id,
|
||||
});
|
||||
}
|
||||
|
||||
async onUserPasswordResetEmailClick(userPasswordResetData: { user: User }): Promise<void> {
|
||||
void this.telemetry.track('User clicked password reset link from email', {
|
||||
onUserPasswordResetEmailClick(userPasswordResetData: { user: User }) {
|
||||
this.telemetry.track('User clicked password reset link from email', {
|
||||
user_id: userPasswordResetData.user.id,
|
||||
});
|
||||
}
|
||||
|
||||
async onUserTransactionalEmail(userTransactionalEmailData: {
|
||||
onUserTransactionalEmail(userTransactionalEmailData: {
|
||||
user_id: string;
|
||||
message_type:
|
||||
| 'Reset password'
|
||||
|
@ -424,37 +389,34 @@ export class InternalHooks {
|
|||
| 'Workflow shared'
|
||||
| 'Credentials shared';
|
||||
public_api: boolean;
|
||||
}): Promise<void> {
|
||||
return await this.telemetry.track(
|
||||
'Instance sent transactional email to user',
|
||||
userTransactionalEmailData,
|
||||
);
|
||||
}) {
|
||||
this.telemetry.track('Instance sent transactional email to user', userTransactionalEmailData);
|
||||
}
|
||||
|
||||
async onUserPasswordResetRequestClick(userPasswordResetData: { user: User }): Promise<void> {
|
||||
void this.telemetry.track('User requested password reset while logged out', {
|
||||
onUserPasswordResetRequestClick(userPasswordResetData: { user: User }) {
|
||||
this.telemetry.track('User requested password reset while logged out', {
|
||||
user_id: userPasswordResetData.user.id,
|
||||
});
|
||||
}
|
||||
|
||||
async onInstanceOwnerSetup(instanceOwnerSetupData: { user_id: string }): Promise<void> {
|
||||
return await this.telemetry.track('Owner finished instance setup', instanceOwnerSetupData);
|
||||
onInstanceOwnerSetup(instanceOwnerSetupData: { user_id: string }) {
|
||||
this.telemetry.track('Owner finished instance setup', instanceOwnerSetupData);
|
||||
}
|
||||
|
||||
async onUserSignup(
|
||||
onUserSignup(
|
||||
user: User,
|
||||
userSignupData: {
|
||||
user_type: AuthProviderType;
|
||||
was_disabled_ldap_user: boolean;
|
||||
},
|
||||
): Promise<void> {
|
||||
void this.telemetry.track('User signed up', {
|
||||
) {
|
||||
this.telemetry.track('User signed up', {
|
||||
user_id: user.id,
|
||||
...userSignupData,
|
||||
});
|
||||
}
|
||||
|
||||
async onEmailFailed(failedEmailData: {
|
||||
onEmailFailed(failedEmailData: {
|
||||
user: User;
|
||||
message_type:
|
||||
| 'Reset password'
|
||||
|
@ -463,8 +425,8 @@ export class InternalHooks {
|
|||
| 'Workflow shared'
|
||||
| 'Credentials shared';
|
||||
public_api: boolean;
|
||||
}): Promise<void> {
|
||||
void this.telemetry.track('Instance failed to send transactional email to user', {
|
||||
}) {
|
||||
this.telemetry.track('Instance failed to send transactional email to user', {
|
||||
user_id: failedEmailData.user.id,
|
||||
});
|
||||
}
|
||||
|
@ -472,21 +434,18 @@ export class InternalHooks {
|
|||
/*
|
||||
* Execution Statistics
|
||||
*/
|
||||
async onFirstProductionWorkflowSuccess(data: {
|
||||
user_id: string;
|
||||
workflow_id: string;
|
||||
}): Promise<void> {
|
||||
return await this.telemetry.track('Workflow first prod success', data);
|
||||
onFirstProductionWorkflowSuccess(data: { user_id: string; workflow_id: string }) {
|
||||
this.telemetry.track('Workflow first prod success', data);
|
||||
}
|
||||
|
||||
async onFirstWorkflowDataLoad(data: {
|
||||
onFirstWorkflowDataLoad(data: {
|
||||
user_id: string;
|
||||
workflow_id: string;
|
||||
node_type: string;
|
||||
node_id: string;
|
||||
credential_type?: string;
|
||||
credential_id?: string;
|
||||
}): Promise<void> {
|
||||
return await this.telemetry.track('Workflow first data fetched', data);
|
||||
}) {
|
||||
this.telemetry.track('Workflow first data fetched', data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ export = {
|
|||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
void Container.get(InternalHooks).onUserRetrievedExecution({
|
||||
Container.get(InternalHooks).onUserRetrievedExecution({
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
});
|
||||
|
@ -129,7 +129,7 @@ export = {
|
|||
const count =
|
||||
await Container.get(ExecutionRepository).getExecutionsCountForPublicApi(filters);
|
||||
|
||||
void Container.get(InternalHooks).onUserRetrievedAllExecutions({
|
||||
Container.get(InternalHooks).onUserRetrievedAllExecutions({
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@ export = {
|
|||
public_api: true,
|
||||
};
|
||||
|
||||
void Container.get(InternalHooks).onUserRetrievedUser(telemetryData);
|
||||
Container.get(InternalHooks).onUserRetrievedUser(telemetryData);
|
||||
|
||||
return res.json(clean(user, { includeRole }));
|
||||
},
|
||||
|
@ -56,7 +56,7 @@ export = {
|
|||
public_api: true,
|
||||
};
|
||||
|
||||
void Container.get(InternalHooks).onUserRetrievedAllUsers(telemetryData);
|
||||
Container.get(InternalHooks).onUserRetrievedAllUsers(telemetryData);
|
||||
|
||||
return res.json({
|
||||
data: clean(users, { includeRole }),
|
||||
|
|
|
@ -58,7 +58,7 @@ export = {
|
|||
);
|
||||
|
||||
await Container.get(ExternalHooks).run('workflow.afterCreate', [createdWorkflow]);
|
||||
void Container.get(InternalHooks).onWorkflowCreated(req.user, createdWorkflow, project, true);
|
||||
Container.get(InternalHooks).onWorkflowCreated(req.user, createdWorkflow, project, true);
|
||||
Container.get(EventService).emit('workflow-created', {
|
||||
workflow: createdWorkflow,
|
||||
user: req.user,
|
||||
|
@ -101,7 +101,7 @@ export = {
|
|||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
void Container.get(InternalHooks).onUserRetrievedWorkflow({
|
||||
Container.get(InternalHooks).onUserRetrievedWorkflow({
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
});
|
||||
|
@ -163,7 +163,7 @@ export = {
|
|||
...(!config.getEnv('workflowTagsDisabled') && { relations: ['tags'] }),
|
||||
});
|
||||
|
||||
void Container.get(InternalHooks).onUserRetrievedAllWorkflows({
|
||||
Container.get(InternalHooks).onUserRetrievedAllWorkflows({
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
});
|
||||
|
|
|
@ -112,7 +112,7 @@ export class UserManagementMailer {
|
|||
|
||||
this.logger.info('Sent workflow shared email successfully', { sharerId: sharer.id });
|
||||
|
||||
void Container.get(InternalHooks).onUserTransactionalEmail({
|
||||
Container.get(InternalHooks).onUserTransactionalEmail({
|
||||
user_id: sharer.id,
|
||||
message_type: 'Workflow shared',
|
||||
public_api: false,
|
||||
|
@ -120,7 +120,7 @@ export class UserManagementMailer {
|
|||
|
||||
return result;
|
||||
} catch (e) {
|
||||
void Container.get(InternalHooks).onEmailFailed({
|
||||
Container.get(InternalHooks).onEmailFailed({
|
||||
user: sharer,
|
||||
message_type: 'Workflow shared',
|
||||
public_api: false,
|
||||
|
@ -171,7 +171,7 @@ export class UserManagementMailer {
|
|||
|
||||
this.logger.info('Sent credentials shared email successfully', { sharerId: sharer.id });
|
||||
|
||||
void Container.get(InternalHooks).onUserTransactionalEmail({
|
||||
Container.get(InternalHooks).onUserTransactionalEmail({
|
||||
user_id: sharer.id,
|
||||
message_type: 'Credentials shared',
|
||||
public_api: false,
|
||||
|
@ -179,7 +179,7 @@ export class UserManagementMailer {
|
|||
|
||||
return result;
|
||||
} catch (e) {
|
||||
void Container.get(InternalHooks).onEmailFailed({
|
||||
Container.get(InternalHooks).onEmailFailed({
|
||||
user: sharer,
|
||||
message_type: 'Credentials shared',
|
||||
public_api: false,
|
||||
|
|
|
@ -51,7 +51,7 @@ export const handleLdapLogin = async (
|
|||
await updateLdapUserOnLocalDb(identity, ldapAttributesValues);
|
||||
} else {
|
||||
const user = await createLdapUserOnLocalDb(ldapAttributesValues, ldapId);
|
||||
void Container.get(InternalHooks).onUserSignup(user, {
|
||||
Container.get(InternalHooks).onUserSignup(user, {
|
||||
user_type: 'ldap',
|
||||
was_disabled_ldap_user: false,
|
||||
});
|
||||
|
|
|
@ -55,7 +55,7 @@ export class ConcurrencyControlService {
|
|||
|
||||
this.productionQueue.on('concurrency-check', ({ capacity }) => {
|
||||
if (this.shouldReport(capacity)) {
|
||||
void this.telemetry.track('User hit concurrency limit', {
|
||||
this.telemetry.track('User hit concurrency limit', {
|
||||
threshold: CLOUD_TEMP_PRODUCTION_LIMIT - capacity,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -179,7 +179,7 @@ export class AuthController {
|
|||
throw new BadRequestError('Invalid request');
|
||||
}
|
||||
|
||||
void this.internalHooks.onUserInviteEmailClick({ inviter, invitee });
|
||||
this.internalHooks.onUserInviteEmailClick({ inviter, invitee });
|
||||
this.eventService.emit('user-invite-email-click', { inviter, invitee });
|
||||
|
||||
const { firstName, lastName } = inviter;
|
||||
|
|
|
@ -9,7 +9,6 @@ import { Delete, Get, Middleware, Patch, Post, RestController, GlobalScope } fro
|
|||
import { NodeRequest } from '@/requests';
|
||||
import type { InstalledPackages } from '@db/entities/InstalledPackages';
|
||||
import type { CommunityPackages } from '@/Interfaces';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { Push } from '@/push';
|
||||
import { CommunityPackagesService } from '@/services/communityPackages.service';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
|
@ -37,7 +36,6 @@ export function isNpmError(error: unknown): error is { code: number; stdout: str
|
|||
export class CommunityPackagesController {
|
||||
constructor(
|
||||
private readonly push: Push,
|
||||
private readonly internalHooks: InternalHooks,
|
||||
private readonly communityPackagesService: CommunityPackagesService,
|
||||
private readonly eventService: EventService,
|
||||
) {}
|
||||
|
|
|
@ -168,7 +168,7 @@ export class InvitationController {
|
|||
|
||||
this.authService.issueCookie(res, updatedUser, req.browserId);
|
||||
|
||||
void this.internalHooks.onUserSignup(updatedUser, {
|
||||
this.internalHooks.onUserSignup(updatedUser, {
|
||||
user_type: 'email',
|
||||
was_disabled_ldap_user: false,
|
||||
});
|
||||
|
|
|
@ -101,7 +101,7 @@ export class MeController {
|
|||
this.authService.issueCookie(res, user, req.browserId);
|
||||
|
||||
const fieldsChanged = Object.keys(payload);
|
||||
void this.internalHooks.onUserUpdate({ user, fields_changed: fieldsChanged });
|
||||
this.internalHooks.onUserUpdate({ user, fields_changed: fieldsChanged });
|
||||
this.eventService.emit('user-updated', { user, fieldsChanged });
|
||||
|
||||
const publicUser = await this.userService.toPublic(user);
|
||||
|
@ -151,7 +151,7 @@ export class MeController {
|
|||
|
||||
this.authService.issueCookie(res, updatedUser, req.browserId);
|
||||
|
||||
void this.internalHooks.onUserUpdate({ user: updatedUser, fields_changed: ['password'] });
|
||||
this.internalHooks.onUserUpdate({ user: updatedUser, fields_changed: ['password'] });
|
||||
this.eventService.emit('user-updated', { user: updatedUser, fieldsChanged: ['password'] });
|
||||
|
||||
await this.externalHooks.run('user.password.update', [updatedUser.email, updatedUser.password]);
|
||||
|
@ -186,7 +186,7 @@ export class MeController {
|
|||
|
||||
this.logger.info('User survey updated successfully', { userId: req.user.id });
|
||||
|
||||
void this.internalHooks.onPersonalizationSurveySubmitted(req.user.id, personalizationAnswers);
|
||||
this.internalHooks.onPersonalizationSurveySubmitted(req.user.id, personalizationAnswers);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ export class OwnerController {
|
|||
|
||||
this.authService.issueCookie(res, owner, req.browserId);
|
||||
|
||||
void this.internalHooks.onInstanceOwnerSetup({ user_id: owner.id });
|
||||
this.internalHooks.onInstanceOwnerSetup({ user_id: owner.id });
|
||||
|
||||
return await this.userService.toPublic(owner, { posthog: this.postHog, withScopes: true });
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ export class PasswordResetController {
|
|||
domain: this.urlService.getInstanceBaseUrl(),
|
||||
});
|
||||
} catch (error) {
|
||||
void this.internalHooks.onEmailFailed({
|
||||
this.internalHooks.onEmailFailed({
|
||||
user,
|
||||
message_type: 'Reset password',
|
||||
public_api: false,
|
||||
|
@ -132,13 +132,13 @@ export class PasswordResetController {
|
|||
}
|
||||
|
||||
this.logger.info('Sent password reset email successfully', { userId: user.id, email });
|
||||
void this.internalHooks.onUserTransactionalEmail({
|
||||
this.internalHooks.onUserTransactionalEmail({
|
||||
user_id: id,
|
||||
message_type: 'Reset password',
|
||||
public_api: false,
|
||||
});
|
||||
|
||||
void this.internalHooks.onUserPasswordResetRequestClick({ user });
|
||||
this.internalHooks.onUserPasswordResetRequestClick({ user });
|
||||
this.eventService.emit('user-password-reset-request-click', { user });
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ export class PasswordResetController {
|
|||
}
|
||||
|
||||
this.logger.info('Reset-password token resolved successfully', { userId: user.id });
|
||||
void this.internalHooks.onUserPasswordResetEmailClick({ user });
|
||||
this.internalHooks.onUserPasswordResetEmailClick({ user });
|
||||
this.eventService.emit('user-password-reset-email-click', { user });
|
||||
}
|
||||
|
||||
|
@ -215,13 +215,13 @@ export class PasswordResetController {
|
|||
|
||||
this.authService.issueCookie(res, user, req.browserId);
|
||||
|
||||
void this.internalHooks.onUserUpdate({ user, fields_changed: ['password'] });
|
||||
this.internalHooks.onUserUpdate({ user, fields_changed: ['password'] });
|
||||
this.eventService.emit('user-updated', { user, fieldsChanged: ['password'] });
|
||||
|
||||
// if this user used to be an LDAP users
|
||||
const ldapIdentity = user?.authIdentities?.find((i) => i.providerType === 'ldap');
|
||||
if (ldapIdentity) {
|
||||
void this.internalHooks.onUserSignup(user, {
|
||||
this.internalHooks.onUserSignup(user, {
|
||||
user_type: 'email',
|
||||
was_disabled_ldap_user: true,
|
||||
});
|
||||
|
|
|
@ -253,7 +253,7 @@ export class UsersController {
|
|||
await trx.delete(User, { id: userToDelete.id });
|
||||
});
|
||||
|
||||
void this.internalHooks.onUserDeletion({
|
||||
this.internalHooks.onUserDeletion({
|
||||
user: req.user,
|
||||
telemetryData,
|
||||
publicApi: false,
|
||||
|
@ -294,7 +294,7 @@ export class UsersController {
|
|||
|
||||
await this.userService.update(targetUser.id, { role: payload.newRoleName });
|
||||
|
||||
void this.internalHooks.onUserRoleChange({
|
||||
this.internalHooks.onUserRoleChange({
|
||||
user: req.user,
|
||||
target_user_id: targetUser.id,
|
||||
target_user_new_role: ['global', payload.newRoleName].join(' '),
|
||||
|
|
|
@ -244,7 +244,7 @@ export class FrontendService {
|
|||
}
|
||||
|
||||
getSettings(pushRef?: string): IN8nUISettings {
|
||||
void this.internalHooks.onFrontendSettingsAPI(pushRef);
|
||||
this.internalHooks.onFrontendSettingsAPI(pushRef);
|
||||
|
||||
const restEndpoint = config.getEnv('endpoints.rest');
|
||||
|
||||
|
|
|
@ -144,14 +144,14 @@ export class UserService {
|
|||
if (result.emailSent) {
|
||||
invitedUser.user.emailSent = true;
|
||||
delete invitedUser.user?.inviteAcceptUrl;
|
||||
void Container.get(InternalHooks).onUserTransactionalEmail({
|
||||
Container.get(InternalHooks).onUserTransactionalEmail({
|
||||
user_id: id,
|
||||
message_type: 'New user invite',
|
||||
public_api: false,
|
||||
});
|
||||
}
|
||||
|
||||
void Container.get(InternalHooks).onUserInvite({
|
||||
Container.get(InternalHooks).onUserInvite({
|
||||
user: owner,
|
||||
target_user_id: Object.values(toInviteUsers),
|
||||
public_api: false,
|
||||
|
@ -164,7 +164,7 @@ export class UserService {
|
|||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
void Container.get(InternalHooks).onEmailFailed({
|
||||
Container.get(InternalHooks).onEmailFailed({
|
||||
user: owner,
|
||||
message_type: 'New user invite',
|
||||
public_api: false,
|
||||
|
|
|
@ -88,30 +88,28 @@ export class Telemetry {
|
|||
); // every 6 hours
|
||||
}
|
||||
|
||||
private async pulse(): Promise<unknown> {
|
||||
private async pulse() {
|
||||
if (!this.rudderStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allPromises = Object.keys(this.executionCountsBuffer)
|
||||
.filter((workflowId) => {
|
||||
const data = this.executionCountsBuffer[workflowId];
|
||||
const sum =
|
||||
(data.manual_error?.count ?? 0) +
|
||||
(data.manual_success?.count ?? 0) +
|
||||
(data.prod_error?.count ?? 0) +
|
||||
(data.prod_success?.count ?? 0);
|
||||
return sum > 0;
|
||||
})
|
||||
.map(async (workflowId) => {
|
||||
const promise = this.track('Workflow execution count', {
|
||||
event_version: '2',
|
||||
workflow_id: workflowId,
|
||||
...this.executionCountsBuffer[workflowId],
|
||||
});
|
||||
const workflowIdsToReport = Object.keys(this.executionCountsBuffer).filter((workflowId) => {
|
||||
const data = this.executionCountsBuffer[workflowId];
|
||||
const sum =
|
||||
(data.manual_error?.count ?? 0) +
|
||||
(data.manual_success?.count ?? 0) +
|
||||
(data.prod_error?.count ?? 0) +
|
||||
(data.prod_success?.count ?? 0);
|
||||
return sum > 0;
|
||||
});
|
||||
|
||||
return await promise;
|
||||
for (const workflowId of workflowIdsToReport) {
|
||||
this.track('Workflow execution count', {
|
||||
event_version: '2',
|
||||
workflow_id: workflowId,
|
||||
...this.executionCountsBuffer[workflowId],
|
||||
});
|
||||
}
|
||||
|
||||
this.executionCountsBuffer = {};
|
||||
|
||||
|
@ -131,11 +129,11 @@ export class Telemetry {
|
|||
team_projects: (await Container.get(ProjectRepository).getProjectCounts()).team,
|
||||
project_role_count: await Container.get(ProjectRelationRepository).countUsersByRole(),
|
||||
};
|
||||
allPromises.push(this.track('pulse', pulsePacket));
|
||||
return await Promise.all(allPromises);
|
||||
|
||||
this.track('pulse', pulsePacket);
|
||||
}
|
||||
|
||||
async trackWorkflowExecution(properties: IExecutionTrackProperties): Promise<void> {
|
||||
trackWorkflowExecution(properties: IExecutionTrackProperties) {
|
||||
if (this.rudderStack) {
|
||||
const execTime = new Date();
|
||||
const workflowId = properties.workflow_id;
|
||||
|
@ -164,66 +162,60 @@ export class Telemetry {
|
|||
properties.is_manual &&
|
||||
properties.error_node_type?.startsWith('n8n-nodes-base')
|
||||
) {
|
||||
void this.track('Workflow execution errored', properties);
|
||||
this.track('Workflow execution errored', properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async trackN8nStop(): Promise<void> {
|
||||
clearInterval(this.pulseIntervalReference);
|
||||
await this.track('User instance stopped');
|
||||
void Promise.all([this.postHog.stop(), this.rudderStack?.flush()]);
|
||||
|
||||
this.track('User instance stopped');
|
||||
|
||||
await Promise.all([this.postHog.stop(), this.rudderStack?.flush()]);
|
||||
}
|
||||
|
||||
async identify(traits?: {
|
||||
[key: string]: string | number | boolean | object | undefined | null;
|
||||
}): Promise<void> {
|
||||
identify(traits?: { [key: string]: string | number | boolean | object | undefined | null }) {
|
||||
if (!this.rudderStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { instanceId } = this.instanceSettings;
|
||||
return await new Promise<void>((resolve) => {
|
||||
if (this.rudderStack) {
|
||||
this.rudderStack.identify(
|
||||
{
|
||||
userId: instanceId,
|
||||
traits: { ...traits, instanceId },
|
||||
},
|
||||
resolve,
|
||||
);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
|
||||
this.rudderStack.identify({
|
||||
userId: instanceId,
|
||||
traits: { ...traits, instanceId },
|
||||
});
|
||||
}
|
||||
|
||||
async track(
|
||||
track(
|
||||
eventName: string,
|
||||
properties: ITelemetryTrackProperties = {},
|
||||
{ withPostHog } = { withPostHog: false }, // whether to additionally track with PostHog
|
||||
): Promise<void> {
|
||||
) {
|
||||
if (!this.rudderStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { instanceId } = this.instanceSettings;
|
||||
return await new Promise<void>((resolve) => {
|
||||
if (this.rudderStack) {
|
||||
const { user_id } = properties;
|
||||
const updatedProperties = {
|
||||
...properties,
|
||||
instance_id: instanceId,
|
||||
version_cli: N8N_VERSION,
|
||||
};
|
||||
const { user_id } = properties;
|
||||
const updatedProperties = {
|
||||
...properties,
|
||||
instance_id: instanceId,
|
||||
version_cli: N8N_VERSION,
|
||||
};
|
||||
|
||||
const payload = {
|
||||
userId: `${instanceId}${user_id ? `#${user_id}` : ''}`,
|
||||
event: eventName,
|
||||
properties: updatedProperties,
|
||||
};
|
||||
const payload = {
|
||||
userId: `${instanceId}${user_id ? `#${user_id}` : ''}`,
|
||||
event: eventName,
|
||||
properties: updatedProperties,
|
||||
};
|
||||
|
||||
if (withPostHog) {
|
||||
this.postHog?.track(payload);
|
||||
}
|
||||
if (withPostHog) {
|
||||
this.postHog?.track(payload);
|
||||
}
|
||||
|
||||
return this.rudderStack.track(payload, resolve);
|
||||
}
|
||||
|
||||
return resolve();
|
||||
});
|
||||
return this.rudderStack.track(payload);
|
||||
}
|
||||
|
||||
// test helpers
|
||||
|
|
|
@ -104,7 +104,7 @@ export class TelemetryEventRelay {
|
|||
}
|
||||
|
||||
private teamProjectUpdated({ userId, role, members, projectId }: Event['team-project-updated']) {
|
||||
void this.telemetry.track('Project settings updated', {
|
||||
this.telemetry.track('Project settings updated', {
|
||||
user_id: userId,
|
||||
role,
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
|
@ -120,7 +120,7 @@ export class TelemetryEventRelay {
|
|||
removalType,
|
||||
targetProjectId,
|
||||
}: Event['team-project-deleted']) {
|
||||
void this.telemetry.track('User deleted project', {
|
||||
this.telemetry.track('User deleted project', {
|
||||
user_id: userId,
|
||||
role,
|
||||
project_id: projectId,
|
||||
|
@ -130,7 +130,7 @@ export class TelemetryEventRelay {
|
|||
}
|
||||
|
||||
private teamProjectCreated({ userId, role }: Event['team-project-created']) {
|
||||
void this.telemetry.track('User created project', {
|
||||
this.telemetry.track('User created project', {
|
||||
user_id: userId,
|
||||
role,
|
||||
});
|
||||
|
@ -142,7 +142,7 @@ export class TelemetryEventRelay {
|
|||
repoType,
|
||||
connected,
|
||||
}: Event['source-control-settings-updated']) {
|
||||
void this.telemetry.track('User updated source control settings', {
|
||||
this.telemetry.track('User updated source control settings', {
|
||||
branch_name: branchName,
|
||||
read_only_instance: readOnlyInstance,
|
||||
repo_type: repoType,
|
||||
|
@ -155,7 +155,7 @@ export class TelemetryEventRelay {
|
|||
workflowConflicts,
|
||||
credConflicts,
|
||||
}: Event['source-control-user-started-pull-ui']) {
|
||||
void this.telemetry.track('User started pull via UI', {
|
||||
this.telemetry.track('User started pull via UI', {
|
||||
workflow_updates: workflowUpdates,
|
||||
workflow_conflicts: workflowConflicts,
|
||||
cred_conflicts: credConflicts,
|
||||
|
@ -165,7 +165,7 @@ export class TelemetryEventRelay {
|
|||
private sourceControlUserFinishedPullUi({
|
||||
workflowUpdates,
|
||||
}: Event['source-control-user-finished-pull-ui']) {
|
||||
void this.telemetry.track('User finished pull via UI', {
|
||||
this.telemetry.track('User finished pull via UI', {
|
||||
workflow_updates: workflowUpdates,
|
||||
});
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ export class TelemetryEventRelay {
|
|||
workflow_updates: workflowUpdates,
|
||||
forced,
|
||||
});
|
||||
void this.telemetry.track('User pulled via API', {
|
||||
this.telemetry.track('User pulled via API', {
|
||||
workflow_updates: workflowUpdates,
|
||||
forced,
|
||||
});
|
||||
|
@ -191,7 +191,7 @@ export class TelemetryEventRelay {
|
|||
credsEligibleWithConflicts,
|
||||
variablesEligible,
|
||||
}: Event['source-control-user-started-push-ui']) {
|
||||
void this.telemetry.track('User started push via UI', {
|
||||
this.telemetry.track('User started push via UI', {
|
||||
workflows_eligible: workflowsEligible,
|
||||
workflows_eligible_with_conflicts: workflowsEligibleWithConflicts,
|
||||
creds_eligible: credsEligible,
|
||||
|
@ -206,7 +206,7 @@ export class TelemetryEventRelay {
|
|||
credsPushed,
|
||||
variablesPushed,
|
||||
}: Event['source-control-user-finished-push-ui']) {
|
||||
void this.telemetry.track('User finished push via UI', {
|
||||
this.telemetry.track('User finished push via UI', {
|
||||
workflows_eligible: workflowsEligible,
|
||||
workflows_pushed: workflowsPushed,
|
||||
creds_pushed: credsPushed,
|
||||
|
@ -215,13 +215,13 @@ export class TelemetryEventRelay {
|
|||
}
|
||||
|
||||
private licenseRenewalAttempted({ success }: Event['license-renewal-attempted']) {
|
||||
void this.telemetry.track('Instance attempted to refresh license', {
|
||||
this.telemetry.track('Instance attempted to refresh license', {
|
||||
success,
|
||||
});
|
||||
}
|
||||
|
||||
private variableCreated() {
|
||||
void this.telemetry.track('User created variable');
|
||||
this.telemetry.track('User created variable');
|
||||
}
|
||||
|
||||
private externalSecretsProviderSettingsSaved({
|
||||
|
@ -231,7 +231,7 @@ export class TelemetryEventRelay {
|
|||
isNew,
|
||||
errorMessage,
|
||||
}: Event['external-secrets-provider-settings-saved']) {
|
||||
void this.telemetry.track('User updated external secrets settings', {
|
||||
this.telemetry.track('User updated external secrets settings', {
|
||||
user_id: userId,
|
||||
vault_type: vaultType,
|
||||
is_valid: isValid,
|
||||
|
@ -241,7 +241,7 @@ export class TelemetryEventRelay {
|
|||
}
|
||||
|
||||
private publicApiInvoked({ userId, path, method, apiVersion }: Event['public-api-invoked']) {
|
||||
void this.telemetry.track('User invoked API', {
|
||||
this.telemetry.track('User invoked API', {
|
||||
user_id: userId,
|
||||
path,
|
||||
method,
|
||||
|
@ -252,7 +252,7 @@ export class TelemetryEventRelay {
|
|||
private publicApiKeyCreated(event: Event['public-api-key-created']) {
|
||||
const { user, publicApi } = event;
|
||||
|
||||
void this.telemetry.track('API key created', {
|
||||
this.telemetry.track('API key created', {
|
||||
user_id: user.id,
|
||||
public_api: publicApi,
|
||||
});
|
||||
|
@ -261,7 +261,7 @@ export class TelemetryEventRelay {
|
|||
private publicApiKeyDeleted(event: Event['public-api-key-deleted']) {
|
||||
const { user, publicApi } = event;
|
||||
|
||||
void this.telemetry.track('API key deleted', {
|
||||
this.telemetry.track('API key deleted', {
|
||||
user_id: user.id,
|
||||
public_api: publicApi,
|
||||
});
|
||||
|
@ -278,7 +278,7 @@ export class TelemetryEventRelay {
|
|||
packageAuthorEmail,
|
||||
failureReason,
|
||||
}: Event['community-package-installed']) {
|
||||
void this.telemetry.track('cnr package install finished', {
|
||||
this.telemetry.track('cnr package install finished', {
|
||||
user_id: user.id,
|
||||
input_string: inputString,
|
||||
package_name: packageName,
|
||||
|
@ -300,7 +300,7 @@ export class TelemetryEventRelay {
|
|||
packageAuthor,
|
||||
packageAuthorEmail,
|
||||
}: Event['community-package-updated']) {
|
||||
void this.telemetry.track('cnr package updated', {
|
||||
this.telemetry.track('cnr package updated', {
|
||||
user_id: user.id,
|
||||
package_name: packageName,
|
||||
package_version_current: packageVersionCurrent,
|
||||
|
@ -319,7 +319,7 @@ export class TelemetryEventRelay {
|
|||
packageAuthor,
|
||||
packageAuthorEmail,
|
||||
}: Event['community-package-deleted']) {
|
||||
void this.telemetry.track('cnr package deleted', {
|
||||
this.telemetry.track('cnr package deleted', {
|
||||
user_id: user.id,
|
||||
package_name: packageName,
|
||||
package_version: packageVersion,
|
||||
|
@ -336,7 +336,7 @@ export class TelemetryEventRelay {
|
|||
projectId,
|
||||
projectType,
|
||||
}: Event['credentials-created']) {
|
||||
void this.telemetry.track('User created credentials', {
|
||||
this.telemetry.track('User created credentials', {
|
||||
user_id: user.id,
|
||||
credential_type: credentialType,
|
||||
credential_id: credentialId,
|
||||
|
@ -353,7 +353,7 @@ export class TelemetryEventRelay {
|
|||
userIdsShareesAdded,
|
||||
shareesRemoved,
|
||||
}: Event['credentials-shared']) {
|
||||
void this.telemetry.track('User updated cred sharing', {
|
||||
this.telemetry.track('User updated cred sharing', {
|
||||
user_id: user.id,
|
||||
credential_type: credentialType,
|
||||
credential_id: credentialId,
|
||||
|
@ -364,7 +364,7 @@ export class TelemetryEventRelay {
|
|||
}
|
||||
|
||||
private credentialsUpdated({ user, credentialId, credentialType }: Event['credentials-updated']) {
|
||||
void this.telemetry.track('User updated credentials', {
|
||||
this.telemetry.track('User updated credentials', {
|
||||
user_id: user.id,
|
||||
credential_type: credentialType,
|
||||
credential_id: credentialId,
|
||||
|
@ -372,7 +372,7 @@ export class TelemetryEventRelay {
|
|||
}
|
||||
|
||||
private credentialsDeleted({ user, credentialId, credentialType }: Event['credentials-deleted']) {
|
||||
void this.telemetry.track('User deleted credentials', {
|
||||
this.telemetry.track('User deleted credentials', {
|
||||
user_id: user.id,
|
||||
credential_type: credentialType,
|
||||
credential_id: credentialId,
|
||||
|
@ -385,7 +385,7 @@ export class TelemetryEventRelay {
|
|||
usersSynced,
|
||||
error,
|
||||
}: Event['ldap-general-sync-finished']) {
|
||||
void this.telemetry.track('Ldap general sync finished', {
|
||||
this.telemetry.track('Ldap general sync finished', {
|
||||
type,
|
||||
succeeded,
|
||||
users_synced: usersSynced,
|
||||
|
@ -407,7 +407,7 @@ export class TelemetryEventRelay {
|
|||
loginLabel,
|
||||
loginEnabled,
|
||||
}: Event['ldap-settings-updated']) {
|
||||
void this.telemetry.track('User updated Ldap settings', {
|
||||
this.telemetry.track('User updated Ldap settings', {
|
||||
user_id: userId,
|
||||
loginIdAttribute,
|
||||
firstNameAttribute,
|
||||
|
@ -424,11 +424,11 @@ export class TelemetryEventRelay {
|
|||
}
|
||||
|
||||
private ldapLoginSyncFailed({ error }: Event['ldap-login-sync-failed']) {
|
||||
void this.telemetry.track('Ldap login sync failed', { error });
|
||||
this.telemetry.track('Ldap login sync failed', { error });
|
||||
}
|
||||
|
||||
private loginFailedDueToLdapDisabled({ userId }: Event['login-failed-due-to-ldap-disabled']) {
|
||||
void this.telemetry.track('User login failed since ldap disabled', { user_ud: userId });
|
||||
this.telemetry.track('User login failed since ldap disabled', { user_ud: userId });
|
||||
}
|
||||
|
||||
private async serverStarted() {
|
||||
|
@ -489,12 +489,10 @@ export class TelemetryEventRelay {
|
|||
where: {},
|
||||
});
|
||||
|
||||
void Promise.all([
|
||||
this.telemetry.identify(info),
|
||||
this.telemetry.track('Instance started', {
|
||||
...info,
|
||||
earliest_workflow_created: firstWorkflow?.createdAt,
|
||||
}),
|
||||
]);
|
||||
this.telemetry.identify(info);
|
||||
this.telemetry.track('Instance started', {
|
||||
...info,
|
||||
earliest_workflow_created: firstWorkflow?.createdAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -282,7 +282,7 @@ export class WorkflowService {
|
|||
await this.workflowRepository.delete(workflowId);
|
||||
await this.binaryDataService.deleteMany(idsForDeletion);
|
||||
|
||||
void Container.get(InternalHooks).onWorkflowDeleted(user, workflowId, false);
|
||||
Container.get(InternalHooks).onWorkflowDeleted(user, workflowId, false);
|
||||
this.eventService.emit('workflow-deleted', { user, workflowId });
|
||||
await this.externalHooks.run('workflow.afterDelete', [workflowId]);
|
||||
|
||||
|
|
|
@ -179,7 +179,7 @@ export class WorkflowsController {
|
|||
delete savedWorkflowWithMetaData.shared;
|
||||
|
||||
await this.externalHooks.run('workflow.afterCreate', [savedWorkflow]);
|
||||
void this.internalHooks.onWorkflowCreated(req.user, newWorkflow, project!, false);
|
||||
this.internalHooks.onWorkflowCreated(req.user, newWorkflow, project!, false);
|
||||
this.eventService.emit('workflow-created', { user: req.user, workflow: newWorkflow });
|
||||
|
||||
const scopes = await this.workflowService.getWorkflowScopes(req.user, savedWorkflow.id);
|
||||
|
@ -454,7 +454,7 @@ export class WorkflowsController {
|
|||
newShareeIds = toShare;
|
||||
});
|
||||
|
||||
void this.internalHooks.onWorkflowSharingUpdate(workflowId, req.user.id, shareWithIds);
|
||||
this.internalHooks.onWorkflowSharingUpdate(workflowId, req.user.id, shareWithIds);
|
||||
|
||||
const projectsRelations = await this.projectRelationRepository.findBy({
|
||||
projectId: In(newShareeIds),
|
||||
|
|
|
@ -14,11 +14,12 @@ describe('Telemetry', () => {
|
|||
let startPulseSpy: jest.SpyInstance;
|
||||
const spyTrack = jest.spyOn(Telemetry.prototype, 'track').mockName('track');
|
||||
|
||||
const mockRudderStack: Pick<RudderStack, 'flush' | 'identify' | 'track'> = {
|
||||
flush: (resolve) => resolve?.(),
|
||||
identify: (data, resolve) => resolve?.(),
|
||||
track: (data, resolve) => resolve?.(),
|
||||
};
|
||||
const mockRudderStack = mock<RudderStack>();
|
||||
mockRudderStack.track.mockImplementation(function (_, cb) {
|
||||
cb?.();
|
||||
|
||||
return this;
|
||||
});
|
||||
|
||||
let telemetry: Telemetry;
|
||||
const instanceId = 'Telemetry unit test';
|
||||
|
@ -26,9 +27,9 @@ describe('Telemetry', () => {
|
|||
const instanceSettings = mockInstance(InstanceSettings, { instanceId });
|
||||
|
||||
beforeAll(() => {
|
||||
startPulseSpy = jest
|
||||
.spyOn(Telemetry.prototype as any, 'startPulse')
|
||||
.mockImplementation(() => {});
|
||||
// @ts-expect-error Spying on private method
|
||||
startPulseSpy = jest.spyOn(Telemetry.prototype, 'startPulse').mockImplementation(() => {});
|
||||
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(testDateTime);
|
||||
config.set('diagnostics.enabled', true);
|
||||
|
@ -49,7 +50,8 @@ describe('Telemetry', () => {
|
|||
await postHog.init();
|
||||
|
||||
telemetry = new Telemetry(mock(), postHog, mock(), instanceSettings, mock());
|
||||
(telemetry as any).rudderStack = mockRudderStack;
|
||||
// @ts-expect-error Assigning to private property
|
||||
telemetry.rudderStack = mockRudderStack;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -79,30 +81,30 @@ describe('Telemetry', () => {
|
|||
payload.is_manual = true;
|
||||
payload.success = true;
|
||||
const execTime1 = fakeJestSystemTime('2022-01-01 12:00:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
fakeJestSystemTime('2022-01-01 12:30:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
payload.is_manual = false;
|
||||
payload.success = true;
|
||||
const execTime2 = fakeJestSystemTime('2022-01-01 13:00:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
fakeJestSystemTime('2022-01-01 12:30:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
payload.is_manual = true;
|
||||
payload.success = false;
|
||||
const execTime3 = fakeJestSystemTime('2022-01-01 14:00:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
fakeJestSystemTime('2022-01-01 12:30:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
payload.is_manual = false;
|
||||
payload.success = false;
|
||||
const execTime4 = fakeJestSystemTime('2022-01-01 15:00:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
fakeJestSystemTime('2022-01-01 12:30:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
expect(spyTrack).toHaveBeenCalledTimes(0);
|
||||
|
||||
|
@ -127,9 +129,9 @@ describe('Telemetry', () => {
|
|||
};
|
||||
|
||||
const execTime1 = fakeJestSystemTime('2022-01-01 12:00:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
fakeJestSystemTime('2022-01-01 12:30:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
let execBuffer = telemetry.getCountsBuffer();
|
||||
|
||||
|
@ -140,9 +142,9 @@ describe('Telemetry', () => {
|
|||
|
||||
payload.error_node_type = 'n8n-nodes-base.node-type';
|
||||
fakeJestSystemTime('2022-01-01 13:00:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
fakeJestSystemTime('2022-01-01 12:30:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
execBuffer = telemetry.getCountsBuffer();
|
||||
|
||||
|
@ -163,7 +165,7 @@ describe('Telemetry', () => {
|
|||
|
||||
// successful execution
|
||||
const execTime1 = fakeJestSystemTime('2022-01-01 12:00:00');
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
expect(spyTrack).toHaveBeenCalledTimes(0);
|
||||
|
||||
|
@ -179,7 +181,7 @@ describe('Telemetry', () => {
|
|||
payload.error_node_type = 'n8n-nodes-base.merge';
|
||||
payload.workflow_id = '2';
|
||||
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
expect(spyTrack).toHaveBeenCalledTimes(0);
|
||||
|
||||
|
@ -198,12 +200,12 @@ describe('Telemetry', () => {
|
|||
payload.error_node_type = 'n8n-nodes-base.merge';
|
||||
payload.workflow_id = '2';
|
||||
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
payload.error_node_type = 'n8n-nodes-base.merge';
|
||||
payload.workflow_id = '1';
|
||||
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
expect(spyTrack).toHaveBeenCalledTimes(0);
|
||||
execBuffer = telemetry.getCountsBuffer();
|
||||
|
@ -225,7 +227,7 @@ describe('Telemetry', () => {
|
|||
const execTime2 = fakeJestSystemTime('2022-01-01 12:00:00');
|
||||
payload.error_node_type = 'custom-package.custom-node';
|
||||
payload.success = false;
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
expect(spyTrack).toHaveBeenCalledTimes(0);
|
||||
|
||||
|
@ -249,7 +251,7 @@ describe('Telemetry', () => {
|
|||
payload.success = false;
|
||||
payload.error_node_type = 'n8n-nodes-base.merge';
|
||||
payload.is_manual = true;
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
expect(spyTrack).toHaveBeenCalledTimes(1);
|
||||
|
||||
|
@ -327,27 +329,27 @@ describe('Telemetry', () => {
|
|||
error_node_type: 'custom-nodes-base.node-type',
|
||||
};
|
||||
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
payload.is_manual = false;
|
||||
payload.success = true;
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
payload.is_manual = true;
|
||||
payload.success = false;
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
payload.is_manual = false;
|
||||
payload.success = false;
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
payload.workflow_id = '2';
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
await telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
telemetry.trackWorkflowExecution(payload);
|
||||
|
||||
expect(spyTrack).toHaveBeenCalledTimes(0);
|
||||
expect(pulseSpy).toBeCalledTimes(0);
|
||||
|
|
Loading…
Reference in a new issue