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

View file

@ -39,13 +39,11 @@ import {
WORKFLOW_REACTIVATE_INITIAL_TIMEOUT,
WORKFLOW_REACTIVATE_MAX_TIMEOUT,
} from '@/constants';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import { OnShutdown } from '@/decorators/on-shutdown';
import { executeErrorWorkflow } from '@/execution-lifecycle/execute-error-workflow';
import { ExecutionService } from '@/executions/execution.service';
import { ExternalHooks } from '@/external-hooks';
import type { IWorkflowDb } from '@/interfaces';
import { NodeTypes } from '@/node-types';
import { Publisher } from '@/scaling/pubsub/publisher.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 { WorkflowExecutionService } from '@/workflows/workflow-execution.service';
import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service';
import { formatWorkflow } from '@/workflows/workflow.formatter';
interface QueuedActivation {
activationMode: WorkflowActivateMode;
lastTimeout: number;
timeout: NodeJS.Timeout;
workflowData: IWorkflowDb;
workflowData: IWorkflowBase;
}
@Service()
@ -271,7 +270,7 @@ export class ActiveWorkflowManager {
* and overwrites the emit to be able to start it in subprocess
*/
getExecutePollFunctions(
workflowData: IWorkflowDb,
workflowData: IWorkflowBase,
additionalData: IWorkflowExecuteAdditionalData,
mode: WorkflowExecuteMode,
activation: WorkflowActivateMode,
@ -322,7 +321,7 @@ export class ActiveWorkflowManager {
* and overwrites the emit to be able to start it in subprocess
*/
getExecuteTriggerFunctions(
workflowData: IWorkflowDb,
workflowData: IWorkflowBase,
additionalData: IWorkflowExecuteAdditionalData,
mode: WorkflowExecuteMode,
activation: WorkflowActivateMode,
@ -379,7 +378,7 @@ export class ActiveWorkflowManager {
);
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);
};
@ -436,7 +435,7 @@ export class ActiveWorkflowManager {
}
private async activateWorkflow(
dbWorkflow: WorkflowEntity,
dbWorkflow: IWorkflowBase,
activationMode: 'init' | 'leadershipChange',
) {
try {
@ -444,9 +443,9 @@ export class ActiveWorkflowManager {
shouldPublish: false,
});
if (wasActivated) {
this.logger.info(` - ${dbWorkflow.display()})`);
this.logger.info(` - ${formatWorkflow(dbWorkflow)})`);
this.logger.info(' => Started');
this.logger.debug(`Successfully started workflow ${dbWorkflow.display()}`, {
this.logger.debug(`Successfully started workflow ${formatWorkflow(dbWorkflow)}`, {
workflowName: dbWorkflow.name,
workflowId: dbWorkflow.id,
});
@ -454,12 +453,12 @@ export class ActiveWorkflowManager {
} catch (error) {
this.errorReporter.error(error);
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.error(
`Issue on initial workflow activation try of ${dbWorkflow.display()} (startup)`,
`Issue on initial workflow activation try of ${formatWorkflow(dbWorkflow)} (startup)`,
{
workflowName: dbWorkflow.name,
workflowId: dbWorkflow.id,
@ -518,7 +517,7 @@ export class ActiveWorkflowManager {
async add(
workflowId: string,
activationMode: WorkflowActivateMode,
existingWorkflow?: WorkflowEntity,
existingWorkflow?: IWorkflowBase,
{ shouldPublish } = { shouldPublish: true },
) {
if (this.instanceSettings.isMultiMain && shouldPublish) {
@ -549,7 +548,7 @@ export class ActiveWorkflowManager {
}
if (shouldDisplayActivationMessage) {
this.logger.debug(`Initializing active workflow ${dbWorkflow.display()} (startup)`, {
this.logger.debug(`Initializing active workflow ${formatWorkflow(dbWorkflow)} (startup)`, {
workflowName: dbWorkflow.name,
workflowId: dbWorkflow.id,
});
@ -570,7 +569,7 @@ export class ActiveWorkflowManager {
if (!canBeActivated) {
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' },
);
}
@ -673,7 +672,7 @@ export class ActiveWorkflowManager {
*/
private addQueuedWorkflowActivation(
activationMode: WorkflowActivateMode,
workflowData: WorkflowEntity,
workflowData: IWorkflowBase,
) {
const workflowId = workflowData.id;
const workflowName = workflowData.name;
@ -811,7 +810,7 @@ export class ActiveWorkflowManager {
* Register as active in memory a trigger- or poller-based workflow.
*/
async addTriggersAndPollers(
dbWorkflow: WorkflowEntity,
dbWorkflow: IWorkflowBase,
workflow: Workflow,
{
activationMode,
@ -838,7 +837,7 @@ export class ActiveWorkflowManager {
);
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(
workflow.id,
@ -850,7 +849,7 @@ export class ActiveWorkflowManager {
getPollFunctions,
);
this.logger.debug(`Workflow ${dbWorkflow.display()} activated`, {
this.logger.debug(`Workflow ${formatWorkflow(dbWorkflow)} activated`, {
workflowId: dbWorkflow.id,
workflowName: dbWorkflow.name,
});

View file

@ -4,7 +4,7 @@ import { Flags } from '@oclif/core';
import fs from 'fs';
import { diff } from 'json-diff';
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 os from 'os';
import { sep } from 'path';
@ -12,7 +12,6 @@ import { sep } from 'path';
import { ActiveExecutions } from '@/active-executions';
import type { User } from '@/databases/entities/user';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import type { IWorkflowDb } from '@/interfaces';
import { OwnershipService } from '@/services/ownership.service';
import { findCliWorkflowStart } from '@/utils';
import { WorkflowRunner } from '@/workflow-runner';
@ -275,7 +274,7 @@ export class ExecuteBatch extends BaseCommand {
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) {
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 = {
totalWorkflows: allWorkflows.length,
slackMessage: '',
@ -401,7 +400,7 @@ export class ExecuteBatch extends BaseCommand {
const promisesArray = [];
for (let i = 0; i < ExecuteBatch.concurrency; i++) {
const promise = new Promise(async (resolve) => {
let workflow: IWorkflowDb | undefined;
let workflow: IWorkflowBase | undefined;
while (allWorkflows.length > 0) {
workflow = allWorkflows.shift();
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.
// It will be updated according to execution progress below.
const executionResult: IExecutionResult = {

View file

@ -2,6 +2,7 @@ import { Container } from '@n8n/di';
import { Flags } from '@oclif/core';
import glob from 'fast-glob';
import fs from 'fs';
import type { IWorkflowBase, WorkflowId } from 'n8n-workflow';
import { ApplicationError, jsonParse } from 'n8n-workflow';
import { UM_FIX_INSTRUCTION } from '@/constants';
@ -102,7 +103,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
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.
if (!userId && !projectId) {
return {
@ -112,11 +113,11 @@ export class ImportWorkflowsCommand extends BaseCommand {
}
for (const workflow of workflows) {
if (!(await this.workflowExists(workflow))) {
if (!(await this.workflowExists(workflow.id))) {
continue;
}
const { user, project: ownerProject } = await this.getWorkflowOwner(workflow);
const { user, project: ownerProject } = await this.getWorkflowOwner(workflow.id);
if (!ownerProject) {
continue;
@ -155,9 +156,9 @@ export class ImportWorkflowsCommand extends BaseCommand {
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({
where: { workflowId: workflow.id, role: 'workflow:owner' },
where: { workflowId, role: 'workflow:owner' },
relations: { project: true },
});
@ -175,8 +176,8 @@ export class ImportWorkflowsCommand extends BaseCommand {
return {};
}
private async workflowExists(workflow: WorkflowEntity) {
return await Container.get(WorkflowRepository).existsBy({ id: workflow.id });
private async workflowExists(workflowId: WorkflowId) {
return await Container.get(WorkflowRepository).existsBy({ id: workflowId });
}
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' })
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 intersection from 'lodash/intersection';
import type { IWorkflowDb } from '@/interfaces';
import { TagEntity } from '../entities/tag-entity';
import type { WorkflowEntity } from '../entities/workflow-entity';
@Service()
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,
* 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;
for (let i = 0; i < workflow.tags.length; i++) {

View file

@ -247,14 +247,15 @@ describe('SourceControlExportService', () => {
projectRelations: [],
}),
workflow: mock({
display: () => 'TestWorkflow',
id: 'test-workflow-id',
name: 'TestWorkflow',
}),
}),
]);
// Act & Assert
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 path from 'path';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
import { TagRepository } from '@/databases/repositories/tag.repository';
import { WorkflowTagMappingRepository } from '@/databases/repositories/workflow-tag-mapping.repository';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import type { IWorkflowDb } from '@/interfaces';
import { formatWorkflow } from '@/workflows/workflow.formatter';
import {
SOURCE_CONTROL_CREDENTIAL_EXPORT_FOLDER,
@ -84,7 +85,7 @@ export class SourceControlExportService {
}
private async writeExportableWorkflowsToExportFolder(
workflowsToBeExported: WorkflowEntity[],
workflowsToBeExported: IWorkflowDb[],
owners: Record<string, ResourceOwner>,
) {
await Promise.all(
@ -119,7 +120,9 @@ export class SourceControlExportService {
const project = sharedWorkflow.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') {
@ -128,7 +131,7 @@ export class SourceControlExportService {
);
if (!ownerRelation) {
throw new ApplicationError(
`Workflow ${sharedWorkflow.workflow.display()} has no owner`,
`Workflow ${formatWorkflow(sharedWorkflow.workflow)} has no owner`,
);
}
owners[sharedWorkflow.workflowId] = {

View file

@ -9,6 +9,6 @@ export interface ExportableWorkflow {
connections: IConnections;
settings?: IWorkflowSettings;
triggerCount: number;
versionId: string;
versionId?: string;
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 type { IWorkflowDb } from '@/interfaces';
export class WorkflowMissingIdError extends ApplicationError {
constructor(workflow: Workflow | IWorkflowDb) {
constructor(workflow: Workflow | IWorkflowBase) {
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 { TestRun } from '@/databases/entities/test-run.ee';
import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ExecutionRepository } from '@/databases/repositories/execution.repository';
import { TestCaseExecutionRepository } from '@/databases/repositories/test-case-execution.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.
*/
private getStartNodesData(
workflow: WorkflowEntity,
workflow: IWorkflowBase,
pastExecutionData: IRunExecutionData,
pastExecutionWorkflowData: IWorkflowBase,
): Pick<IWorkflowExecutionDataProcess, 'startNodes' | 'triggerToStartFrom'> {
@ -140,7 +139,7 @@ export class TestRunnerService {
* Waits for the workflow under test to finish execution.
*/
private async runTestCase(
workflow: WorkflowEntity,
workflow: IWorkflowBase,
pastExecutionData: IRunExecutionData,
pastExecutionWorkflowData: IWorkflowBase,
mockedNodes: MockedNodeItem[],
@ -197,7 +196,7 @@ export class TestRunnerService {
* Run the evaluation workflow with the expected and actual run data.
*/
private async runTestCaseEvaluation(
evaluationWorkflow: WorkflowEntity,
evaluationWorkflow: IWorkflowBase,
expectedData: IRunData,
actualData: IRunData,
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 { 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 { 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.
*/
export function createPinData(
workflow: WorkflowEntity,
workflow: IWorkflowBase,
mockedNodes: MockedNodeItem[],
executionData: IRunExecutionData,
pastWorkflowData?: IWorkflowBase,

View file

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

View file

@ -1,11 +1,10 @@
import type { IWorkflowBase } 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.
*/
export const OOM_WORKFLOW: Partial<WorkflowEntity> = {
export const OOM_WORKFLOW: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {},

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,13 @@
import type { WorkerStatus } from '@n8n/api-types';
import { mock } from 'jest-mock-extended';
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 { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import type { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
import { EventService } from '@/events/event.service';
import type { ExternalSecretsManager } from '@/external-secrets.ee/external-secrets-manager.ee';
import type { IWorkflowDb } from '@/interfaces';
import type { License } from '@/license';
import type { Push } from '@/push';
import type { CommunityPackagesService } from '@/services/community-packages.service';
@ -852,7 +851,7 @@ describe('PubSubHandler', () => {
).init();
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';
push.hasPushRef.mockReturnValue(true);

View file

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

View file

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

View file

@ -1,13 +1,13 @@
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 type { RiskReporter, Risk } from '@/security-audit/types';
import { getNodeTypes } from '@/security-audit/utils';
@Service()
export class FilesystemRiskReporter implements RiskReporter {
async report(workflows: WorkflowEntity[]) {
async report(workflows: IWorkflowBase[]) {
const fsInteractionNodeTypes = getNodeTypes(workflows, (node) =>
FILESYSTEM_INTERACTION_NODE_TYPES.has(node.type),
);

View file

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

View file

@ -1,9 +1,9 @@
import { GlobalConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import glob from 'fast-glob';
import type { IWorkflowBase } from 'n8n-workflow';
import * as path from 'path';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import {
OFFICIAL_RISKY_NODE_TYPES,
@ -24,7 +24,7 @@ export class NodesRiskReporter implements RiskReporter {
private readonly globalConfig: GlobalConfig,
) {}
async report(workflows: WorkflowEntity[]) {
async report(workflows: IWorkflowBase[]) {
const officialRiskyNodes = getNodeTypes(workflows, (node) =>
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 type Category = 'database' | 'credentials' | 'nodes' | 'instance' | 'filesystem';
@ -62,16 +62,16 @@ export namespace Risk {
[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 type Version = {
name: string;
nodes: Array<
Workflow['nodes'][number] & {
IWorkflowBase['nodes'][number] & {
iconData?: { type: string; fileBuffer: string }; // removed to declutter report
}
>;
@ -86,5 +86,5 @@ export namespace n8n {
}
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';
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,
workflowId: workflow.id,
workflowName: workflow.name,
@ -15,7 +16,7 @@ export const toFlaggedNode = ({ node, workflow }: { node: Node; workflow: Workfl
export const toReportTitle = (riskCategory: Risk.Category) =>
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) => {
workflow.nodes.forEach((node) => {
if (test(node)) acc.push(toFlaggedNode({ node, workflow }));

View file

@ -1,6 +1,6 @@
import { Service } from '@n8n/di';
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 { 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 { TagRepository } from '@/databases/repositories/tag.repository';
import * as Db from '@/db';
import type { ICredentialsDb } from '@/interfaces';
import type { ICredentialsDb, IWorkflowDb } from '@/interfaces';
import { replaceInvalidCredentials } from '@/workflow-helpers';
@Service()
@ -31,7 +31,7 @@ export class ImportService {
this.dbTags = await this.tagRepository.find();
}
async importWorkflows(workflows: WorkflowEntity[], projectId: string) {
async importWorkflows(workflows: IWorkflowDb[], projectId: string) {
await this.initRecords();
for (const workflow of workflows) {
@ -84,7 +84,7 @@ export class ImportService {
});
}
async replaceInvalidCreds(workflow: WorkflowEntity) {
async replaceInvalidCreds(workflow: IWorkflowBase) {
try {
await replaceInvalidCredentials(workflow);
} catch (e) {

View file

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

View file

@ -1,6 +1,6 @@
import type * as express from 'express';
import { mock } from 'jest-mock-extended';
import type { ITaskData } from 'n8n-workflow';
import type { ITaskData, IWorkflowBase } from 'n8n-workflow';
import {
type IWebhookData,
type IWorkflowExecuteAdditionalData,
@ -11,7 +11,6 @@ import { v4 as uuid } from 'uuid';
import { generateNanoId } from '@/databases/utils/generators';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { WebhookNotFoundError } from '@/errors/response-errors/webhook-not-found.error';
import type { IWorkflowDb } from '@/interfaces';
import type {
TestWebhookRegistrationsService,
TestWebhookRegistration,
@ -26,7 +25,7 @@ jest.mock('@/workflow-execute-additional-data');
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 path = uuid();

View file

@ -1,14 +1,13 @@
import { Service } from '@n8n/di';
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 type { IWorkflowDb } from '@/interfaces';
import { CacheService } from '@/services/cache/cache.service';
export type TestWebhookRegistration = {
pushRef?: string;
workflowEntity: IWorkflowDb;
workflowEntity: IWorkflowBase;
destinationNode?: string;
webhook: IWebhookData;
};

View file

@ -7,13 +7,13 @@ import type {
IWorkflowExecuteAdditionalData,
IHttpRequestMethods,
IRunData,
IWorkflowBase,
} from 'n8n-workflow';
import { TEST_WEBHOOK_TIMEOUT } from '@/constants';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { WebhookNotFoundError } from '@/errors/response-errors/webhook-not-found.error';
import { WorkflowMissingIdError } from '@/errors/workflow-missing-id.error';
import type { IWorkflowDb } from '@/interfaces';
import { NodeTypes } from '@/node-types';
import { Push } from '@/push';
import { Publisher } from '@/scaling/pubsub/publisher.service';
@ -217,7 +217,7 @@ export class TestWebhooks implements IWebhookManager {
*/
async needsWebhook(options: {
userId: string;
workflowEntity: IWorkflowDb;
workflowEntity: IWorkflowBase;
additionalData: IWorkflowExecuteAdditionalData;
runData?: IRunData;
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({
id: workflowEntity.id,
name: workflowEntity.name,

View file

@ -13,7 +13,7 @@ import {
import { ExecutionRepository } from '@/databases/repositories/execution.repository';
import { ConflictError } from '@/errors/response-errors/conflict.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 * as WebhookHelpers from '@/webhooks/webhook-helpers';
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
@ -217,7 +217,7 @@ export class WaitingWebhooks implements IWebhookManager {
void WebhookHelpers.executeWebhook(
workflow,
webhookData,
workflowData as IWorkflowDb,
workflowData,
workflowStartNode,
executionMode,
runExecutionData.pushRef,

View file

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

View file

@ -9,10 +9,10 @@ import type {
WorkflowExecuteMode,
WorkflowOperationError,
NodeOperationError,
IWorkflowBase,
} from 'n8n-workflow';
import { v4 as uuid } from 'uuid';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
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
*/
export function addNodeIds(workflow: WorkflowEntity) {
export function addNodeIds(workflow: IWorkflowBase) {
const { nodes } = workflow;
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
export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promise<WorkflowEntity> {
export async function replaceInvalidCredentials<T extends IWorkflowBase>(workflow: T): Promise<T> {
const { nodes } = workflow;
if (!nodes) return workflow;

View file

@ -1,9 +1,7 @@
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 { WorkflowEntity } from '@/databases/entities/workflow-entity';
import type { IWorkflowDb } from '@/interfaces';
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
import type { WorkflowRunner } from '@/workflow-runner';
import { WorkflowExecutionService } from '@/workflows/workflow-execution.service';
@ -73,7 +71,7 @@ describe('WorkflowExecutionService', () => {
describe('runWorkflow()', () => {
test('should call `WorkflowRunner.run()`', async () => {
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');
@ -300,7 +298,7 @@ describe('WorkflowExecutionService', () => {
});
describe('selectPinnedActivatorStarter()', () => {
const workflow = mock<IWorkflowDb>({
const workflow = mock<IWorkflowBase>({
nodes: [],
});

View file

@ -12,6 +12,7 @@ import type {
IWorkflowExecuteAdditionalData,
WorkflowExecuteMode,
IWorkflowExecutionDataProcess,
IWorkflowBase,
} 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 { ExecutionRepository } from '@/databases/repositories/execution.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 { SubworkflowPolicyChecker } from '@/subworkflows/subworkflow-policy-checker.service';
import { TestWebhooks } from '@/webhooks/test-webhooks';
@ -44,7 +45,7 @@ export class WorkflowExecutionService {
) {}
async runWorkflow(
workflowData: IWorkflowDb,
workflowData: IWorkflowBase,
node: INode,
data: INodeExecutionData[][],
additionalData: IWorkflowExecuteAdditionalData,
@ -346,7 +347,7 @@ export class WorkflowExecutionService {
* 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.
*/
selectPinnedActivatorStarter(workflow: IWorkflowDb, startNodes?: string[], pinData?: IPinData) {
selectPinnedActivatorStarter(workflow: IWorkflowBase, startNodes?: string[], pinData?: IPinData) {
if (!pinData || !startNodes) return null;
const allPinnedActivators = this.findAllPinnedActivators(workflow, pinData);
@ -385,7 +386,7 @@ export class WorkflowExecutionService {
return allPinnedActivators.find((pa) => pa.name === firstStartNodeName) ?? null;
}
private findAllPinnedActivators(workflow: IWorkflowDb, pinData?: IPinData) {
private findAllPinnedActivators(workflow: IWorkflowBase, pinData?: IPinData) {
return workflow.nodes
.filter(
(node) =>

View file

@ -1,9 +1,9 @@
import { Service } from '@n8n/di';
import { Logger } from 'n8n-core';
import type { IWorkflowBase } from 'n8n-workflow';
import { ensureError } from 'n8n-workflow';
import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import type { WorkflowHistory } from '@/databases/entities/workflow-history';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
@ -66,7 +66,7 @@ export class WorkflowHistoryService {
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
// changing workflow settings or renaming. In these cases, we don't want to save
// 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,
StartNodeData,
ITaskData,
IWorkflowBase,
} from 'n8n-workflow';
import type { IWorkflowDb } from '@/interfaces';
import type { AuthenticatedRequest, ListQuery } from '@/requests';
export declare namespace WorkflowRequest {
@ -25,7 +25,7 @@ export declare namespace WorkflowRequest {
}>;
type ManualRunPayload = {
workflowData: IWorkflowDb;
workflowData: IWorkflowBase;
runData: IRunData;
startNodes?: StartNodeData[];
destinationNode?: string;

View file

@ -3,6 +3,7 @@ import { Service } from '@n8n/di';
import { In, type EntityManager } from '@n8n/typeorm';
import omit from 'lodash/omit';
import { Logger } from 'n8n-core';
import type { IWorkflowBase, WorkflowId } from 'n8n-workflow';
import { ApplicationError, NodeOperationError, WorkflowActivationError } from 'n8n-workflow';
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 { SharedWorkflow } from '@/databases/entities/shared-workflow';
import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
@ -40,7 +40,7 @@ export class EnterpriseWorkflowService {
) {}
async shareWithProjects(
workflow: WorkflowEntity,
workflowId: WorkflowId,
shareWithIds: string[],
entityManager: EntityManager,
) {
@ -55,7 +55,7 @@ export class EnterpriseWorkflowService {
// always only be one owner.
.map((project) =>
this.sharedWorkflowRepository.create({
workflowId: workflow.id,
workflowId,
role: 'workflow:editor',
projectId: project.id,
}),
@ -118,7 +118,7 @@ export class EnterpriseWorkflowService {
}
validateCredentialPermissionsToUser(
workflow: WorkflowEntity,
workflow: IWorkflowBase,
allowedCredentials: CredentialsEntity[],
) {
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 });
if (!previousVersion) {
@ -162,9 +162,9 @@ export class EnterpriseWorkflowService {
}
}
validateWorkflowCredentialUsage(
newWorkflowVersion: WorkflowEntity,
previousWorkflowVersion: WorkflowEntity,
validateWorkflowCredentialUsage<T extends IWorkflowBase>(
newWorkflowVersion: T,
previousWorkflowVersion: IWorkflowBase,
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. */
getNodesWithInaccessibleCreds(workflow: WorkflowEntity, userCredIds: string[]) {
getNodesWithInaccessibleCreds(workflow: IWorkflowBase, userCredIds: string[]) {
if (!workflow.nodes) {
return [];
}

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
import { Container } from '@n8n/di';
import type { IWorkflowBase } from 'n8n-workflow';
import type { TestDefinition } from '@/databases/entities/test-definition.ee';
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 { TestMetricRepository } from '@/databases/repositories/test-metric.repository.ee';
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';
let authOwnerAgent: SuperAgentTest;
let workflowUnderTest: WorkflowEntity;
let otherWorkflow: WorkflowEntity;
let workflowUnderTest: IWorkflowBase;
let otherWorkflow: IWorkflowBase;
let testDefinition: TestDefinition;
let otherTestDefinition: TestDefinition;
let ownerShell: User;

View file

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

View file

@ -1,9 +1,9 @@
import { Container } from '@n8n/di';
import { mockInstance } from 'n8n-core/test/utils';
import type { IWorkflowBase } from 'n8n-workflow';
import type { TestDefinition } from '@/databases/entities/test-definition.ee';
import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { TestDefinitionRepository } from '@/databases/repositories/test-definition.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';
let authOwnerAgent: SuperAgentTest;
let workflowUnderTest: WorkflowEntity;
let otherWorkflow: WorkflowEntity;
let workflowUnderTest: IWorkflowBase;
let otherWorkflow: IWorkflowBase;
let testDefinition: TestDefinition;
let otherTestDefinition: TestDefinition;
let ownerShell: User;

View file

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

View file

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

View file

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

View file

@ -1,14 +1,14 @@
import { Container } from '@n8n/di';
import type { IWorkflowBase } from 'n8n-workflow';
import type { TagEntity } from '@/databases/entities/tag-entity';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { TagRepository } from '@/databases/repositories/tag.repository';
import { WorkflowTagMappingRepository } from '@/databases/repositories/workflow-tag-mapping.repository';
import { generateNanoId } from '@/databases/utils/generators';
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 tag = await Container.get(TagRepository).save({

View file

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

View file

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

View file

@ -1,11 +1,10 @@
import { Container } from '@n8n/di';
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 { ActiveWorkflowManager } from '@/active-workflow-manager';
import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
@ -519,7 +518,7 @@ describe('GET /workflows', () => {
expect(response.statusCode).toBe(200);
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 wf2 = workflows.find((w) => w.id === savedWorkflow2.id)!;
@ -546,7 +545,7 @@ describe('GET /workflows', () => {
expect(response.statusCode).toBe(200);
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 wf2 = workflows.find((w) => w.id === savedWorkflow2.id)!;
@ -579,7 +578,7 @@ describe('GET /workflows', () => {
expect(response.statusCode).toBe(200);
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 wf2 = workflows.find((w) => w.id === savedWorkflow2.id)!;
@ -1124,7 +1123,7 @@ describe('PATCH /workflows/:workflowId', () => {
describe('POST /workflows/:workflowId/run', () => {
let sharingSpy: jest.SpyInstance;
let tamperingSpy: jest.SpyInstance;
let workflow: WorkflowEntity;
let workflow: IWorkflowBase;
beforeAll(() => {
const enterpriseWorkflowService = Container.get(EnterpriseWorkflowService);

View file

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