mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
refactor(core): Bring active executions into executions controller (no-changelog) (#8371)
This commit is contained in:
@@ -14,13 +14,10 @@ import cookieParser from 'cookie-parser';
|
||||
import express from 'express';
|
||||
import { engine as expressHandlebars } from 'express-handlebars';
|
||||
import type { ServeStaticOptions } from 'serve-static';
|
||||
import type { FindManyOptions, FindOptionsWhere } from 'typeorm';
|
||||
import { Not, In } from 'typeorm';
|
||||
|
||||
import { type Class, InstanceSettings } from 'n8n-core';
|
||||
|
||||
import type { ExecutionStatus, IExecutionsSummary, IN8nUISettings } from 'n8n-workflow';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import type { IN8nUISettings } from 'n8n-workflow';
|
||||
|
||||
// @ts-ignore
|
||||
import timezones from 'google-timezones-json';
|
||||
@@ -39,7 +36,6 @@ import {
|
||||
} from '@/constants';
|
||||
import { credentialsController } from '@/credentials/credentials.controller';
|
||||
import type { CurlHelper } from '@/requests';
|
||||
import type { ExecutionRequest } from '@/executions/execution.request';
|
||||
import { registerController } from '@/decorators';
|
||||
import { AuthController } from '@/controllers/auth.controller';
|
||||
import { BinaryDataController } from '@/controllers/binaryData.controller';
|
||||
@@ -58,7 +54,7 @@ import { WorkflowStatisticsController } from '@/controllers/workflowStatistics.c
|
||||
import { ExternalSecretsController } from '@/ExternalSecrets/ExternalSecrets.controller.ee';
|
||||
import { ExecutionsController } from '@/executions/executions.controller';
|
||||
import { isApiEnabled, loadPublicApiVersions } from '@/PublicApi';
|
||||
import type { ICredentialsOverwrite, IDiagnosticInfo, IExecutionsStopData } from '@/Interfaces';
|
||||
import type { ICredentialsOverwrite, IDiagnosticInfo } from '@/Interfaces';
|
||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||
@@ -76,7 +72,6 @@ import { PostHogClient } from './posthog';
|
||||
import { eventBus } from './eventbus';
|
||||
import { InternalHooks } from './InternalHooks';
|
||||
import { License } from './License';
|
||||
import { getStatusUsingPreviousExecutionStatusMethod } from './executions/executionHelpers';
|
||||
import { SamlController } from './sso/saml/routes/saml.controller.ee';
|
||||
import { SamlService } from './sso/saml/saml.service.ee';
|
||||
import { VariablesController } from './environments/variables/variables.controller.ee';
|
||||
@@ -87,8 +82,6 @@ import {
|
||||
import { SourceControlService } from '@/environments/sourceControl/sourceControl.service.ee';
|
||||
import { SourceControlController } from '@/environments/sourceControl/sourceControl.controller.ee';
|
||||
|
||||
import type { ExecutionEntity } from '@db/entities/ExecutionEntity';
|
||||
import { ExecutionRepository } from '@db/repositories/execution.repository';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
|
||||
import { handleMfaDisable, isMfaFeatureEnabled } from './Mfa/helpers';
|
||||
@@ -100,9 +93,7 @@ import { InvitationController } from './controllers/invitation.controller';
|
||||
import { CollaborationService } from './collaboration/collaboration.service';
|
||||
import { RoleController } from './controllers/role.controller';
|
||||
import { BadRequestError } from './errors/response-errors/bad-request.error';
|
||||
import { NotFoundError } from './errors/response-errors/not-found.error';
|
||||
import { OrchestrationService } from '@/services/orchestration.service';
|
||||
import { WorkflowSharingService } from './workflows/workflowSharing.service';
|
||||
|
||||
const exec = promisify(callbackExec);
|
||||
|
||||
@@ -408,219 +399,6 @@ export class Server extends AbstractServer {
|
||||
}),
|
||||
);
|
||||
|
||||
// ----------------------------------------
|
||||
// Executing Workflows
|
||||
// ----------------------------------------
|
||||
|
||||
// Returns all the currently working executions
|
||||
this.app.get(
|
||||
`/${this.restEndpoint}/executions-current`,
|
||||
ResponseHelper.send(
|
||||
async (req: ExecutionRequest.GetAllCurrent): Promise<IExecutionsSummary[]> => {
|
||||
if (config.getEnv('executions.mode') === 'queue') {
|
||||
const queue = Container.get(Queue);
|
||||
const currentJobs = await queue.getJobs(['active', 'waiting']);
|
||||
|
||||
const currentlyRunningQueueIds = currentJobs.map((job) => job.data.executionId);
|
||||
|
||||
const currentlyRunningManualExecutions =
|
||||
this.activeExecutionsInstance.getActiveExecutions();
|
||||
const manualExecutionIds = currentlyRunningManualExecutions.map(
|
||||
(execution) => execution.id,
|
||||
);
|
||||
|
||||
const currentlyRunningExecutionIds =
|
||||
currentlyRunningQueueIds.concat(manualExecutionIds);
|
||||
|
||||
if (!currentlyRunningExecutionIds.length) return [];
|
||||
|
||||
const findOptions: FindManyOptions<ExecutionEntity> & {
|
||||
where: FindOptionsWhere<ExecutionEntity>;
|
||||
} = {
|
||||
select: ['id', 'workflowId', 'mode', 'retryOf', 'startedAt', 'stoppedAt', 'status'],
|
||||
order: { id: 'DESC' },
|
||||
where: {
|
||||
id: In(currentlyRunningExecutionIds),
|
||||
status: Not(In(['finished', 'stopped', 'failed', 'crashed'] as ExecutionStatus[])),
|
||||
},
|
||||
};
|
||||
|
||||
const sharedWorkflowIds = await Container.get(
|
||||
WorkflowSharingService,
|
||||
).getSharedWorkflowIds(req.user);
|
||||
|
||||
if (!sharedWorkflowIds.length) return [];
|
||||
|
||||
if (req.query.filter) {
|
||||
const { workflowId, status, finished } = jsonParse<any>(req.query.filter);
|
||||
if (workflowId && sharedWorkflowIds.includes(workflowId)) {
|
||||
Object.assign(findOptions.where, { workflowId });
|
||||
} else {
|
||||
Object.assign(findOptions.where, { workflowId: In(sharedWorkflowIds) });
|
||||
}
|
||||
if (status) {
|
||||
Object.assign(findOptions.where, { status: In(status) });
|
||||
}
|
||||
if (finished) {
|
||||
Object.assign(findOptions.where, { finished });
|
||||
}
|
||||
} else {
|
||||
Object.assign(findOptions.where, { workflowId: In(sharedWorkflowIds) });
|
||||
}
|
||||
|
||||
const executions =
|
||||
await Container.get(ExecutionRepository).findMultipleExecutions(findOptions);
|
||||
|
||||
if (!executions.length) return [];
|
||||
|
||||
return executions.map((execution) => {
|
||||
if (!execution.status) {
|
||||
execution.status = getStatusUsingPreviousExecutionStatusMethod(execution);
|
||||
}
|
||||
return {
|
||||
id: execution.id,
|
||||
workflowId: execution.workflowId,
|
||||
mode: execution.mode,
|
||||
retryOf: execution.retryOf !== null ? execution.retryOf : undefined,
|
||||
startedAt: new Date(execution.startedAt),
|
||||
status: execution.status ?? null,
|
||||
stoppedAt: execution.stoppedAt ?? null,
|
||||
} as IExecutionsSummary;
|
||||
});
|
||||
}
|
||||
|
||||
const executingWorkflows = this.activeExecutionsInstance.getActiveExecutions();
|
||||
|
||||
const returnData: IExecutionsSummary[] = [];
|
||||
|
||||
const filter = req.query.filter ? jsonParse<any>(req.query.filter) : {};
|
||||
|
||||
const sharedWorkflowIds = await Container.get(
|
||||
WorkflowSharingService,
|
||||
).getSharedWorkflowIds(req.user);
|
||||
|
||||
for (const data of executingWorkflows) {
|
||||
if (
|
||||
(filter.workflowId !== undefined && filter.workflowId !== data.workflowId) ||
|
||||
(data.workflowId !== undefined && !sharedWorkflowIds.includes(data.workflowId))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
id: data.id,
|
||||
workflowId: data.workflowId === undefined ? '' : data.workflowId,
|
||||
mode: data.mode,
|
||||
retryOf: data.retryOf,
|
||||
startedAt: new Date(data.startedAt),
|
||||
status: data.status,
|
||||
});
|
||||
}
|
||||
|
||||
returnData.sort((a, b) => Number(b.id) - Number(a.id));
|
||||
|
||||
return returnData;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Forces the execution to stop
|
||||
this.app.post(
|
||||
`/${this.restEndpoint}/executions-current/:id/stop`,
|
||||
ResponseHelper.send(async (req: ExecutionRequest.Stop): Promise<IExecutionsStopData> => {
|
||||
const { id: executionId } = req.params;
|
||||
|
||||
const sharedWorkflowIds = await Container.get(WorkflowSharingService).getSharedWorkflowIds(
|
||||
req.user,
|
||||
);
|
||||
|
||||
if (!sharedWorkflowIds.length) {
|
||||
throw new NotFoundError('Execution not found');
|
||||
}
|
||||
|
||||
const fullExecutionData = await Container.get(ExecutionRepository).findSingleExecution(
|
||||
executionId,
|
||||
{
|
||||
where: {
|
||||
workflowId: In(sharedWorkflowIds),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!fullExecutionData) {
|
||||
throw new NotFoundError('Execution not found');
|
||||
}
|
||||
|
||||
if (config.getEnv('executions.mode') === 'queue') {
|
||||
// Manual executions should still be stoppable, so
|
||||
// try notifying the `activeExecutions` to stop it.
|
||||
const result = await this.activeExecutionsInstance.stopExecution(req.params.id);
|
||||
|
||||
if (result === undefined) {
|
||||
// If active execution could not be found check if it is a waiting one
|
||||
try {
|
||||
return await this.waitTracker.stopExecution(req.params.id);
|
||||
} catch (error) {
|
||||
// Ignore, if it errors as then it is probably a currently running
|
||||
// execution
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
mode: result.mode,
|
||||
startedAt: new Date(result.startedAt),
|
||||
stoppedAt: result.stoppedAt ? new Date(result.stoppedAt) : undefined,
|
||||
finished: result.finished,
|
||||
status: result.status,
|
||||
} as IExecutionsStopData;
|
||||
}
|
||||
|
||||
const queue = Container.get(Queue);
|
||||
const currentJobs = await queue.getJobs(['active', 'waiting']);
|
||||
|
||||
const job = currentJobs.find((job) => job.data.executionId === req.params.id);
|
||||
|
||||
if (!job) {
|
||||
this.logger.debug('Could not stop job because it is no longer in queue', {
|
||||
jobId: req.params.id,
|
||||
});
|
||||
} else {
|
||||
await queue.stopJob(job);
|
||||
}
|
||||
|
||||
const returnData: IExecutionsStopData = {
|
||||
mode: fullExecutionData.mode,
|
||||
startedAt: new Date(fullExecutionData.startedAt),
|
||||
stoppedAt: fullExecutionData.stoppedAt
|
||||
? new Date(fullExecutionData.stoppedAt)
|
||||
: undefined,
|
||||
finished: fullExecutionData.finished,
|
||||
status: fullExecutionData.status,
|
||||
};
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
// Stop the execution and wait till it is done and we got the data
|
||||
const result = await this.activeExecutionsInstance.stopExecution(executionId);
|
||||
|
||||
let returnData: IExecutionsStopData;
|
||||
if (result === undefined) {
|
||||
// If active execution could not be found check if it is a waiting one
|
||||
returnData = await this.waitTracker.stopExecution(executionId);
|
||||
} else {
|
||||
returnData = {
|
||||
mode: result.mode,
|
||||
startedAt: new Date(result.startedAt),
|
||||
stoppedAt: result.stoppedAt ? new Date(result.stoppedAt) : undefined,
|
||||
finished: result.finished,
|
||||
status: result.status,
|
||||
};
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}),
|
||||
);
|
||||
|
||||
// ----------------------------------------
|
||||
// Options
|
||||
// ----------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user