mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor(core): Use IWorkflowBase over WorkflowEntity in most places (#13225)
This commit is contained in:
parent
11cf1cd23a
commit
f001edb2a2
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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[]> {
|
||||||
|
|
|
@ -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})`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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++) {
|
||||||
|
|
|
@ -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',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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] = {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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: {},
|
||||||
|
|
|
@ -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,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -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[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }));
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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) =>
|
||||||
|
|
|
@ -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
|
||||||
|
|
8
packages/cli/src/workflows/workflow.formatter.ts
Normal file
8
packages/cli/src/workflows/workflow.formatter.ts
Normal 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})`;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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'] });
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue