refactor(core): Use IWorkflowBase over WorkflowEntity in most places (#13225)

This commit is contained in:
Tomi Turtiainen 2025-02-13 10:54:11 +02:00 committed by GitHub
parent 11cf1cd23a
commit f001edb2a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 190 additions and 192 deletions

View file

@ -9,6 +9,7 @@ import type {
ITaskData, ITaskData,
IWaitingForExecution, IWaitingForExecution,
IWaitingForExecutionSource, IWaitingForExecutionSource,
IWorkflowBase,
IWorkflowExecutionDataProcess, IWorkflowExecutionDataProcess,
StartNodeData, StartNodeData,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -19,7 +20,6 @@ import { ActiveExecutions } from '@/active-executions';
import config from '@/config'; import config from '@/config';
import type { ExecutionEntity } from '@/databases/entities/execution-entity'; import type { ExecutionEntity } from '@/databases/entities/execution-entity';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ExecutionNotFoundError } from '@/errors/execution-not-found-error'; import { ExecutionNotFoundError } from '@/errors/execution-not-found-error';
import { Telemetry } from '@/telemetry'; import { Telemetry } from '@/telemetry';
import { PermissionChecker } from '@/user-management/permission-checker'; import { PermissionChecker } from '@/user-management/permission-checker';
@ -53,7 +53,7 @@ beforeEach(async () => {
}); });
describe('processError', () => { describe('processError', () => {
let workflow: WorkflowEntity; let workflow: IWorkflowBase;
let execution: ExecutionEntity; let execution: ExecutionEntity;
let hooks: core.ExecutionLifecycleHooks; let hooks: core.ExecutionLifecycleHooks;

View file

@ -39,13 +39,11 @@ import {
WORKFLOW_REACTIVATE_INITIAL_TIMEOUT, WORKFLOW_REACTIVATE_INITIAL_TIMEOUT,
WORKFLOW_REACTIVATE_MAX_TIMEOUT, WORKFLOW_REACTIVATE_MAX_TIMEOUT,
} from '@/constants'; } from '@/constants';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import { OnShutdown } from '@/decorators/on-shutdown'; import { OnShutdown } from '@/decorators/on-shutdown';
import { executeErrorWorkflow } from '@/execution-lifecycle/execute-error-workflow'; import { executeErrorWorkflow } from '@/execution-lifecycle/execute-error-workflow';
import { ExecutionService } from '@/executions/execution.service'; import { ExecutionService } from '@/executions/execution.service';
import { ExternalHooks } from '@/external-hooks'; import { ExternalHooks } from '@/external-hooks';
import type { IWorkflowDb } from '@/interfaces';
import { NodeTypes } from '@/node-types'; import { NodeTypes } from '@/node-types';
import { Publisher } from '@/scaling/pubsub/publisher.service'; import { Publisher } from '@/scaling/pubsub/publisher.service';
import { ActiveWorkflowsService } from '@/services/active-workflows.service'; import { ActiveWorkflowsService } from '@/services/active-workflows.service';
@ -55,12 +53,13 @@ import { WebhookService } from '@/webhooks/webhook.service';
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data'; import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
import { WorkflowExecutionService } from '@/workflows/workflow-execution.service'; import { WorkflowExecutionService } from '@/workflows/workflow-execution.service';
import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service'; import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service';
import { formatWorkflow } from '@/workflows/workflow.formatter';
interface QueuedActivation { interface QueuedActivation {
activationMode: WorkflowActivateMode; activationMode: WorkflowActivateMode;
lastTimeout: number; lastTimeout: number;
timeout: NodeJS.Timeout; timeout: NodeJS.Timeout;
workflowData: IWorkflowDb; workflowData: IWorkflowBase;
} }
@Service() @Service()
@ -271,7 +270,7 @@ export class ActiveWorkflowManager {
* and overwrites the emit to be able to start it in subprocess * and overwrites the emit to be able to start it in subprocess
*/ */
getExecutePollFunctions( getExecutePollFunctions(
workflowData: IWorkflowDb, workflowData: IWorkflowBase,
additionalData: IWorkflowExecuteAdditionalData, additionalData: IWorkflowExecuteAdditionalData,
mode: WorkflowExecuteMode, mode: WorkflowExecuteMode,
activation: WorkflowActivateMode, activation: WorkflowActivateMode,
@ -322,7 +321,7 @@ export class ActiveWorkflowManager {
* and overwrites the emit to be able to start it in subprocess * and overwrites the emit to be able to start it in subprocess
*/ */
getExecuteTriggerFunctions( getExecuteTriggerFunctions(
workflowData: IWorkflowDb, workflowData: IWorkflowBase,
additionalData: IWorkflowExecuteAdditionalData, additionalData: IWorkflowExecuteAdditionalData,
mode: WorkflowExecuteMode, mode: WorkflowExecuteMode,
activation: WorkflowActivateMode, activation: WorkflowActivateMode,
@ -379,7 +378,7 @@ export class ActiveWorkflowManager {
); );
this.executeErrorWorkflow(activationError, workflowData, mode); this.executeErrorWorkflow(activationError, workflowData, mode);
this.addQueuedWorkflowActivation(activation, workflowData as WorkflowEntity); this.addQueuedWorkflowActivation(activation, workflowData);
}; };
return new TriggerContext(workflow, node, additionalData, mode, activation, emit, emitError); return new TriggerContext(workflow, node, additionalData, mode, activation, emit, emitError);
}; };
@ -436,7 +435,7 @@ export class ActiveWorkflowManager {
} }
private async activateWorkflow( private async activateWorkflow(
dbWorkflow: WorkflowEntity, dbWorkflow: IWorkflowBase,
activationMode: 'init' | 'leadershipChange', activationMode: 'init' | 'leadershipChange',
) { ) {
try { try {
@ -444,9 +443,9 @@ export class ActiveWorkflowManager {
shouldPublish: false, shouldPublish: false,
}); });
if (wasActivated) { if (wasActivated) {
this.logger.info(` - ${dbWorkflow.display()})`); this.logger.info(` - ${formatWorkflow(dbWorkflow)})`);
this.logger.info(' => Started'); this.logger.info(' => Started');
this.logger.debug(`Successfully started workflow ${dbWorkflow.display()}`, { this.logger.debug(`Successfully started workflow ${formatWorkflow(dbWorkflow)}`, {
workflowName: dbWorkflow.name, workflowName: dbWorkflow.name,
workflowId: dbWorkflow.id, workflowId: dbWorkflow.id,
}); });
@ -454,12 +453,12 @@ export class ActiveWorkflowManager {
} catch (error) { } catch (error) {
this.errorReporter.error(error); this.errorReporter.error(error);
this.logger.info( this.logger.info(
` => ERROR: Workflow ${dbWorkflow.display()} could not be activated on first try, keep on trying if not an auth issue`, ` => ERROR: Workflow ${formatWorkflow(dbWorkflow)} could not be activated on first try, keep on trying if not an auth issue`,
); );
this.logger.info(` ${error.message}`); this.logger.info(` ${error.message}`);
this.logger.error( this.logger.error(
`Issue on initial workflow activation try of ${dbWorkflow.display()} (startup)`, `Issue on initial workflow activation try of ${formatWorkflow(dbWorkflow)} (startup)`,
{ {
workflowName: dbWorkflow.name, workflowName: dbWorkflow.name,
workflowId: dbWorkflow.id, workflowId: dbWorkflow.id,
@ -518,7 +517,7 @@ export class ActiveWorkflowManager {
async add( async add(
workflowId: string, workflowId: string,
activationMode: WorkflowActivateMode, activationMode: WorkflowActivateMode,
existingWorkflow?: WorkflowEntity, existingWorkflow?: IWorkflowBase,
{ shouldPublish } = { shouldPublish: true }, { shouldPublish } = { shouldPublish: true },
) { ) {
if (this.instanceSettings.isMultiMain && shouldPublish) { if (this.instanceSettings.isMultiMain && shouldPublish) {
@ -549,7 +548,7 @@ export class ActiveWorkflowManager {
} }
if (shouldDisplayActivationMessage) { if (shouldDisplayActivationMessage) {
this.logger.debug(`Initializing active workflow ${dbWorkflow.display()} (startup)`, { this.logger.debug(`Initializing active workflow ${formatWorkflow(dbWorkflow)} (startup)`, {
workflowName: dbWorkflow.name, workflowName: dbWorkflow.name,
workflowId: dbWorkflow.id, workflowId: dbWorkflow.id,
}); });
@ -570,7 +569,7 @@ export class ActiveWorkflowManager {
if (!canBeActivated) { if (!canBeActivated) {
throw new WorkflowActivationError( throw new WorkflowActivationError(
`Workflow ${dbWorkflow.display()} has no node to start the workflow - at least one trigger, poller or webhook node is required`, `Workflow ${formatWorkflow(dbWorkflow)} has no node to start the workflow - at least one trigger, poller or webhook node is required`,
{ level: 'warning' }, { level: 'warning' },
); );
} }
@ -673,7 +672,7 @@ export class ActiveWorkflowManager {
*/ */
private addQueuedWorkflowActivation( private addQueuedWorkflowActivation(
activationMode: WorkflowActivateMode, activationMode: WorkflowActivateMode,
workflowData: WorkflowEntity, workflowData: IWorkflowBase,
) { ) {
const workflowId = workflowData.id; const workflowId = workflowData.id;
const workflowName = workflowData.name; const workflowName = workflowData.name;
@ -811,7 +810,7 @@ export class ActiveWorkflowManager {
* Register as active in memory a trigger- or poller-based workflow. * Register as active in memory a trigger- or poller-based workflow.
*/ */
async addTriggersAndPollers( async addTriggersAndPollers(
dbWorkflow: WorkflowEntity, dbWorkflow: IWorkflowBase,
workflow: Workflow, workflow: Workflow,
{ {
activationMode, activationMode,
@ -838,7 +837,7 @@ export class ActiveWorkflowManager {
); );
if (workflow.getTriggerNodes().length !== 0 || workflow.getPollNodes().length !== 0) { if (workflow.getTriggerNodes().length !== 0 || workflow.getPollNodes().length !== 0) {
this.logger.debug(`Adding triggers and pollers for workflow ${dbWorkflow.display()}`); this.logger.debug(`Adding triggers and pollers for workflow ${formatWorkflow(dbWorkflow)}`);
await this.activeWorkflows.add( await this.activeWorkflows.add(
workflow.id, workflow.id,
@ -850,7 +849,7 @@ export class ActiveWorkflowManager {
getPollFunctions, getPollFunctions,
); );
this.logger.debug(`Workflow ${dbWorkflow.display()} activated`, { this.logger.debug(`Workflow ${formatWorkflow(dbWorkflow)} activated`, {
workflowId: dbWorkflow.id, workflowId: dbWorkflow.id,
workflowName: dbWorkflow.name, workflowName: dbWorkflow.name,
}); });

View file

@ -4,7 +4,7 @@ import { Flags } from '@oclif/core';
import fs from 'fs'; import fs from 'fs';
import { diff } from 'json-diff'; import { diff } from 'json-diff';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import type { IRun, ITaskData, IWorkflowExecutionDataProcess } from 'n8n-workflow'; import type { IRun, ITaskData, IWorkflowBase, IWorkflowExecutionDataProcess } from 'n8n-workflow';
import { ApplicationError, jsonParse } from 'n8n-workflow'; import { ApplicationError, jsonParse } from 'n8n-workflow';
import os from 'os'; import os from 'os';
import { sep } from 'path'; import { sep } from 'path';
@ -12,7 +12,6 @@ import { sep } from 'path';
import { ActiveExecutions } from '@/active-executions'; import { ActiveExecutions } from '@/active-executions';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import type { IWorkflowDb } from '@/interfaces';
import { OwnershipService } from '@/services/ownership.service'; import { OwnershipService } from '@/services/ownership.service';
import { findCliWorkflowStart } from '@/utils'; import { findCliWorkflowStart } from '@/utils';
import { WorkflowRunner } from '@/workflow-runner'; import { WorkflowRunner } from '@/workflow-runner';
@ -275,7 +274,7 @@ export class ExecuteBatch extends BaseCommand {
query.andWhere('workflows.id not in (:...skipIds)', { skipIds }); query.andWhere('workflows.id not in (:...skipIds)', { skipIds });
} }
const allWorkflows = (await query.getMany()) as IWorkflowDb[]; const allWorkflows = (await query.getMany()) as IWorkflowBase[];
if (ExecuteBatch.debug) { if (ExecuteBatch.debug) {
process.stdout.write(`Found ${allWorkflows.length} workflows to execute.\n`); process.stdout.write(`Found ${allWorkflows.length} workflows to execute.\n`);
@ -378,7 +377,7 @@ export class ExecuteBatch extends BaseCommand {
}); });
} }
private async runTests(allWorkflows: IWorkflowDb[]): Promise<IResult> { private async runTests(allWorkflows: IWorkflowBase[]): Promise<IResult> {
const result: IResult = { const result: IResult = {
totalWorkflows: allWorkflows.length, totalWorkflows: allWorkflows.length,
slackMessage: '', slackMessage: '',
@ -401,7 +400,7 @@ export class ExecuteBatch extends BaseCommand {
const promisesArray = []; const promisesArray = [];
for (let i = 0; i < ExecuteBatch.concurrency; i++) { for (let i = 0; i < ExecuteBatch.concurrency; i++) {
const promise = new Promise(async (resolve) => { const promise = new Promise(async (resolve) => {
let workflow: IWorkflowDb | undefined; let workflow: IWorkflowBase | undefined;
while (allWorkflows.length > 0) { while (allWorkflows.length > 0) {
workflow = allWorkflows.shift(); workflow = allWorkflows.shift();
if (ExecuteBatch.cancelled) { if (ExecuteBatch.cancelled) {
@ -563,7 +562,7 @@ export class ExecuteBatch extends BaseCommand {
} }
} }
async startThread(workflowData: IWorkflowDb): Promise<IExecutionResult> { async startThread(workflowData: IWorkflowBase): Promise<IExecutionResult> {
// This will be the object returned by the promise. // This will be the object returned by the promise.
// It will be updated according to execution progress below. // It will be updated according to execution progress below.
const executionResult: IExecutionResult = { const executionResult: IExecutionResult = {

View file

@ -2,6 +2,7 @@ import { Container } from '@n8n/di';
import { Flags } from '@oclif/core'; import { Flags } from '@oclif/core';
import glob from 'fast-glob'; import glob from 'fast-glob';
import fs from 'fs'; import fs from 'fs';
import type { IWorkflowBase, WorkflowId } from 'n8n-workflow';
import { ApplicationError, jsonParse } from 'n8n-workflow'; import { ApplicationError, jsonParse } from 'n8n-workflow';
import { UM_FIX_INSTRUCTION } from '@/constants'; import { UM_FIX_INSTRUCTION } from '@/constants';
@ -102,7 +103,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
this.reportSuccess(workflows.length); this.reportSuccess(workflows.length);
} }
private async checkRelations(workflows: WorkflowEntity[], projectId?: string, userId?: string) { private async checkRelations(workflows: IWorkflowBase[], projectId?: string, userId?: string) {
// The credential is not supposed to be re-owned. // The credential is not supposed to be re-owned.
if (!userId && !projectId) { if (!userId && !projectId) {
return { return {
@ -112,11 +113,11 @@ export class ImportWorkflowsCommand extends BaseCommand {
} }
for (const workflow of workflows) { for (const workflow of workflows) {
if (!(await this.workflowExists(workflow))) { if (!(await this.workflowExists(workflow.id))) {
continue; continue;
} }
const { user, project: ownerProject } = await this.getWorkflowOwner(workflow); const { user, project: ownerProject } = await this.getWorkflowOwner(workflow.id);
if (!ownerProject) { if (!ownerProject) {
continue; continue;
@ -155,9 +156,9 @@ export class ImportWorkflowsCommand extends BaseCommand {
this.logger.info(`Successfully imported ${total} ${total === 1 ? 'workflow.' : 'workflows.'}`); this.logger.info(`Successfully imported ${total} ${total === 1 ? 'workflow.' : 'workflows.'}`);
} }
private async getWorkflowOwner(workflow: WorkflowEntity) { private async getWorkflowOwner(workflowId: WorkflowId) {
const sharing = await Container.get(SharedWorkflowRepository).findOne({ const sharing = await Container.get(SharedWorkflowRepository).findOne({
where: { workflowId: workflow.id, role: 'workflow:owner' }, where: { workflowId, role: 'workflow:owner' },
relations: { project: true }, relations: { project: true },
}); });
@ -175,8 +176,8 @@ export class ImportWorkflowsCommand extends BaseCommand {
return {}; return {};
} }
private async workflowExists(workflow: WorkflowEntity) { private async workflowExists(workflowId: WorkflowId) {
return await Container.get(WorkflowRepository).existsBy({ id: workflow.id }); return await Container.get(WorkflowRepository).existsBy({ id: workflowId });
} }
private async readWorkflows(path: string, separate: boolean): Promise<WorkflowEntity[]> { private async readWorkflows(path: string, separate: boolean): Promise<WorkflowEntity[]> {

View file

@ -104,10 +104,6 @@ export class WorkflowEntity extends WithTimestampsAndStringId implements IWorkfl
}) })
@JoinColumn({ name: 'parentFolderId' }) @JoinColumn({ name: 'parentFolderId' })
parentFolder: Folder | null; parentFolder: Folder | null;
display() {
return `"${this.name}" (ID: ${this.id})`;
}
} }
/** /**

View file

@ -3,8 +3,9 @@ import type { EntityManager } from '@n8n/typeorm';
import { DataSource, In, Repository } from '@n8n/typeorm'; import { DataSource, In, Repository } from '@n8n/typeorm';
import intersection from 'lodash/intersection'; import intersection from 'lodash/intersection';
import type { IWorkflowDb } from '@/interfaces';
import { TagEntity } from '../entities/tag-entity'; import { TagEntity } from '../entities/tag-entity';
import type { WorkflowEntity } from '../entities/workflow-entity';
@Service() @Service()
export class TagRepository extends Repository<TagEntity> { export class TagRepository extends Repository<TagEntity> {
@ -23,7 +24,7 @@ export class TagRepository extends Repository<TagEntity> {
* Set tags on workflow to import while ensuring all tags exist in the database, * Set tags on workflow to import while ensuring all tags exist in the database,
* either by matching incoming to existing tags or by creating them first. * either by matching incoming to existing tags or by creating them first.
*/ */
async setTags(tx: EntityManager, dbTags: TagEntity[], workflow: WorkflowEntity) { async setTags(tx: EntityManager, dbTags: TagEntity[], workflow: IWorkflowDb) {
if (!workflow?.tags?.length) return; if (!workflow?.tags?.length) return;
for (let i = 0; i < workflow.tags.length; i++) { for (let i = 0; i < workflow.tags.length; i++) {

View file

@ -247,14 +247,15 @@ describe('SourceControlExportService', () => {
projectRelations: [], projectRelations: [],
}), }),
workflow: mock({ workflow: mock({
display: () => 'TestWorkflow', id: 'test-workflow-id',
name: 'TestWorkflow',
}), }),
}), }),
]); ]);
// Act & Assert // Act & Assert
await expect(service.exportWorkflowsToWorkFolder([mock()])).rejects.toThrow( await expect(service.exportWorkflowsToWorkFolder([mock()])).rejects.toThrow(
'Workflow TestWorkflow has no owner', 'Workflow "TestWorkflow" (ID: test-workflow-id) has no owner',
); );
}); });
}); });

View file

@ -6,12 +6,13 @@ import { ApplicationError, type ICredentialDataDecryptedObject } from 'n8n-workf
import { writeFile as fsWriteFile, rm as fsRm } from 'node:fs/promises'; import { writeFile as fsWriteFile, rm as fsRm } from 'node:fs/promises';
import path from 'path'; import path from 'path';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository'; import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository'; import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
import { TagRepository } from '@/databases/repositories/tag.repository'; import { TagRepository } from '@/databases/repositories/tag.repository';
import { WorkflowTagMappingRepository } from '@/databases/repositories/workflow-tag-mapping.repository'; import { WorkflowTagMappingRepository } from '@/databases/repositories/workflow-tag-mapping.repository';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import type { IWorkflowDb } from '@/interfaces';
import { formatWorkflow } from '@/workflows/workflow.formatter';
import { import {
SOURCE_CONTROL_CREDENTIAL_EXPORT_FOLDER, SOURCE_CONTROL_CREDENTIAL_EXPORT_FOLDER,
@ -84,7 +85,7 @@ export class SourceControlExportService {
} }
private async writeExportableWorkflowsToExportFolder( private async writeExportableWorkflowsToExportFolder(
workflowsToBeExported: WorkflowEntity[], workflowsToBeExported: IWorkflowDb[],
owners: Record<string, ResourceOwner>, owners: Record<string, ResourceOwner>,
) { ) {
await Promise.all( await Promise.all(
@ -119,7 +120,9 @@ export class SourceControlExportService {
const project = sharedWorkflow.project; const project = sharedWorkflow.project;
if (!project) { if (!project) {
throw new ApplicationError(`Workflow ${sharedWorkflow.workflow.display()} has no owner`); throw new ApplicationError(
`Workflow ${formatWorkflow(sharedWorkflow.workflow)} has no owner`,
);
} }
if (project.type === 'personal') { if (project.type === 'personal') {
@ -128,7 +131,7 @@ export class SourceControlExportService {
); );
if (!ownerRelation) { if (!ownerRelation) {
throw new ApplicationError( throw new ApplicationError(
`Workflow ${sharedWorkflow.workflow.display()} has no owner`, `Workflow ${formatWorkflow(sharedWorkflow.workflow)} has no owner`,
); );
} }
owners[sharedWorkflow.workflowId] = { owners[sharedWorkflow.workflowId] = {

View file

@ -9,6 +9,6 @@ export interface ExportableWorkflow {
connections: IConnections; connections: IConnections;
settings?: IWorkflowSettings; settings?: IWorkflowSettings;
triggerCount: number; triggerCount: number;
versionId: string; versionId?: string;
owner: ResourceOwner; owner: ResourceOwner;
} }

View file

@ -1,10 +1,8 @@
import type { Workflow } from 'n8n-workflow'; import type { Workflow, IWorkflowBase } from 'n8n-workflow';
import { ApplicationError } from 'n8n-workflow'; import { ApplicationError } from 'n8n-workflow';
import type { IWorkflowDb } from '@/interfaces';
export class WorkflowMissingIdError extends ApplicationError { export class WorkflowMissingIdError extends ApplicationError {
constructor(workflow: Workflow | IWorkflowDb) { constructor(workflow: Workflow | IWorkflowBase) {
super('Detected ID-less worklfow', { extra: { workflow } }); super('Detected ID-less worklfow', { extra: { workflow } });
} }
} }

View file

@ -17,7 +17,6 @@ import type { ExecutionEntity } from '@/databases/entities/execution-entity';
import type { MockedNodeItem, TestDefinition } from '@/databases/entities/test-definition.ee'; import type { MockedNodeItem, TestDefinition } from '@/databases/entities/test-definition.ee';
import type { TestRun } from '@/databases/entities/test-run.ee'; import type { TestRun } from '@/databases/entities/test-run.ee';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ExecutionRepository } from '@/databases/repositories/execution.repository'; import { ExecutionRepository } from '@/databases/repositories/execution.repository';
import { TestCaseExecutionRepository } from '@/databases/repositories/test-case-execution.repository.ee'; import { TestCaseExecutionRepository } from '@/databases/repositories/test-case-execution.repository.ee';
import { TestMetricRepository } from '@/databases/repositories/test-metric.repository.ee'; import { TestMetricRepository } from '@/databases/repositories/test-metric.repository.ee';
@ -80,7 +79,7 @@ export class TestRunnerService {
* Prepares the start nodes and trigger node data props for the `workflowRunner.run` method input. * Prepares the start nodes and trigger node data props for the `workflowRunner.run` method input.
*/ */
private getStartNodesData( private getStartNodesData(
workflow: WorkflowEntity, workflow: IWorkflowBase,
pastExecutionData: IRunExecutionData, pastExecutionData: IRunExecutionData,
pastExecutionWorkflowData: IWorkflowBase, pastExecutionWorkflowData: IWorkflowBase,
): Pick<IWorkflowExecutionDataProcess, 'startNodes' | 'triggerToStartFrom'> { ): Pick<IWorkflowExecutionDataProcess, 'startNodes' | 'triggerToStartFrom'> {
@ -140,7 +139,7 @@ export class TestRunnerService {
* Waits for the workflow under test to finish execution. * Waits for the workflow under test to finish execution.
*/ */
private async runTestCase( private async runTestCase(
workflow: WorkflowEntity, workflow: IWorkflowBase,
pastExecutionData: IRunExecutionData, pastExecutionData: IRunExecutionData,
pastExecutionWorkflowData: IWorkflowBase, pastExecutionWorkflowData: IWorkflowBase,
mockedNodes: MockedNodeItem[], mockedNodes: MockedNodeItem[],
@ -197,7 +196,7 @@ export class TestRunnerService {
* Run the evaluation workflow with the expected and actual run data. * Run the evaluation workflow with the expected and actual run data.
*/ */
private async runTestCaseEvaluation( private async runTestCaseEvaluation(
evaluationWorkflow: WorkflowEntity, evaluationWorkflow: IWorkflowBase,
expectedData: IRunData, expectedData: IRunData,
actualData: IRunData, actualData: IRunData,
abortSignal: AbortSignal, abortSignal: AbortSignal,

View file

@ -3,7 +3,6 @@ import type { IRunExecutionData, IPinData, IWorkflowBase } from 'n8n-workflow';
import type { TestCaseExecution } from '@/databases/entities/test-case-execution.ee'; import type { TestCaseExecution } from '@/databases/entities/test-case-execution.ee';
import type { MockedNodeItem } from '@/databases/entities/test-definition.ee'; import type { MockedNodeItem } from '@/databases/entities/test-definition.ee';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import type { TestRunFinalResult } from '@/databases/repositories/test-run.repository.ee'; import type { TestRunFinalResult } from '@/databases/repositories/test-run.repository.ee';
import { TestCaseExecutionError } from '@/evaluation.ee/test-runner/errors.ee'; import { TestCaseExecutionError } from '@/evaluation.ee/test-runner/errors.ee';
@ -14,7 +13,7 @@ import { TestCaseExecutionError } from '@/evaluation.ee/test-runner/errors.ee';
* to decide which nodes to pin. * to decide which nodes to pin.
*/ */
export function createPinData( export function createPinData(
workflow: WorkflowEntity, workflow: IWorkflowBase,
mockedNodes: MockedNodeItem[], mockedNodes: MockedNodeItem[],
executionData: IRunExecutionData, executionData: IRunExecutionData,
pastWorkflowData?: IWorkflowBase, pastWorkflowData?: IWorkflowBase,

View file

@ -1,6 +1,5 @@
import type { PushMessage, WorkerStatus } from '@n8n/api-types'; import type { PushMessage, WorkerStatus } from '@n8n/api-types';
import type { IWorkflowBase } from 'n8n-workflow';
import type { IWorkflowDb } from '@/interfaces';
export type PubSubEventMap = PubSubCommandMap & PubSubWorkerResponseMap; export type PubSubEventMap = PubSubCommandMap & PubSubWorkerResponseMap;
@ -70,7 +69,7 @@ export type PubSubCommandMap = {
'clear-test-webhooks': { 'clear-test-webhooks': {
webhookKey: string; webhookKey: string;
workflowEntity: IWorkflowDb; workflowEntity: IWorkflowBase;
pushRef: string; pushRef: string;
}; };

View file

@ -1,11 +1,10 @@
import type { IWorkflowBase } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow'; import { NodeConnectionType } from 'n8n-workflow';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
/** /**
* Workflow producing an execution whose data will be truncated by an instance crash. * Workflow producing an execution whose data will be truncated by an instance crash.
*/ */
export const OOM_WORKFLOW: Partial<WorkflowEntity> = { export const OOM_WORKFLOW: Partial<IWorkflowBase> = {
nodes: [ nodes: [
{ {
parameters: {}, parameters: {},

View file

@ -36,7 +36,6 @@ import type {
CreateExecutionPayload, CreateExecutionPayload,
IExecutionFlattedResponse, IExecutionFlattedResponse,
IExecutionResponse, IExecutionResponse,
IWorkflowDb,
} from '@/interfaces'; } from '@/interfaces';
import { License } from '@/license'; import { License } from '@/license';
import { NodeTypes } from '@/node-types'; import { NodeTypes } from '@/node-types';
@ -268,7 +267,7 @@ export class ExecutionService {
async createErrorExecution( async createErrorExecution(
error: ExecutionError, error: ExecutionError,
node: INode, node: INode,
workflowData: IWorkflowDb, workflowData: IWorkflowBase,
workflow: Workflow, workflow: Workflow,
mode: WorkflowExecuteMode, mode: WorkflowExecuteMode,
) { ) {

View file

@ -90,6 +90,7 @@ export type IAnnotationTagWithCountDb = IAnnotationTagDb & UsageCount;
// Almost identical to editor-ui.Interfaces.ts // Almost identical to editor-ui.Interfaces.ts
export interface IWorkflowDb extends IWorkflowBase { export interface IWorkflowDb extends IWorkflowBase {
triggerCount: number;
tags?: TagEntity[]; tags?: TagEntity[];
} }

View file

@ -330,7 +330,7 @@ export = {
} }
// change the status to active in the DB // change the status to active in the DB
await setWorkflowAsActive(workflow); await setWorkflowAsActive(workflow.id);
workflow.active = true; workflow.active = true;
@ -363,7 +363,7 @@ export = {
if (workflow.active) { if (workflow.active) {
await activeWorkflowManager.remove(workflow.id); await activeWorkflowManager.remove(workflow.id);
await setWorkflowAsInactive(workflow); await setWorkflowAsInactive(workflow.id);
workflow.active = false; workflow.active = false;

View file

@ -1,6 +1,7 @@
import { GlobalConfig } from '@n8n/config'; import { GlobalConfig } from '@n8n/config';
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import type { Scope } from '@n8n/permissions'; import type { Scope } from '@n8n/permissions';
import type { WorkflowId } from 'n8n-workflow';
import type { Project } from '@/databases/entities/project'; import type { Project } from '@/databases/entities/project';
import { SharedWorkflow, type WorkflowSharingRole } from '@/databases/entities/shared-workflow'; import { SharedWorkflow, type WorkflowSharingRole } from '@/databases/entities/shared-workflow';
@ -83,15 +84,15 @@ export async function createWorkflow(
}); });
} }
export async function setWorkflowAsActive(workflow: WorkflowEntity) { export async function setWorkflowAsActive(workflowId: WorkflowId) {
await Container.get(WorkflowRepository).update(workflow.id, { await Container.get(WorkflowRepository).update(workflowId, {
active: true, active: true,
updatedAt: new Date(), updatedAt: new Date(),
}); });
} }
export async function setWorkflowAsInactive(workflow: WorkflowEntity) { export async function setWorkflowAsInactive(workflowId: WorkflowId) {
return await Container.get(WorkflowRepository).update(workflow.id, { return await Container.get(WorkflowRepository).update(workflowId, {
active: false, active: false,
updatedAt: new Date(), updatedAt: new Date(),
}); });

View file

@ -1,14 +1,13 @@
import type { WorkerStatus } from '@n8n/api-types'; import type { WorkerStatus } from '@n8n/api-types';
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import type { InstanceSettings } from 'n8n-core'; import type { InstanceSettings } from 'n8n-core';
import type { Workflow } from 'n8n-workflow'; import type { IWorkflowBase, Workflow } from 'n8n-workflow';
import type { ActiveWorkflowManager } from '@/active-workflow-manager'; import type { ActiveWorkflowManager } from '@/active-workflow-manager';
import type { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import type { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import type { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import type { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
import { EventService } from '@/events/event.service'; import { EventService } from '@/events/event.service';
import type { ExternalSecretsManager } from '@/external-secrets.ee/external-secrets-manager.ee'; import type { ExternalSecretsManager } from '@/external-secrets.ee/external-secrets-manager.ee';
import type { IWorkflowDb } from '@/interfaces';
import type { License } from '@/license'; import type { License } from '@/license';
import type { Push } from '@/push'; import type { Push } from '@/push';
import type { CommunityPackagesService } from '@/services/community-packages.service'; import type { CommunityPackagesService } from '@/services/community-packages.service';
@ -852,7 +851,7 @@ describe('PubSubHandler', () => {
).init(); ).init();
const webhookKey = 'test-webhook-key'; const webhookKey = 'test-webhook-key';
const workflowEntity = mock<IWorkflowDb>({ id: 'test-workflow-id' }); const workflowEntity = mock<IWorkflowBase>({ id: 'test-workflow-id' });
const pushRef = 'test-push-ref'; const pushRef = 'test-push-ref';
push.hasPushRef.mockReturnValue(true); push.hasPushRef.mockReturnValue(true);

View file

@ -2,7 +2,6 @@ import { SecurityConfig } from '@n8n/config';
import { Service } from '@n8n/di'; import { Service } from '@n8n/di';
import type { IWorkflowBase } from 'n8n-workflow'; import type { IWorkflowBase } from 'n8n-workflow';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { CredentialsRepository } from '@/databases/repositories/credentials.repository'; import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
import { ExecutionDataRepository } from '@/databases/repositories/execution-data.repository'; import { ExecutionDataRepository } from '@/databases/repositories/execution-data.repository';
import { ExecutionRepository } from '@/databases/repositories/execution.repository'; import { ExecutionRepository } from '@/databases/repositories/execution.repository';
@ -18,7 +17,7 @@ export class CredentialsRiskReporter implements RiskReporter {
private readonly securityConfig: SecurityConfig, private readonly securityConfig: SecurityConfig,
) {} ) {}
async report(workflows: WorkflowEntity[]) { async report(workflows: IWorkflowBase[]) {
const days = this.securityConfig.daysAbandonedWorkflow; const days = this.securityConfig.daysAbandonedWorkflow;
const allExistingCreds = await this.getAllExistingCreds(); const allExistingCreds = await this.getAllExistingCreds();
@ -84,7 +83,7 @@ export class CredentialsRiskReporter implements RiskReporter {
return report; return report;
} }
private async getAllCredsInUse(workflows: WorkflowEntity[]) { private async getAllCredsInUse(workflows: IWorkflowBase[]) {
const credsInAnyUse = new Set<string>(); const credsInAnyUse = new Set<string>();
const credsInActiveUse = new Set<string>(); const credsInActiveUse = new Set<string>();

View file

@ -1,6 +1,6 @@
import { Service } from '@n8n/di'; import { Service } from '@n8n/di';
import type { IWorkflowBase } from 'n8n-workflow';
import type { WorkflowEntity as Workflow } from '@/databases/entities/workflow-entity';
import { import {
SQL_NODE_TYPES, SQL_NODE_TYPES,
DATABASE_REPORT, DATABASE_REPORT,
@ -12,7 +12,7 @@ import { toFlaggedNode } from '@/security-audit/utils';
@Service() @Service()
export class DatabaseRiskReporter implements RiskReporter { export class DatabaseRiskReporter implements RiskReporter {
async report(workflows: Workflow[]) { async report(workflows: IWorkflowBase[]) {
const { expressionsInQueries, expressionsInQueryParams, unusedQueryParams } = const { expressionsInQueries, expressionsInQueryParams, unusedQueryParams } =
this.getIssues(workflows); this.getIssues(workflows);
@ -69,7 +69,7 @@ export class DatabaseRiskReporter implements RiskReporter {
return report; return report;
} }
private getIssues(workflows: Workflow[]) { private getIssues(workflows: IWorkflowBase[]) {
return workflows.reduce<{ [sectionTitle: string]: Risk.NodeLocation[] }>( return workflows.reduce<{ [sectionTitle: string]: Risk.NodeLocation[] }>(
(acc, workflow) => { (acc, workflow) => {
workflow.nodes.forEach((node) => { workflow.nodes.forEach((node) => {

View file

@ -1,13 +1,13 @@
import { Service } from '@n8n/di'; import { Service } from '@n8n/di';
import type { IWorkflowBase } from 'n8n-workflow';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { FILESYSTEM_INTERACTION_NODE_TYPES, FILESYSTEM_REPORT } from '@/security-audit/constants'; import { FILESYSTEM_INTERACTION_NODE_TYPES, FILESYSTEM_REPORT } from '@/security-audit/constants';
import type { RiskReporter, Risk } from '@/security-audit/types'; import type { RiskReporter, Risk } from '@/security-audit/types';
import { getNodeTypes } from '@/security-audit/utils'; import { getNodeTypes } from '@/security-audit/utils';
@Service() @Service()
export class FilesystemRiskReporter implements RiskReporter { export class FilesystemRiskReporter implements RiskReporter {
async report(workflows: WorkflowEntity[]) { async report(workflows: IWorkflowBase[]) {
const fsInteractionNodeTypes = getNodeTypes(workflows, (node) => const fsInteractionNodeTypes = getNodeTypes(workflows, (node) =>
FILESYSTEM_INTERACTION_NODE_TYPES.has(node.type), FILESYSTEM_INTERACTION_NODE_TYPES.has(node.type),
); );

View file

@ -2,10 +2,10 @@ import { GlobalConfig } from '@n8n/config';
import { Service } from '@n8n/di'; import { Service } from '@n8n/di';
import axios from 'axios'; import axios from 'axios';
import { InstanceSettings, Logger } from 'n8n-core'; import { InstanceSettings, Logger } from 'n8n-core';
import type { IWorkflowBase } from 'n8n-workflow';
import config from '@/config'; import config from '@/config';
import { getN8nPackageJson, inDevelopment } from '@/constants'; import { getN8nPackageJson, inDevelopment } from '@/constants';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { isApiEnabled } from '@/public-api'; import { isApiEnabled } from '@/public-api';
import { import {
ENV_VARS_DOCS_URL, ENV_VARS_DOCS_URL,
@ -25,7 +25,7 @@ export class InstanceRiskReporter implements RiskReporter {
private readonly globalConfig: GlobalConfig, private readonly globalConfig: GlobalConfig,
) {} ) {}
async report(workflows: WorkflowEntity[]) { async report(workflows: IWorkflowBase[]) {
const unprotectedWebhooks = this.getUnprotectedWebhookNodes(workflows); const unprotectedWebhooks = this.getUnprotectedWebhookNodes(workflows);
const outdatedState = await this.getOutdatedState(); const outdatedState = await this.getOutdatedState();
const securitySettings = this.getSecuritySettings(); const securitySettings = this.getSecuritySettings();
@ -115,8 +115,8 @@ export class InstanceRiskReporter implements RiskReporter {
node, node,
workflow, workflow,
}: { }: {
node: WorkflowEntity['nodes'][number]; node: IWorkflowBase['nodes'][number];
workflow: WorkflowEntity; workflow: IWorkflowBase;
}) { }) {
const childNodeNames = workflow.connections[node.name]?.main[0]?.map((i) => i.node); const childNodeNames = workflow.connections[node.name]?.main[0]?.map((i) => i.node);
@ -127,7 +127,7 @@ export class InstanceRiskReporter implements RiskReporter {
); );
} }
private getUnprotectedWebhookNodes(workflows: WorkflowEntity[]) { private getUnprotectedWebhookNodes(workflows: IWorkflowBase[]) {
return workflows.reduce<Risk.NodeLocation[]>((acc, workflow) => { return workflows.reduce<Risk.NodeLocation[]>((acc, workflow) => {
if (!workflow.active) return acc; if (!workflow.active) return acc;

View file

@ -1,9 +1,9 @@
import { GlobalConfig } from '@n8n/config'; import { GlobalConfig } from '@n8n/config';
import { Service } from '@n8n/di'; import { Service } from '@n8n/di';
import glob from 'fast-glob'; import glob from 'fast-glob';
import type { IWorkflowBase } from 'n8n-workflow';
import * as path from 'path'; import * as path from 'path';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials'; import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import { import {
OFFICIAL_RISKY_NODE_TYPES, OFFICIAL_RISKY_NODE_TYPES,
@ -24,7 +24,7 @@ export class NodesRiskReporter implements RiskReporter {
private readonly globalConfig: GlobalConfig, private readonly globalConfig: GlobalConfig,
) {} ) {}
async report(workflows: WorkflowEntity[]) { async report(workflows: IWorkflowBase[]) {
const officialRiskyNodes = getNodeTypes(workflows, (node) => const officialRiskyNodes = getNodeTypes(workflows, (node) =>
OFFICIAL_RISKY_NODE_TYPES.has(node.type), OFFICIAL_RISKY_NODE_TYPES.has(node.type),
); );

View file

@ -1,4 +1,4 @@
import type { WorkflowEntity as Workflow } from '@/databases/entities/workflow-entity'; import type { IWorkflowBase } from 'n8n-workflow';
export namespace Risk { export namespace Risk {
export type Category = 'database' | 'credentials' | 'nodes' | 'instance' | 'filesystem'; export type Category = 'database' | 'credentials' | 'nodes' | 'instance' | 'filesystem';
@ -62,16 +62,16 @@ export namespace Risk {
[reportTitle: string]: Report; [reportTitle: string]: Report;
}; };
export type SyncReportFn = (workflows: Workflow[]) => StandardReport | null; export type SyncReportFn = (workflows: IWorkflowBase[]) => StandardReport | null;
export type AsyncReportFn = (workflows: Workflow[]) => Promise<Report | null>; export type AsyncReportFn = (workflows: IWorkflowBase[]) => Promise<Report | null>;
} }
export namespace n8n { export namespace n8n {
export type Version = { export type Version = {
name: string; name: string;
nodes: Array< nodes: Array<
Workflow['nodes'][number] & { IWorkflowBase['nodes'][number] & {
iconData?: { type: string; fileBuffer: string }; // removed to declutter report iconData?: { type: string; fileBuffer: string }; // removed to declutter report
} }
>; >;
@ -86,5 +86,5 @@ export namespace n8n {
} }
export interface RiskReporter { export interface RiskReporter {
report(workflows: Workflow[]): Promise<Risk.Report | null>; report(workflows: IWorkflowBase[]): Promise<Risk.Report | null>;
} }

View file

@ -1,9 +1,10 @@
import type { WorkflowEntity as Workflow } from '@/databases/entities/workflow-entity'; import type { IWorkflowBase } from 'n8n-workflow';
import type { Risk } from '@/security-audit/types'; import type { Risk } from '@/security-audit/types';
type Node = Workflow['nodes'][number]; type Node = IWorkflowBase['nodes'][number];
export const toFlaggedNode = ({ node, workflow }: { node: Node; workflow: Workflow }) => ({ export const toFlaggedNode = ({ node, workflow }: { node: Node; workflow: IWorkflowBase }) => ({
kind: 'node' as const, kind: 'node' as const,
workflowId: workflow.id, workflowId: workflow.id,
workflowName: workflow.name, workflowName: workflow.name,
@ -15,7 +16,7 @@ export const toFlaggedNode = ({ node, workflow }: { node: Node; workflow: Workfl
export const toReportTitle = (riskCategory: Risk.Category) => export const toReportTitle = (riskCategory: Risk.Category) =>
riskCategory.charAt(0).toUpperCase() + riskCategory.slice(1) + ' Risk Report'; riskCategory.charAt(0).toUpperCase() + riskCategory.slice(1) + ' Risk Report';
export function getNodeTypes(workflows: Workflow[], test: (element: Node) => boolean) { export function getNodeTypes(workflows: IWorkflowBase[], test: (element: Node) => boolean) {
return workflows.reduce<Risk.NodeLocation[]>((acc, workflow) => { return workflows.reduce<Risk.NodeLocation[]>((acc, workflow) => {
workflow.nodes.forEach((node) => { workflow.nodes.forEach((node) => {
if (test(node)) acc.push(toFlaggedNode({ node, workflow })); if (test(node)) acc.push(toFlaggedNode({ node, workflow }));

View file

@ -1,6 +1,6 @@
import { Service } from '@n8n/di'; import { Service } from '@n8n/di';
import { Logger } from 'n8n-core'; import { Logger } from 'n8n-core';
import { type INode, type INodeCredentialsDetails } from 'n8n-workflow'; import { type INode, type INodeCredentialsDetails, type IWorkflowBase } from 'n8n-workflow';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { Project } from '@/databases/entities/project'; import { Project } from '@/databases/entities/project';
@ -11,7 +11,7 @@ import { WorkflowTagMapping } from '@/databases/entities/workflow-tag-mapping';
import { CredentialsRepository } from '@/databases/repositories/credentials.repository'; import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
import { TagRepository } from '@/databases/repositories/tag.repository'; import { TagRepository } from '@/databases/repositories/tag.repository';
import * as Db from '@/db'; import * as Db from '@/db';
import type { ICredentialsDb } from '@/interfaces'; import type { ICredentialsDb, IWorkflowDb } from '@/interfaces';
import { replaceInvalidCredentials } from '@/workflow-helpers'; import { replaceInvalidCredentials } from '@/workflow-helpers';
@Service() @Service()
@ -31,7 +31,7 @@ export class ImportService {
this.dbTags = await this.tagRepository.find(); this.dbTags = await this.tagRepository.find();
} }
async importWorkflows(workflows: WorkflowEntity[], projectId: string) { async importWorkflows(workflows: IWorkflowDb[], projectId: string) {
await this.initRecords(); await this.initRecords();
for (const workflow of workflows) { for (const workflow of workflows) {
@ -84,7 +84,7 @@ export class ImportService {
}); });
} }
async replaceInvalidCreds(workflow: WorkflowEntity) { async replaceInvalidCreds(workflow: IWorkflowBase) {
try { try {
await replaceInvalidCredentials(workflow); await replaceInvalidCredentials(workflow);
} catch (e) { } catch (e) {

View file

@ -4,11 +4,11 @@ import { existsSync } from 'fs';
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import Handlebars from 'handlebars'; import Handlebars from 'handlebars';
import { Logger } from 'n8n-core'; import { Logger } from 'n8n-core';
import type { IWorkflowBase } from 'n8n-workflow';
import { join as pathJoin } from 'path'; import { join as pathJoin } from 'path';
import { inTest } from '@/constants'; import { inTest } from '@/constants';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { UserRepository } from '@/databases/repositories/user.repository'; import { UserRepository } from '@/databases/repositories/user.repository';
import { InternalServerError } from '@/errors/response-errors/internal-server.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { EventService } from '@/events/event.service'; import { EventService } from '@/events/event.service';
@ -81,7 +81,7 @@ export class UserManagementMailer {
}: { }: {
sharer: User; sharer: User;
newShareeIds: string[]; newShareeIds: string[];
workflow: WorkflowEntity; workflow: IWorkflowBase;
}): Promise<SendEmailResult> { }): Promise<SendEmailResult> {
if (!this.mailer) return { emailSent: false }; if (!this.mailer) return { emailSent: false };

View file

@ -1,6 +1,6 @@
import type * as express from 'express'; import type * as express from 'express';
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import type { ITaskData } from 'n8n-workflow'; import type { ITaskData, IWorkflowBase } from 'n8n-workflow';
import { import {
type IWebhookData, type IWebhookData,
type IWorkflowExecuteAdditionalData, type IWorkflowExecuteAdditionalData,
@ -11,7 +11,6 @@ import { v4 as uuid } from 'uuid';
import { generateNanoId } from '@/databases/utils/generators'; import { generateNanoId } from '@/databases/utils/generators';
import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { WebhookNotFoundError } from '@/errors/response-errors/webhook-not-found.error'; import { WebhookNotFoundError } from '@/errors/response-errors/webhook-not-found.error';
import type { IWorkflowDb } from '@/interfaces';
import type { import type {
TestWebhookRegistrationsService, TestWebhookRegistrationsService,
TestWebhookRegistration, TestWebhookRegistration,
@ -26,7 +25,7 @@ jest.mock('@/workflow-execute-additional-data');
const mockedAdditionalData = AdditionalData as jest.Mocked<typeof AdditionalData>; const mockedAdditionalData = AdditionalData as jest.Mocked<typeof AdditionalData>;
const workflowEntity = mock<IWorkflowDb>({ id: generateNanoId(), nodes: [] }); const workflowEntity = mock<IWorkflowBase>({ id: generateNanoId(), nodes: [] });
const httpMethod = 'GET'; const httpMethod = 'GET';
const path = uuid(); const path = uuid();

View file

@ -1,14 +1,13 @@
import { Service } from '@n8n/di'; import { Service } from '@n8n/di';
import { InstanceSettings } from 'n8n-core'; import { InstanceSettings } from 'n8n-core';
import type { IWebhookData } from 'n8n-workflow'; import type { IWebhookData, IWorkflowBase } from 'n8n-workflow';
import { TEST_WEBHOOK_TIMEOUT, TEST_WEBHOOK_TIMEOUT_BUFFER } from '@/constants'; import { TEST_WEBHOOK_TIMEOUT, TEST_WEBHOOK_TIMEOUT_BUFFER } from '@/constants';
import type { IWorkflowDb } from '@/interfaces';
import { CacheService } from '@/services/cache/cache.service'; import { CacheService } from '@/services/cache/cache.service';
export type TestWebhookRegistration = { export type TestWebhookRegistration = {
pushRef?: string; pushRef?: string;
workflowEntity: IWorkflowDb; workflowEntity: IWorkflowBase;
destinationNode?: string; destinationNode?: string;
webhook: IWebhookData; webhook: IWebhookData;
}; };

View file

@ -7,13 +7,13 @@ import type {
IWorkflowExecuteAdditionalData, IWorkflowExecuteAdditionalData,
IHttpRequestMethods, IHttpRequestMethods,
IRunData, IRunData,
IWorkflowBase,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { TEST_WEBHOOK_TIMEOUT } from '@/constants'; import { TEST_WEBHOOK_TIMEOUT } from '@/constants';
import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { WebhookNotFoundError } from '@/errors/response-errors/webhook-not-found.error'; import { WebhookNotFoundError } from '@/errors/response-errors/webhook-not-found.error';
import { WorkflowMissingIdError } from '@/errors/workflow-missing-id.error'; import { WorkflowMissingIdError } from '@/errors/workflow-missing-id.error';
import type { IWorkflowDb } from '@/interfaces';
import { NodeTypes } from '@/node-types'; import { NodeTypes } from '@/node-types';
import { Push } from '@/push'; import { Push } from '@/push';
import { Publisher } from '@/scaling/pubsub/publisher.service'; import { Publisher } from '@/scaling/pubsub/publisher.service';
@ -217,7 +217,7 @@ export class TestWebhooks implements IWebhookManager {
*/ */
async needsWebhook(options: { async needsWebhook(options: {
userId: string; userId: string;
workflowEntity: IWorkflowDb; workflowEntity: IWorkflowBase;
additionalData: IWorkflowExecuteAdditionalData; additionalData: IWorkflowExecuteAdditionalData;
runData?: IRunData; runData?: IRunData;
pushRef?: string; pushRef?: string;
@ -434,9 +434,10 @@ export class TestWebhooks implements IWebhookManager {
} }
/** /**
* Convert a `WorkflowEntity` from `typeorm` to a temporary `Workflow` from `n8n-workflow`. * Convert a `IWorkflowBase` interface (e.g. `WorkflowEntity`) to a temporary
* `Workflow` from `n8n-workflow`.
*/ */
toWorkflow(workflowEntity: IWorkflowDb) { toWorkflow(workflowEntity: IWorkflowBase) {
return new Workflow({ return new Workflow({
id: workflowEntity.id, id: workflowEntity.id,
name: workflowEntity.name, name: workflowEntity.name,

View file

@ -13,7 +13,7 @@ import {
import { ExecutionRepository } from '@/databases/repositories/execution.repository'; import { ExecutionRepository } from '@/databases/repositories/execution.repository';
import { ConflictError } from '@/errors/response-errors/conflict.error'; import { ConflictError } from '@/errors/response-errors/conflict.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error';
import type { IExecutionResponse, IWorkflowDb } from '@/interfaces'; import type { IExecutionResponse } from '@/interfaces';
import { NodeTypes } from '@/node-types'; import { NodeTypes } from '@/node-types';
import * as WebhookHelpers from '@/webhooks/webhook-helpers'; import * as WebhookHelpers from '@/webhooks/webhook-helpers';
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data'; import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
@ -217,7 +217,7 @@ export class WaitingWebhooks implements IWebhookManager {
void WebhookHelpers.executeWebhook( void WebhookHelpers.executeWebhook(
workflow, workflow,
webhookData, webhookData,
workflowData as IWorkflowDb, workflowData,
workflowStartNode, workflowStartNode,
executionMode, executionMode,
runExecutionData.pushRef, runExecutionData.pushRef,

View file

@ -29,6 +29,7 @@ import type {
Workflow, Workflow,
WorkflowExecuteMode, WorkflowExecuteMode,
IWorkflowExecutionDataProcess, IWorkflowExecutionDataProcess,
IWorkflowBase,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
ApplicationError, ApplicationError,
@ -47,7 +48,6 @@ import type { Project } from '@/databases/entities/project';
import { InternalServerError } from '@/errors/response-errors/internal-server.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error'; import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error';
import type { IWorkflowDb } from '@/interfaces';
import { parseBody } from '@/middlewares'; import { parseBody } from '@/middlewares';
import { OwnershipService } from '@/services/ownership.service'; import { OwnershipService } from '@/services/ownership.service';
import { WorkflowStatisticsService } from '@/services/workflow-statistics.service'; import { WorkflowStatisticsService } from '@/services/workflow-statistics.service';
@ -111,7 +111,7 @@ const parseFormData = createMultiFormDataParser(formDataFileSizeMax);
export async function executeWebhook( export async function executeWebhook(
workflow: Workflow, workflow: Workflow,
webhookData: IWebhookData, webhookData: IWebhookData,
workflowData: IWorkflowDb, workflowData: IWorkflowBase,
workflowStartNode: INode, workflowStartNode: INode,
executionMode: WorkflowExecuteMode, executionMode: WorkflowExecuteMode,
pushRef: string | undefined, pushRef: string | undefined,

View file

@ -9,10 +9,10 @@ import type {
WorkflowExecuteMode, WorkflowExecuteMode,
WorkflowOperationError, WorkflowOperationError,
NodeOperationError, NodeOperationError,
IWorkflowBase,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { CredentialsRepository } from '@/databases/repositories/credentials.repository'; import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
import { VariablesService } from '@/environments.ee/variables/variables.service.ee'; import { VariablesService } from '@/environments.ee/variables/variables.service.ee';
@ -103,7 +103,7 @@ export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefi
/** /**
* Set node ids if not already set * Set node ids if not already set
*/ */
export function addNodeIds(workflow: WorkflowEntity) { export function addNodeIds(workflow: IWorkflowBase) {
const { nodes } = workflow; const { nodes } = workflow;
if (!nodes) return; if (!nodes) return;
@ -115,7 +115,7 @@ export function addNodeIds(workflow: WorkflowEntity) {
} }
// Checking if credentials of old format are in use and run a DB check if they might exist uniquely // Checking if credentials of old format are in use and run a DB check if they might exist uniquely
export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promise<WorkflowEntity> { export async function replaceInvalidCredentials<T extends IWorkflowBase>(workflow: T): Promise<T> {
const { nodes } = workflow; const { nodes } = workflow;
if (!nodes) return workflow; if (!nodes) return workflow;

View file

@ -1,9 +1,7 @@
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import type { INode, IWorkflowExecuteAdditionalData } from 'n8n-workflow'; import type { INode, IWorkflowBase, IWorkflowExecuteAdditionalData } from 'n8n-workflow';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import type { IWorkflowDb } from '@/interfaces';
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data'; import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
import type { WorkflowRunner } from '@/workflow-runner'; import type { WorkflowRunner } from '@/workflow-runner';
import { WorkflowExecutionService } from '@/workflows/workflow-execution.service'; import { WorkflowExecutionService } from '@/workflows/workflow-execution.service';
@ -73,7 +71,7 @@ describe('WorkflowExecutionService', () => {
describe('runWorkflow()', () => { describe('runWorkflow()', () => {
test('should call `WorkflowRunner.run()`', async () => { test('should call `WorkflowRunner.run()`', async () => {
const node = mock<INode>(); const node = mock<INode>();
const workflow = mock<WorkflowEntity>({ active: true, nodes: [node] }); const workflow = mock<IWorkflowBase>({ active: true, nodes: [node] });
workflowRunner.run.mockResolvedValue('fake-execution-id'); workflowRunner.run.mockResolvedValue('fake-execution-id');
@ -300,7 +298,7 @@ describe('WorkflowExecutionService', () => {
}); });
describe('selectPinnedActivatorStarter()', () => { describe('selectPinnedActivatorStarter()', () => {
const workflow = mock<IWorkflowDb>({ const workflow = mock<IWorkflowBase>({
nodes: [], nodes: [],
}); });

View file

@ -12,6 +12,7 @@ import type {
IWorkflowExecuteAdditionalData, IWorkflowExecuteAdditionalData,
WorkflowExecuteMode, WorkflowExecuteMode,
IWorkflowExecutionDataProcess, IWorkflowExecutionDataProcess,
IWorkflowBase,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { SubworkflowOperationError, Workflow } from 'n8n-workflow'; import { SubworkflowOperationError, Workflow } from 'n8n-workflow';
@ -20,7 +21,7 @@ import type { Project } from '@/databases/entities/project';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import { ExecutionRepository } from '@/databases/repositories/execution.repository'; import { ExecutionRepository } from '@/databases/repositories/execution.repository';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import type { CreateExecutionPayload, IWorkflowDb, IWorkflowErrorData } from '@/interfaces'; import type { CreateExecutionPayload, IWorkflowErrorData } from '@/interfaces';
import { NodeTypes } from '@/node-types'; import { NodeTypes } from '@/node-types';
import { SubworkflowPolicyChecker } from '@/subworkflows/subworkflow-policy-checker.service'; import { SubworkflowPolicyChecker } from '@/subworkflows/subworkflow-policy-checker.service';
import { TestWebhooks } from '@/webhooks/test-webhooks'; import { TestWebhooks } from '@/webhooks/test-webhooks';
@ -44,7 +45,7 @@ export class WorkflowExecutionService {
) {} ) {}
async runWorkflow( async runWorkflow(
workflowData: IWorkflowDb, workflowData: IWorkflowBase,
node: INode, node: INode,
data: INodeExecutionData[][], data: INodeExecutionData[][],
additionalData: IWorkflowExecuteAdditionalData, additionalData: IWorkflowExecuteAdditionalData,
@ -346,7 +347,7 @@ export class WorkflowExecutionService {
* prioritizing `n8n-nodes-base.webhook` over other activators. If the executed node * prioritizing `n8n-nodes-base.webhook` over other activators. If the executed node
* has no upstream nodes and is itself is a pinned activator, select it. * has no upstream nodes and is itself is a pinned activator, select it.
*/ */
selectPinnedActivatorStarter(workflow: IWorkflowDb, startNodes?: string[], pinData?: IPinData) { selectPinnedActivatorStarter(workflow: IWorkflowBase, startNodes?: string[], pinData?: IPinData) {
if (!pinData || !startNodes) return null; if (!pinData || !startNodes) return null;
const allPinnedActivators = this.findAllPinnedActivators(workflow, pinData); const allPinnedActivators = this.findAllPinnedActivators(workflow, pinData);
@ -385,7 +386,7 @@ export class WorkflowExecutionService {
return allPinnedActivators.find((pa) => pa.name === firstStartNodeName) ?? null; return allPinnedActivators.find((pa) => pa.name === firstStartNodeName) ?? null;
} }
private findAllPinnedActivators(workflow: IWorkflowDb, pinData?: IPinData) { private findAllPinnedActivators(workflow: IWorkflowBase, pinData?: IPinData) {
return workflow.nodes return workflow.nodes
.filter( .filter(
(node) => (node) =>

View file

@ -1,9 +1,9 @@
import { Service } from '@n8n/di'; import { Service } from '@n8n/di';
import { Logger } from 'n8n-core'; import { Logger } from 'n8n-core';
import type { IWorkflowBase } from 'n8n-workflow';
import { ensureError } from 'n8n-workflow'; import { ensureError } from 'n8n-workflow';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import type { WorkflowHistory } from '@/databases/entities/workflow-history'; import type { WorkflowHistory } from '@/databases/entities/workflow-history';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository'; import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository'; import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
@ -66,7 +66,7 @@ export class WorkflowHistoryService {
return hist; return hist;
} }
async saveVersion(user: User, workflow: WorkflowEntity, workflowId: string) { async saveVersion(user: User, workflow: IWorkflowBase, workflowId: string) {
// On some update scenarios, `nodes` and `connections` are missing, such as when // On some update scenarios, `nodes` and `connections` are missing, such as when
// changing workflow settings or renaming. In these cases, we don't want to save // changing workflow settings or renaming. In these cases, we don't want to save
// a new version // a new version

View file

@ -0,0 +1,8 @@
import type { IWorkflowBase } from 'n8n-workflow';
/**
* Display a workflow in a user-friendly format
*/
export function formatWorkflow(workflow: IWorkflowBase) {
return `"${workflow.name}" (ID: ${workflow.id})`;
}

View file

@ -5,9 +5,9 @@ import type {
IRunData, IRunData,
StartNodeData, StartNodeData,
ITaskData, ITaskData,
IWorkflowBase,
} from 'n8n-workflow'; } from 'n8n-workflow';
import type { IWorkflowDb } from '@/interfaces';
import type { AuthenticatedRequest, ListQuery } from '@/requests'; import type { AuthenticatedRequest, ListQuery } from '@/requests';
export declare namespace WorkflowRequest { export declare namespace WorkflowRequest {
@ -25,7 +25,7 @@ export declare namespace WorkflowRequest {
}>; }>;
type ManualRunPayload = { type ManualRunPayload = {
workflowData: IWorkflowDb; workflowData: IWorkflowBase;
runData: IRunData; runData: IRunData;
startNodes?: StartNodeData[]; startNodes?: StartNodeData[];
destinationNode?: string; destinationNode?: string;

View file

@ -3,6 +3,7 @@ import { Service } from '@n8n/di';
import { In, type EntityManager } from '@n8n/typeorm'; import { In, type EntityManager } from '@n8n/typeorm';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import { Logger } from 'n8n-core'; import { Logger } from 'n8n-core';
import type { IWorkflowBase, WorkflowId } from 'n8n-workflow';
import { ApplicationError, NodeOperationError, WorkflowActivationError } from 'n8n-workflow'; import { ApplicationError, NodeOperationError, WorkflowActivationError } from 'n8n-workflow';
import { ActiveWorkflowManager } from '@/active-workflow-manager'; import { ActiveWorkflowManager } from '@/active-workflow-manager';
@ -11,7 +12,6 @@ import type { CredentialsEntity } from '@/databases/entities/credentials-entity'
import { Project } from '@/databases/entities/project'; import { Project } from '@/databases/entities/project';
import { SharedWorkflow } from '@/databases/entities/shared-workflow'; import { SharedWorkflow } from '@/databases/entities/shared-workflow';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { CredentialsRepository } from '@/databases/repositories/credentials.repository'; import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository'; import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
@ -40,7 +40,7 @@ export class EnterpriseWorkflowService {
) {} ) {}
async shareWithProjects( async shareWithProjects(
workflow: WorkflowEntity, workflowId: WorkflowId,
shareWithIds: string[], shareWithIds: string[],
entityManager: EntityManager, entityManager: EntityManager,
) { ) {
@ -55,7 +55,7 @@ export class EnterpriseWorkflowService {
// always only be one owner. // always only be one owner.
.map((project) => .map((project) =>
this.sharedWorkflowRepository.create({ this.sharedWorkflowRepository.create({
workflowId: workflow.id, workflowId,
role: 'workflow:editor', role: 'workflow:editor',
projectId: project.id, projectId: project.id,
}), }),
@ -118,7 +118,7 @@ export class EnterpriseWorkflowService {
} }
validateCredentialPermissionsToUser( validateCredentialPermissionsToUser(
workflow: WorkflowEntity, workflow: IWorkflowBase,
allowedCredentials: CredentialsEntity[], allowedCredentials: CredentialsEntity[],
) { ) {
workflow.nodes.forEach((node) => { workflow.nodes.forEach((node) => {
@ -138,7 +138,7 @@ export class EnterpriseWorkflowService {
}); });
} }
async preventTampering(workflow: WorkflowEntity, workflowId: string, user: User) { async preventTampering<T extends IWorkflowBase>(workflow: T, workflowId: string, user: User) {
const previousVersion = await this.workflowRepository.get({ id: workflowId }); const previousVersion = await this.workflowRepository.get({ id: workflowId });
if (!previousVersion) { if (!previousVersion) {
@ -162,9 +162,9 @@ export class EnterpriseWorkflowService {
} }
} }
validateWorkflowCredentialUsage( validateWorkflowCredentialUsage<T extends IWorkflowBase>(
newWorkflowVersion: WorkflowEntity, newWorkflowVersion: T,
previousWorkflowVersion: WorkflowEntity, previousWorkflowVersion: IWorkflowBase,
credentialsUserHasAccessTo: Array<{ id: string }>, credentialsUserHasAccessTo: Array<{ id: string }>,
) { ) {
/** /**
@ -232,7 +232,7 @@ export class EnterpriseWorkflowService {
} }
/** Get all nodes in a workflow where the node credential is not accessible to the user. */ /** Get all nodes in a workflow where the node credential is not accessible to the user. */
getNodesWithInaccessibleCreds(workflow: WorkflowEntity, userCredIds: string[]) { getNodesWithInaccessibleCreds(workflow: IWorkflowBase, userCredIds: string[]) {
if (!workflow.nodes) { if (!workflow.nodes) {
return []; return [];
} }

View file

@ -447,7 +447,7 @@ export class WorkflowsController {
projectId: In(toUnshare), projectId: In(toUnshare),
}); });
await this.enterpriseWorkflowService.shareWithProjects(workflow, toShare, trx); await this.enterpriseWorkflowService.shareWithProjects(workflow.id, toShare, trx);
newShareeIds = toShare; newShareeIds = toShare;
}); });

View file

@ -2,12 +2,11 @@ import { Container } from '@n8n/di';
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import { Logger } from 'n8n-core'; import { Logger } from 'n8n-core';
import { NodeApiError, Workflow } from 'n8n-workflow'; import { NodeApiError, Workflow } from 'n8n-workflow';
import type { IWebhookData, WorkflowActivateMode } from 'n8n-workflow'; import type { IWebhookData, IWorkflowBase, WorkflowActivateMode } from 'n8n-workflow';
import { ActiveExecutions } from '@/active-executions'; import { ActiveExecutions } from '@/active-executions';
import { ActiveWorkflowManager } from '@/active-workflow-manager'; import { ActiveWorkflowManager } from '@/active-workflow-manager';
import type { WebhookEntity } from '@/databases/entities/webhook-entity'; import type { WebhookEntity } from '@/databases/entities/webhook-entity';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ExecutionService } from '@/executions/execution.service'; import { ExecutionService } from '@/executions/execution.service';
import { ExternalHooks } from '@/external-hooks'; import { ExternalHooks } from '@/external-hooks';
import { NodeTypes } from '@/node-types'; import { NodeTypes } from '@/node-types';
@ -36,8 +35,8 @@ const externalHooks = mockInstance(ExternalHooks);
let activeWorkflowManager: ActiveWorkflowManager; let activeWorkflowManager: ActiveWorkflowManager;
let createActiveWorkflow: () => Promise<WorkflowEntity>; let createActiveWorkflow: () => Promise<IWorkflowBase>;
let createInactiveWorkflow: () => Promise<WorkflowEntity>; let createInactiveWorkflow: () => Promise<IWorkflowBase>;
beforeAll(async () => { beforeAll(async () => {
await testDb.init(); await testDb.init();

View file

@ -1,5 +1,6 @@
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import type { IWorkflowBase } from 'n8n-workflow';
import type { import type {
WorkflowClosedMessage, WorkflowClosedMessage,
@ -7,7 +8,6 @@ import type {
} from '@/collaboration/collaboration.message'; } from '@/collaboration/collaboration.message';
import { CollaborationService } from '@/collaboration/collaboration.service'; import { CollaborationService } from '@/collaboration/collaboration.service';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { Push } from '@/push'; import { Push } from '@/push';
import { CacheService } from '@/services/cache/cache.service'; import { CacheService } from '@/services/cache/cache.service';
import { mockInstance } from '@test/mocking'; import { mockInstance } from '@test/mocking';
@ -22,7 +22,7 @@ describe('CollaborationService', () => {
let owner: User; let owner: User;
let memberWithoutAccess: User; let memberWithoutAccess: User;
let memberWithAccess: User; let memberWithAccess: User;
let workflow: WorkflowEntity; let workflow: IWorkflowBase;
let cacheService: CacheService; let cacheService: CacheService;
beforeAll(async () => { beforeAll(async () => {

View file

@ -1,8 +1,8 @@
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import type { IWorkflowBase } from 'n8n-workflow';
import type { TestDefinition } from '@/databases/entities/test-definition.ee'; import type { TestDefinition } from '@/databases/entities/test-definition.ee';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { TestDefinitionRepository } from '@/databases/repositories/test-definition.repository.ee'; import { TestDefinitionRepository } from '@/databases/repositories/test-definition.repository.ee';
import { TestMetricRepository } from '@/databases/repositories/test-metric.repository.ee'; import { TestMetricRepository } from '@/databases/repositories/test-metric.repository.ee';
import { createUserShell } from '@test-integration/db/users'; import { createUserShell } from '@test-integration/db/users';
@ -12,8 +12,8 @@ import type { SuperAgentTest } from '@test-integration/types';
import * as utils from '@test-integration/utils'; import * as utils from '@test-integration/utils';
let authOwnerAgent: SuperAgentTest; let authOwnerAgent: SuperAgentTest;
let workflowUnderTest: WorkflowEntity; let workflowUnderTest: IWorkflowBase;
let otherWorkflow: WorkflowEntity; let otherWorkflow: IWorkflowBase;
let testDefinition: TestDefinition; let testDefinition: TestDefinition;
let otherTestDefinition: TestDefinition; let otherTestDefinition: TestDefinition;
let ownerShell: User; let ownerShell: User;

View file

@ -1,9 +1,9 @@
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import { mockInstance } from 'n8n-core/test/utils'; import { mockInstance } from 'n8n-core/test/utils';
import type { IWorkflowBase } from 'n8n-workflow';
import type { AnnotationTagEntity } from '@/databases/entities/annotation-tag-entity.ee'; import type { AnnotationTagEntity } from '@/databases/entities/annotation-tag-entity.ee';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { TestDefinitionRepository } from '@/databases/repositories/test-definition.repository.ee'; import { TestDefinitionRepository } from '@/databases/repositories/test-definition.repository.ee';
import { TestRunnerService } from '@/evaluation.ee/test-runner/test-runner.service.ee'; import { TestRunnerService } from '@/evaluation.ee/test-runner/test-runner.service.ee';
import { createAnnotationTags } from '@test-integration/db/executions'; import { createAnnotationTags } from '@test-integration/db/executions';
@ -17,10 +17,10 @@ import * as utils from './../shared/utils/';
const testRunner = mockInstance(TestRunnerService); const testRunner = mockInstance(TestRunnerService);
let authOwnerAgent: SuperAgentTest; let authOwnerAgent: SuperAgentTest;
let workflowUnderTest: WorkflowEntity; let workflowUnderTest: IWorkflowBase;
let workflowUnderTest2: WorkflowEntity; let workflowUnderTest2: IWorkflowBase;
let evaluationWorkflow: WorkflowEntity; let evaluationWorkflow: IWorkflowBase;
let otherWorkflow: WorkflowEntity; let otherWorkflow: IWorkflowBase;
let ownerShell: User; let ownerShell: User;
let annotationTag: AnnotationTagEntity; let annotationTag: AnnotationTagEntity;
const testServer = utils.setupTestServer({ endpointGroups: ['evaluation'] }); const testServer = utils.setupTestServer({ endpointGroups: ['evaluation'] });

View file

@ -1,9 +1,9 @@
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import { mockInstance } from 'n8n-core/test/utils'; import { mockInstance } from 'n8n-core/test/utils';
import type { IWorkflowBase } from 'n8n-workflow';
import type { TestDefinition } from '@/databases/entities/test-definition.ee'; import type { TestDefinition } from '@/databases/entities/test-definition.ee';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ProjectRepository } from '@/databases/repositories/project.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository';
import { TestDefinitionRepository } from '@/databases/repositories/test-definition.repository.ee'; import { TestDefinitionRepository } from '@/databases/repositories/test-definition.repository.ee';
import { TestRunRepository } from '@/databases/repositories/test-run.repository.ee'; import { TestRunRepository } from '@/databases/repositories/test-run.repository.ee';
@ -15,8 +15,8 @@ import type { SuperAgentTest } from '@test-integration/types';
import * as utils from '@test-integration/utils'; import * as utils from '@test-integration/utils';
let authOwnerAgent: SuperAgentTest; let authOwnerAgent: SuperAgentTest;
let workflowUnderTest: WorkflowEntity; let workflowUnderTest: IWorkflowBase;
let otherWorkflow: WorkflowEntity; let otherWorkflow: IWorkflowBase;
let testDefinition: TestDefinition; let testDefinition: TestDefinition;
let otherTestDefinition: TestDefinition; let otherTestDefinition: TestDefinition;
let ownerShell: User; let ownerShell: User;

View file

@ -1,11 +1,10 @@
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import type { INode } from 'n8n-workflow'; import type { INode, IWorkflowBase } from 'n8n-workflow';
import { randomInt } from 'n8n-workflow'; import { randomInt } from 'n8n-workflow';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import type { Project } from '@/databases/entities/project'; import type { Project } from '@/databases/entities/project';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ProjectRepository } from '@/databases/repositories/project.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository';
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository'; import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository'; import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
@ -26,7 +25,7 @@ import { mockInstance } from '../shared/mocking';
const ownershipService = mockInstance(OwnershipService); const ownershipService = mockInstance(OwnershipService);
const createWorkflow = async (nodes: INode[], workflowOwner?: User): Promise<WorkflowEntity> => { const createWorkflow = async (nodes: INode[], workflowOwner?: User): Promise<IWorkflowBase> => {
const workflowDetails = { const workflowDetails = {
id: randomInt(1, 10).toString(), id: randomInt(1, 10).toString(),
name: 'test', name: 'test',

View file

@ -2,11 +2,10 @@ import { ExecutionsConfig } from '@n8n/config';
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import { BinaryDataService, InstanceSettings } from 'n8n-core'; import { BinaryDataService, InstanceSettings } from 'n8n-core';
import type { ExecutionStatus } from 'n8n-workflow'; import type { ExecutionStatus, IWorkflowBase } from 'n8n-workflow';
import { Time } from '@/constants'; import { Time } from '@/constants';
import type { ExecutionEntity } from '@/databases/entities/execution-entity'; import type { ExecutionEntity } from '@/databases/entities/execution-entity';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ExecutionRepository } from '@/databases/repositories/execution.repository'; import { ExecutionRepository } from '@/databases/repositories/execution.repository';
import { PruningService } from '@/services/pruning/pruning.service'; import { PruningService } from '@/services/pruning/pruning.service';
@ -26,7 +25,7 @@ describe('softDeleteOnPruningCycle()', () => {
const now = new Date(); const now = new Date();
const yesterday = new Date(Date.now() - 1 * Time.days.toMilliseconds); const yesterday = new Date(Date.now() - 1 * Time.days.toMilliseconds);
let workflow: WorkflowEntity; let workflow: IWorkflowBase;
let executionsConfig: ExecutionsConfig; let executionsConfig: ExecutionsConfig;
beforeAll(async () => { beforeAll(async () => {

View file

@ -1,9 +1,8 @@
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import type { AnnotationVote } from 'n8n-workflow'; import type { AnnotationVote, IWorkflowBase } from 'n8n-workflow';
import type { ExecutionData } from '@/databases/entities/execution-data'; import type { ExecutionData } from '@/databases/entities/execution-data';
import type { ExecutionEntity } from '@/databases/entities/execution-entity'; import type { ExecutionEntity } from '@/databases/entities/execution-entity';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { AnnotationTagRepository } from '@/databases/repositories/annotation-tag.repository.ee'; import { AnnotationTagRepository } from '@/databases/repositories/annotation-tag.repository.ee';
import { ExecutionDataRepository } from '@/databases/repositories/execution-data.repository'; import { ExecutionDataRepository } from '@/databases/repositories/execution-data.repository';
import { ExecutionMetadataRepository } from '@/databases/repositories/execution-metadata.repository'; import { ExecutionMetadataRepository } from '@/databases/repositories/execution-metadata.repository';
@ -16,8 +15,8 @@ mockInstance(Telemetry);
export async function createManyExecutions( export async function createManyExecutions(
amount: number, amount: number,
workflow: WorkflowEntity, workflow: IWorkflowBase,
callback: (workflow: WorkflowEntity) => Promise<ExecutionEntity>, callback: (workflow: IWorkflowBase) => Promise<ExecutionEntity>,
) { ) {
const executionsRequests = [...Array(amount)].map(async (_) => await callback(workflow)); const executionsRequests = [...Array(amount)].map(async (_) => await callback(workflow));
return await Promise.all(executionsRequests); return await Promise.all(executionsRequests);
@ -31,7 +30,7 @@ export async function createExecution(
Omit<ExecutionEntity, 'metadata'> & Omit<ExecutionEntity, 'metadata'> &
ExecutionData & { metadata: Array<{ key: string; value: string }> } ExecutionData & { metadata: Array<{ key: string; value: string }> }
>, >,
workflow: WorkflowEntity, workflow: IWorkflowBase,
) { ) {
const { data, finished, mode, startedAt, stoppedAt, waitTill, status, deletedAt, metadata } = const { data, finished, mode, startedAt, stoppedAt, waitTill, status, deletedAt, metadata } =
attributes; attributes;
@ -70,14 +69,14 @@ export async function createExecution(
/** /**
* Store a successful execution in the DB and assign it to a workflow. * Store a successful execution in the DB and assign it to a workflow.
*/ */
export async function createSuccessfulExecution(workflow: WorkflowEntity) { export async function createSuccessfulExecution(workflow: IWorkflowBase) {
return await createExecution({ finished: true, status: 'success' }, workflow); return await createExecution({ finished: true, status: 'success' }, workflow);
} }
/** /**
* Store an error execution in the DB and assign it to a workflow. * Store an error execution in the DB and assign it to a workflow.
*/ */
export async function createErrorExecution(workflow: WorkflowEntity) { export async function createErrorExecution(workflow: IWorkflowBase) {
return await createExecution( return await createExecution(
{ finished: false, stoppedAt: new Date(), status: 'error' }, { finished: false, stoppedAt: new Date(), status: 'error' },
workflow, workflow,
@ -87,7 +86,7 @@ export async function createErrorExecution(workflow: WorkflowEntity) {
/** /**
* Store a waiting execution in the DB and assign it to a workflow. * Store a waiting execution in the DB and assign it to a workflow.
*/ */
export async function createWaitingExecution(workflow: WorkflowEntity) { export async function createWaitingExecution(workflow: IWorkflowBase) {
return await createExecution( return await createExecution(
{ finished: false, waitTill: new Date(), status: 'waiting' }, { finished: false, waitTill: new Date(), status: 'waiting' },
workflow, workflow,

View file

@ -1,14 +1,14 @@
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import type { IWorkflowBase } from 'n8n-workflow';
import type { TagEntity } from '@/databases/entities/tag-entity'; import type { TagEntity } from '@/databases/entities/tag-entity';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { TagRepository } from '@/databases/repositories/tag.repository'; import { TagRepository } from '@/databases/repositories/tag.repository';
import { WorkflowTagMappingRepository } from '@/databases/repositories/workflow-tag-mapping.repository'; import { WorkflowTagMappingRepository } from '@/databases/repositories/workflow-tag-mapping.repository';
import { generateNanoId } from '@/databases/utils/generators'; import { generateNanoId } from '@/databases/utils/generators';
import { randomName } from '../random'; import { randomName } from '../random';
export async function createTag(attributes: Partial<TagEntity> = {}, workflow?: WorkflowEntity) { export async function createTag(attributes: Partial<TagEntity> = {}, workflow?: IWorkflowBase) {
const { name } = attributes; const { name } = attributes;
const tag = await Container.get(TagRepository).save({ const tag = await Container.get(TagRepository).save({

View file

@ -1,19 +1,20 @@
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import type { DeepPartial } from '@n8n/typeorm'; import type { DeepPartial } from '@n8n/typeorm';
import type { IWorkflowBase } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow'; import { NodeConnectionType } from 'n8n-workflow';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { Project } from '@/databases/entities/project'; import { Project } from '@/databases/entities/project';
import type { SharedWorkflow, WorkflowSharingRole } from '@/databases/entities/shared-workflow'; import type { SharedWorkflow, WorkflowSharingRole } from '@/databases/entities/shared-workflow';
import { User } from '@/databases/entities/user'; import { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ProjectRepository } from '@/databases/repositories/project.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository'; import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import type { IWorkflowDb } from '@/interfaces';
export async function createManyWorkflows( export async function createManyWorkflows(
amount: number, amount: number,
attributes: Partial<WorkflowEntity> = {}, attributes: Partial<IWorkflowDb> = {},
user?: User, user?: User,
) { ) {
const workflowRequests = [...Array(amount)].map( const workflowRequests = [...Array(amount)].map(
@ -22,7 +23,7 @@ export async function createManyWorkflows(
return await Promise.all(workflowRequests); return await Promise.all(workflowRequests);
} }
export function newWorkflow(attributes: Partial<WorkflowEntity> = {}): WorkflowEntity { export function newWorkflow(attributes: Partial<IWorkflowDb> = {}): IWorkflowDb {
const { active, name, nodes, connections, versionId } = attributes; const { active, name, nodes, connections, versionId } = attributes;
const workflowEntity = Container.get(WorkflowRepository).create({ const workflowEntity = Container.get(WorkflowRepository).create({
@ -53,7 +54,7 @@ export function newWorkflow(attributes: Partial<WorkflowEntity> = {}): WorkflowE
* @param user user to assign the workflow to * @param user user to assign the workflow to
*/ */
export async function createWorkflow( export async function createWorkflow(
attributes: Partial<WorkflowEntity> = {}, attributes: Partial<IWorkflowDb> = {},
userOrProject?: User | Project, userOrProject?: User | Project,
) { ) {
const workflow = await Container.get(WorkflowRepository).save(newWorkflow(attributes)); const workflow = await Container.get(WorkflowRepository).save(newWorkflow(attributes));
@ -84,7 +85,7 @@ export async function createWorkflow(
return workflow; return workflow;
} }
export async function shareWorkflowWithUsers(workflow: WorkflowEntity, users: User[]) { export async function shareWorkflowWithUsers(workflow: IWorkflowBase, users: User[]) {
const sharedWorkflows: Array<DeepPartial<SharedWorkflow>> = await Promise.all( const sharedWorkflows: Array<DeepPartial<SharedWorkflow>> = await Promise.all(
users.map(async (user) => { users.map(async (user) => {
const project = await Container.get(ProjectRepository).getPersonalProjectForUserOrFail( const project = await Container.get(ProjectRepository).getPersonalProjectForUserOrFail(
@ -101,7 +102,7 @@ export async function shareWorkflowWithUsers(workflow: WorkflowEntity, users: Us
} }
export async function shareWorkflowWithProjects( export async function shareWorkflowWithProjects(
workflow: WorkflowEntity, workflow: IWorkflowBase,
projectsWithRole: Array<{ project: Project; role?: WorkflowSharingRole }>, projectsWithRole: Array<{ project: Project; role?: WorkflowSharingRole }>,
) { ) {
const newSharedWorkflow = await Promise.all( const newSharedWorkflow = await Promise.all(
@ -117,7 +118,7 @@ export async function shareWorkflowWithProjects(
return await Container.get(SharedWorkflowRepository).save(newSharedWorkflow); return await Container.get(SharedWorkflowRepository).save(newSharedWorkflow);
} }
export async function getWorkflowSharing(workflow: WorkflowEntity) { export async function getWorkflowSharing(workflow: IWorkflowBase) {
return await Container.get(SharedWorkflowRepository).findBy({ return await Container.get(SharedWorkflowRepository).findBy({
workflowId: workflow.id, workflowId: workflow.id,
}); });
@ -128,7 +129,7 @@ export async function getWorkflowSharing(workflow: WorkflowEntity) {
* @param user user to assign the workflow to * @param user user to assign the workflow to
*/ */
export async function createWorkflowWithTrigger( export async function createWorkflowWithTrigger(
attributes: Partial<WorkflowEntity> = {}, attributes: Partial<IWorkflowDb> = {},
user?: User, user?: User,
) { ) {
const workflow = await createWorkflow( const workflow = await createWorkflow(

View file

@ -1,4 +1,5 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import type { IWorkflowBase } from 'n8n-workflow';
import { import {
NodeConnectionType, NodeConnectionType,
type INodeType, type INodeType,
@ -7,7 +8,6 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { agent as testAgent } from 'supertest'; import { agent as testAgent } from 'supertest';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ExternalHooks } from '@/external-hooks'; import { ExternalHooks } from '@/external-hooks';
import { NodeTypes } from '@/node-types'; import { NodeTypes } from '@/node-types';
import { Push } from '@/push'; import { Push } from '@/push';
@ -230,7 +230,7 @@ describe('Webhook API', () => {
node: WebhookTestingNode, node: WebhookTestingNode,
path = 'abcd', path = 'abcd',
httpMethod = 'POST', httpMethod = 'POST',
): Partial<WorkflowEntity> => ({ ): Partial<IWorkflowBase> => ({
active: true, active: true,
nodes: [ nodes: [
{ {

View file

@ -1,11 +1,10 @@
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import type { Scope } from '@n8n/permissions'; import type { Scope } from '@n8n/permissions';
import type { INode, IPinData } from 'n8n-workflow'; import type { INode, IPinData, IWorkflowBase } from 'n8n-workflow';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { ActiveWorkflowManager } from '@/active-workflow-manager'; import { ActiveWorkflowManager } from '@/active-workflow-manager';
import type { User } from '@/databases/entities/user'; import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ProjectRepository } from '@/databases/repositories/project.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository'; import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository'; import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
@ -519,7 +518,7 @@ describe('GET /workflows', () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(2); expect(response.body.data.length).toBe(2);
const workflows = response.body.data as Array<WorkflowEntity & { scopes: Scope[] }>; const workflows = response.body.data as Array<IWorkflowBase & { scopes: Scope[] }>;
const wf1 = workflows.find((w) => w.id === savedWorkflow1.id)!; const wf1 = workflows.find((w) => w.id === savedWorkflow1.id)!;
const wf2 = workflows.find((w) => w.id === savedWorkflow2.id)!; const wf2 = workflows.find((w) => w.id === savedWorkflow2.id)!;
@ -546,7 +545,7 @@ describe('GET /workflows', () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(2); expect(response.body.data.length).toBe(2);
const workflows = response.body.data as Array<WorkflowEntity & { scopes: Scope[] }>; const workflows = response.body.data as Array<IWorkflowBase & { scopes: Scope[] }>;
const wf1 = workflows.find((w) => w.id === savedWorkflow1.id)!; const wf1 = workflows.find((w) => w.id === savedWorkflow1.id)!;
const wf2 = workflows.find((w) => w.id === savedWorkflow2.id)!; const wf2 = workflows.find((w) => w.id === savedWorkflow2.id)!;
@ -579,7 +578,7 @@ describe('GET /workflows', () => {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(2); expect(response.body.data.length).toBe(2);
const workflows = response.body.data as Array<WorkflowEntity & { scopes: Scope[] }>; const workflows = response.body.data as Array<IWorkflowBase & { scopes: Scope[] }>;
const wf1 = workflows.find((w) => w.id === savedWorkflow1.id)!; const wf1 = workflows.find((w) => w.id === savedWorkflow1.id)!;
const wf2 = workflows.find((w) => w.id === savedWorkflow2.id)!; const wf2 = workflows.find((w) => w.id === savedWorkflow2.id)!;
@ -1124,7 +1123,7 @@ describe('PATCH /workflows/:workflowId', () => {
describe('POST /workflows/:workflowId/run', () => { describe('POST /workflows/:workflowId/run', () => {
let sharingSpy: jest.SpyInstance; let sharingSpy: jest.SpyInstance;
let tamperingSpy: jest.SpyInstance; let tamperingSpy: jest.SpyInstance;
let workflow: WorkflowEntity; let workflow: IWorkflowBase;
beforeAll(() => { beforeAll(() => {
const enterpriseWorkflowService = Container.get(EnterpriseWorkflowService); const enterpriseWorkflowService = Container.get(EnterpriseWorkflowService);

View file

@ -2196,6 +2196,8 @@ export interface IWaitingForExecutionSource {
}; };
} }
export type WorkflowId = IWorkflowBase['id'];
export interface IWorkflowBase { export interface IWorkflowBase {
id: string; id: string;
name: string; name: string;