2023-12-18 07:10:30 -08:00
|
|
|
import Container, { Service } from 'typedi';
|
2023-12-21 08:37:08 -08:00
|
|
|
import type { INode, IPinData } from 'n8n-workflow';
|
|
|
|
import { NodeApiError, Workflow } from 'n8n-workflow';
|
2023-06-16 07:26:35 -07:00
|
|
|
import pick from 'lodash/pick';
|
2023-12-13 03:41:06 -08:00
|
|
|
import omit from 'lodash/omit';
|
2022-12-06 00:25:39 -08:00
|
|
|
import { v4 as uuid } from 'uuid';
|
2023-02-21 10:21:56 -08:00
|
|
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
2022-11-09 06:25:00 -08:00
|
|
|
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
|
|
|
import config from '@/config';
|
2023-01-27 05:56:56 -08:00
|
|
|
import type { User } from '@db/entities/User';
|
|
|
|
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
2022-11-09 06:25:00 -08:00
|
|
|
import { validateEntity } from '@/GenericHelpers';
|
2022-12-21 01:46:26 -08:00
|
|
|
import { ExternalHooks } from '@/ExternalHooks';
|
2024-01-08 03:54:23 -08:00
|
|
|
import { hasSharing, type ListQuery } from '@/requests';
|
|
|
|
import type { WorkflowRequest } from '@/workflows/workflow.request';
|
2023-08-22 03:24:43 -07:00
|
|
|
import { TagService } from '@/services/tag.service';
|
2023-01-02 08:42:32 -08:00
|
|
|
import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces';
|
2022-11-11 02:14:45 -08:00
|
|
|
import { NodeTypes } from '@/NodeTypes';
|
|
|
|
import { WorkflowRunner } from '@/WorkflowRunner';
|
|
|
|
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
2023-02-21 10:21:56 -08:00
|
|
|
import { TestWebhooks } from '@/TestWebhooks';
|
|
|
|
import { InternalHooks } from '@/InternalHooks';
|
2023-11-10 06:04:26 -08:00
|
|
|
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
2023-08-22 04:19:37 -07:00
|
|
|
import { OwnershipService } from '@/services/ownership.service';
|
2023-09-27 07:22:39 -07:00
|
|
|
import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee';
|
2023-10-10 01:06:06 -07:00
|
|
|
import { BinaryDataService } from 'n8n-core';
|
2023-10-25 07:35:22 -07:00
|
|
|
import { Logger } from '@/Logger';
|
2023-11-17 06:58:50 -08:00
|
|
|
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
2023-11-10 06:04:26 -08:00
|
|
|
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
|
|
|
import { WorkflowTagMappingRepository } from '@db/repositories/workflowTagMapping.repository';
|
|
|
|
import { ExecutionRepository } from '@db/repositories/execution.repository';
|
2023-11-28 01:19:27 -08:00
|
|
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
|
|
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
2022-10-11 05:55:05 -07:00
|
|
|
|
2023-12-15 03:59:56 -08:00
|
|
|
@Service()
|
|
|
|
export class WorkflowService {
|
2023-12-18 07:10:30 -08:00
|
|
|
constructor(
|
|
|
|
private readonly logger: Logger,
|
|
|
|
private readonly executionRepository: ExecutionRepository,
|
|
|
|
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
|
|
|
private readonly workflowRepository: WorkflowRepository,
|
|
|
|
private readonly workflowTagMappingRepository: WorkflowTagMappingRepository,
|
|
|
|
private readonly binaryDataService: BinaryDataService,
|
|
|
|
private readonly ownershipService: OwnershipService,
|
|
|
|
private readonly tagService: TagService,
|
|
|
|
private readonly workflowHistoryService: WorkflowHistoryService,
|
|
|
|
private readonly multiMainSetup: MultiMainSetup,
|
|
|
|
private readonly nodeTypes: NodeTypes,
|
|
|
|
private readonly testWebhooks: TestWebhooks,
|
|
|
|
private readonly externalHooks: ExternalHooks,
|
|
|
|
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
|
|
|
|
) {}
|
|
|
|
|
2022-11-10 05:03:14 -08:00
|
|
|
/**
|
|
|
|
* Find the pinned trigger to execute the workflow from, if any.
|
2022-12-05 01:09:31 -08:00
|
|
|
*
|
|
|
|
* - In a full execution, select the _first_ pinned trigger.
|
|
|
|
* - In a partial execution,
|
|
|
|
* - select the _first_ pinned trigger that leads to the executed node,
|
|
|
|
* - else select the executed pinned trigger.
|
2022-11-10 05:03:14 -08:00
|
|
|
*/
|
2023-12-15 03:59:56 -08:00
|
|
|
findPinnedTrigger(workflow: IWorkflowDb, startNodes?: string[], pinData?: IPinData) {
|
2022-11-10 05:03:14 -08:00
|
|
|
if (!pinData || !startNodes) return null;
|
|
|
|
|
|
|
|
const isTrigger = (nodeTypeName: string) =>
|
|
|
|
['trigger', 'webhook'].some((suffix) => nodeTypeName.toLowerCase().includes(suffix));
|
|
|
|
|
|
|
|
const pinnedTriggers = workflow.nodes.filter(
|
|
|
|
(node) => !node.disabled && pinData[node.name] && isTrigger(node.type),
|
|
|
|
);
|
|
|
|
|
|
|
|
if (pinnedTriggers.length === 0) return null;
|
|
|
|
|
|
|
|
if (startNodes?.length === 0) return pinnedTriggers[0]; // full execution
|
|
|
|
|
|
|
|
const [startNodeName] = startNodes;
|
|
|
|
|
2022-12-05 01:09:31 -08:00
|
|
|
const parentNames = new Workflow({
|
|
|
|
nodes: workflow.nodes,
|
|
|
|
connections: workflow.connections,
|
|
|
|
active: workflow.active,
|
2023-12-18 07:10:30 -08:00
|
|
|
nodeTypes: this.nodeTypes,
|
2022-12-05 01:09:31 -08:00
|
|
|
}).getParentNodes(startNodeName);
|
|
|
|
|
|
|
|
let checkNodeName = '';
|
|
|
|
|
|
|
|
if (parentNames.length === 0) {
|
|
|
|
checkNodeName = startNodeName;
|
|
|
|
} else {
|
|
|
|
checkNodeName = parentNames.find((pn) => pn === pinnedTriggers[0].name) as string;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pinnedTriggers.find((pt) => pt.name === checkNodeName) ?? null; // partial execution
|
2022-11-10 05:03:14 -08:00
|
|
|
}
|
|
|
|
|
2023-12-15 03:59:56 -08:00
|
|
|
async getMany(sharedWorkflowIds: string[], options?: ListQuery.Options) {
|
2023-12-22 04:35:23 -08:00
|
|
|
const { workflows, count } = await this.workflowRepository.getMany(sharedWorkflowIds, options);
|
2023-08-22 04:19:37 -07:00
|
|
|
|
2023-12-05 01:11:18 -08:00
|
|
|
return hasSharing(workflows)
|
|
|
|
? {
|
2023-12-18 07:10:30 -08:00
|
|
|
workflows: workflows.map((w) => this.ownershipService.addOwnedByAndSharedWith(w)),
|
2023-12-05 01:11:18 -08:00
|
|
|
count,
|
|
|
|
}
|
|
|
|
: { workflows, count };
|
2022-11-08 08:52:42 -08:00
|
|
|
}
|
|
|
|
|
2023-12-15 03:59:56 -08:00
|
|
|
async update(
|
2022-10-26 06:49:43 -07:00
|
|
|
user: User,
|
|
|
|
workflow: WorkflowEntity,
|
|
|
|
workflowId: string,
|
2023-03-30 07:25:51 -07:00
|
|
|
tagIds?: string[],
|
2022-10-31 02:35:24 -07:00
|
|
|
forceSave?: boolean,
|
2022-11-18 04:07:39 -08:00
|
|
|
roles?: string[],
|
2022-10-26 06:49:43 -07:00
|
|
|
): Promise<WorkflowEntity> {
|
2023-12-28 04:14:10 -08:00
|
|
|
const shared = await this.sharedWorkflowRepository.findSharing(
|
|
|
|
workflowId,
|
|
|
|
user,
|
|
|
|
'workflow:update',
|
|
|
|
{ roles },
|
|
|
|
);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
|
|
|
if (!shared) {
|
2023-12-18 07:10:30 -08:00
|
|
|
this.logger.verbose('User attempted to update a workflow without permissions', {
|
2022-10-26 06:49:43 -07:00
|
|
|
workflowId,
|
|
|
|
userId: user.id,
|
|
|
|
});
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new NotFoundError(
|
2022-11-22 04:05:51 -08:00
|
|
|
'You do not have permission to update this workflow. Ask the owner to share it with you.',
|
2022-10-26 06:49:43 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-11-17 06:58:50 -08:00
|
|
|
const oldState = shared.workflow.active;
|
|
|
|
|
2022-12-06 00:25:39 -08:00
|
|
|
if (
|
|
|
|
!forceSave &&
|
|
|
|
workflow.versionId !== '' &&
|
|
|
|
workflow.versionId !== shared.workflow.versionId
|
|
|
|
) {
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError(
|
2022-11-28 12:05:19 -08:00
|
|
|
'Your most recent changes may be lost, because someone else just updated this workflow. Open this workflow in a new tab to see those new updates.',
|
2022-12-06 00:25:39 -08:00
|
|
|
100,
|
2022-11-14 06:38:19 -08:00
|
|
|
);
|
|
|
|
}
|
2022-10-31 02:35:24 -07:00
|
|
|
|
2023-12-13 03:41:06 -08:00
|
|
|
if (Object.keys(omit(workflow, ['id', 'versionId', 'active'])).length > 0) {
|
|
|
|
// Update the workflow's version when changing properties such as
|
|
|
|
// `name`, `pinData`, `nodes`, `connections`, `settings` or `tags`
|
2023-07-26 00:25:01 -07:00
|
|
|
workflow.versionId = uuid();
|
2023-12-18 07:10:30 -08:00
|
|
|
this.logger.verbose(
|
2023-07-26 00:25:01 -07:00
|
|
|
`Updating versionId for workflow ${workflowId} for user ${user.id} after saving`,
|
|
|
|
{
|
|
|
|
previousVersionId: shared.workflow.versionId,
|
|
|
|
newVersionId: workflow.versionId,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2022-12-06 00:25:39 -08:00
|
|
|
|
2022-10-26 06:49:43 -07:00
|
|
|
// check credentials for old format
|
|
|
|
await WorkflowHelpers.replaceInvalidCredentials(workflow);
|
|
|
|
|
|
|
|
WorkflowHelpers.addNodeIds(workflow);
|
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.externalHooks.run('workflow.update', [workflow]);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2023-11-17 06:58:50 -08:00
|
|
|
/**
|
|
|
|
* If the workflow being updated is stored as `active`, remove it from
|
|
|
|
* active workflows in memory, and re-add it after the update.
|
|
|
|
*
|
|
|
|
* If a trigger or poller in the workflow was updated, the new value
|
|
|
|
* will take effect only on removing and re-adding.
|
|
|
|
*/
|
2022-10-26 06:49:43 -07:00
|
|
|
if (shared.workflow.active) {
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.activeWorkflowRunner.remove(workflowId);
|
2022-10-26 06:49:43 -07:00
|
|
|
}
|
|
|
|
|
2023-03-24 05:11:48 -07:00
|
|
|
const workflowSettings = workflow.settings ?? {};
|
|
|
|
|
|
|
|
const keysAllowingDefault = [
|
|
|
|
'timezone',
|
|
|
|
'saveDataErrorExecution',
|
|
|
|
'saveDataSuccessExecution',
|
|
|
|
'saveManualExecutions',
|
|
|
|
'saveExecutionProgress',
|
|
|
|
] as const;
|
|
|
|
for (const key of keysAllowingDefault) {
|
|
|
|
// Do not save the default value
|
|
|
|
if (workflowSettings[key] === 'DEFAULT') {
|
|
|
|
delete workflowSettings[key];
|
2022-10-26 06:49:43 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-24 05:11:48 -07:00
|
|
|
if (workflowSettings.executionTimeout === config.get('executions.timeout')) {
|
|
|
|
// Do not save when default got set
|
|
|
|
delete workflowSettings.executionTimeout;
|
|
|
|
}
|
|
|
|
|
2022-10-26 06:49:43 -07:00
|
|
|
if (workflow.name) {
|
|
|
|
workflow.updatedAt = new Date(); // required due to atomic update
|
|
|
|
await validateEntity(workflow);
|
|
|
|
}
|
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.workflowRepository.update(
|
2022-11-21 06:51:23 -08:00
|
|
|
workflowId,
|
|
|
|
pick(workflow, [
|
|
|
|
'name',
|
|
|
|
'active',
|
|
|
|
'nodes',
|
|
|
|
'connections',
|
2024-01-08 03:59:04 -08:00
|
|
|
'meta',
|
2022-11-21 06:51:23 -08:00
|
|
|
'settings',
|
|
|
|
'staticData',
|
|
|
|
'pinData',
|
2022-12-06 00:25:39 -08:00
|
|
|
'versionId',
|
2022-11-21 06:51:23 -08:00
|
|
|
]),
|
|
|
|
);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2023-03-30 07:25:51 -07:00
|
|
|
if (tagIds && !config.getEnv('workflowTagsDisabled')) {
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.workflowTagMappingRepository.delete({ workflowId });
|
|
|
|
await this.workflowTagMappingRepository.insert(
|
2023-03-30 07:25:51 -07:00
|
|
|
tagIds.map((tagId) => ({ tagId, workflowId })),
|
|
|
|
);
|
2022-10-26 06:49:43 -07:00
|
|
|
}
|
|
|
|
|
2023-12-13 03:41:06 -08:00
|
|
|
if (workflow.versionId !== shared.workflow.versionId) {
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.workflowHistoryService.saveVersion(user, workflow, workflowId);
|
2023-09-27 07:22:39 -07:00
|
|
|
}
|
|
|
|
|
2023-01-02 08:42:32 -08:00
|
|
|
const relations = config.getEnv('workflowTagsDisabled') ? [] : ['tags'];
|
2022-10-26 06:49:43 -07:00
|
|
|
|
|
|
|
// We sadly get nothing back from "update". Neither if it updated a record
|
|
|
|
// nor the new value. So query now the hopefully updated entry.
|
2023-12-18 07:10:30 -08:00
|
|
|
const updatedWorkflow = await this.workflowRepository.findOne({
|
2023-01-13 09:12:22 -08:00
|
|
|
where: { id: workflowId },
|
|
|
|
relations,
|
|
|
|
});
|
2022-10-26 06:49:43 -07:00
|
|
|
|
2023-01-13 09:12:22 -08:00
|
|
|
if (updatedWorkflow === null) {
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError(
|
2022-10-26 06:49:43 -07:00
|
|
|
`Workflow with ID "${workflowId}" could not be found to be updated.`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-03-30 07:25:51 -07:00
|
|
|
if (updatedWorkflow.tags?.length && tagIds?.length) {
|
2023-12-18 07:10:30 -08:00
|
|
|
updatedWorkflow.tags = this.tagService.sortByRequestOrder(updatedWorkflow.tags, {
|
2023-03-30 07:25:51 -07:00
|
|
|
requestOrder: tagIds,
|
2022-10-26 06:49:43 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.externalHooks.run('workflow.afterUpdate', [updatedWorkflow]);
|
2023-02-21 10:21:56 -08:00
|
|
|
void Container.get(InternalHooks).onWorkflowSaved(user, updatedWorkflow, false);
|
2022-10-26 06:49:43 -07:00
|
|
|
|
|
|
|
if (updatedWorkflow.active) {
|
|
|
|
// When the workflow is supposed to be active add it again
|
|
|
|
try {
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.externalHooks.run('workflow.activate', [updatedWorkflow]);
|
|
|
|
await this.activeWorkflowRunner.add(
|
2022-10-26 06:49:43 -07:00
|
|
|
workflowId,
|
|
|
|
shared.workflow.active ? 'update' : 'activate',
|
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
// If workflow could not be activated set it again to inactive
|
2023-02-20 03:22:27 -08:00
|
|
|
// and revert the versionId change so UI remains consistent
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.workflowRepository.update(workflowId, {
|
2023-02-20 03:22:27 -08:00
|
|
|
active: false,
|
|
|
|
versionId: shared.workflow.versionId,
|
|
|
|
});
|
2022-10-26 06:49:43 -07:00
|
|
|
|
|
|
|
// Also set it in the returned data
|
|
|
|
updatedWorkflow.active = false;
|
|
|
|
|
2023-02-01 16:00:24 -08:00
|
|
|
let message;
|
|
|
|
if (error instanceof NodeApiError) message = error.description;
|
|
|
|
message = message ?? (error as Error).message;
|
|
|
|
|
2022-10-26 06:49:43 -07:00
|
|
|
// Now return the original error for UI to display
|
2023-11-28 01:19:27 -08:00
|
|
|
throw new BadRequestError(message);
|
2022-10-26 06:49:43 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.multiMainSetup.init();
|
2023-11-17 06:58:50 -08:00
|
|
|
|
2023-12-27 07:55:01 -08:00
|
|
|
const newState = updatedWorkflow.active;
|
|
|
|
|
|
|
|
if (this.multiMainSetup.isEnabled && oldState !== newState) {
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.multiMainSetup.broadcastWorkflowActiveStateChanged({
|
2023-11-23 03:18:39 -08:00
|
|
|
workflowId,
|
|
|
|
oldState,
|
2023-12-27 07:55:01 -08:00
|
|
|
newState,
|
2023-11-23 03:18:39 -08:00
|
|
|
versionId: shared.workflow.versionId,
|
|
|
|
});
|
2023-11-17 06:58:50 -08:00
|
|
|
}
|
|
|
|
|
2022-10-26 06:49:43 -07:00
|
|
|
return updatedWorkflow;
|
|
|
|
}
|
2022-11-11 02:14:45 -08:00
|
|
|
|
2023-12-15 03:59:56 -08:00
|
|
|
async runManually(
|
2022-11-11 02:14:45 -08:00
|
|
|
{
|
|
|
|
workflowData,
|
|
|
|
runData,
|
|
|
|
pinData,
|
|
|
|
startNodes,
|
|
|
|
destinationNode,
|
|
|
|
}: WorkflowRequest.ManualRunPayload,
|
|
|
|
user: User,
|
|
|
|
sessionId?: string,
|
|
|
|
) {
|
2023-12-15 03:59:56 -08:00
|
|
|
const pinnedTrigger = this.findPinnedTrigger(workflowData, startNodes, pinData);
|
2022-11-11 02:14:45 -08:00
|
|
|
|
|
|
|
// If webhooks nodes exist and are active we have to wait for till we receive a call
|
|
|
|
if (
|
|
|
|
pinnedTrigger === null &&
|
|
|
|
(runData === undefined ||
|
|
|
|
startNodes === undefined ||
|
|
|
|
startNodes.length === 0 ||
|
|
|
|
destinationNode === undefined)
|
|
|
|
) {
|
|
|
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(user.id);
|
|
|
|
|
2023-12-19 08:32:02 -08:00
|
|
|
const needsWebhook = await this.testWebhooks.needsWebhook(
|
2024-01-09 07:02:32 -08:00
|
|
|
user.id,
|
2022-11-11 02:14:45 -08:00
|
|
|
workflowData,
|
|
|
|
additionalData,
|
2024-01-11 05:01:07 -08:00
|
|
|
runData,
|
2022-11-11 02:14:45 -08:00
|
|
|
sessionId,
|
|
|
|
destinationNode,
|
|
|
|
);
|
2023-12-19 08:32:02 -08:00
|
|
|
|
|
|
|
if (needsWebhook) return { waitingForWebhook: true };
|
2022-11-11 02:14:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// For manual testing always set to not active
|
|
|
|
workflowData.active = false;
|
|
|
|
|
|
|
|
// Start the workflow
|
|
|
|
const data: IWorkflowExecutionDataProcess = {
|
|
|
|
destinationNode,
|
feat(core): Cache test webhook registrations (#8176)
In a multi-main setup, we have the following issue. The user's client
connects to main A and runs a test webhook, so main A starts listening
for a webhook call. A third-party service sends a request to the test
webhook URL. The request is forwarded by the load balancer to main B,
who is not listening for this webhook call. Therefore, the webhook call
is unhandled.
To start addressing this, cache test webhook registrations, using Redis
for queue mode and in-memory for regular mode. When the third-party
service sends a request to the test webhook URL, the request is
forwarded by the load balancer to main B, who fetches test webhooks from
the cache and, if it finds a match, executes the test webhook. This
should be transparent - test webhook behavior should remain the same as
so far.
Notes:
- Test webhook timeouts are not cached. A timeout is only relevant to
the process it was created in, so another process retrieving from Redis
a "foreign" timeout will be unable to act on it. A timeout also has
circular references, so `cache-manager-ioredis-yet` is unable to
serialize it.
- In a single-main scenario, the timeout remains in the single process
and is cleared on test webhook expiration, successful execution, and
manual cancellation - all as usual.
- In a multi-main scenario, we will need to have the process who
received the webhook call send a message to the process who created the
webhook directing this originating process to clear the timeout. This
will likely be implemented via execution lifecycle hooks and Redis
channel messages checking session ID. This implementation is out of
scope for this PR and will come next.
- Additional data in test webhooks is not cached. From what I can tell,
additional data is not needed for test webhooks to be executed.
Additional data also has circular references, so
`cache-manager-ioredis-yet` is unable to serialize it.
Follow-up to: #8155
2024-01-03 07:58:33 -08:00
|
|
|
executionMode: 'manual',
|
2022-11-11 02:14:45 -08:00
|
|
|
runData,
|
|
|
|
pinData,
|
|
|
|
sessionId,
|
|
|
|
startNodes,
|
|
|
|
workflowData,
|
|
|
|
userId: user.id,
|
|
|
|
};
|
|
|
|
|
|
|
|
const hasRunData = (node: INode) => runData !== undefined && !!runData[node.name];
|
|
|
|
|
|
|
|
if (pinnedTrigger && !hasRunData(pinnedTrigger)) {
|
|
|
|
data.startNodes = [pinnedTrigger.name];
|
|
|
|
}
|
|
|
|
|
|
|
|
const workflowRunner = new WorkflowRunner();
|
|
|
|
const executionId = await workflowRunner.run(data);
|
|
|
|
|
|
|
|
return {
|
|
|
|
executionId,
|
|
|
|
};
|
|
|
|
}
|
2023-01-10 00:23:44 -08:00
|
|
|
|
2023-12-15 03:59:56 -08:00
|
|
|
async delete(user: User, workflowId: string): Promise<WorkflowEntity | undefined> {
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.externalHooks.run('workflow.delete', [workflowId]);
|
2023-01-10 00:23:44 -08:00
|
|
|
|
2023-12-28 04:14:10 -08:00
|
|
|
const sharedWorkflow = await this.sharedWorkflowRepository.findSharing(
|
|
|
|
workflowId,
|
|
|
|
user,
|
|
|
|
'workflow:delete',
|
|
|
|
{ roles: ['owner'] },
|
|
|
|
);
|
2023-01-10 00:23:44 -08:00
|
|
|
|
|
|
|
if (!sharedWorkflow) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sharedWorkflow.workflow.active) {
|
|
|
|
// deactivate before deleting
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.activeWorkflowRunner.remove(workflowId);
|
2023-01-10 00:23:44 -08:00
|
|
|
}
|
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
const idsForDeletion = await this.executionRepository
|
2023-11-10 06:04:26 -08:00
|
|
|
.find({
|
|
|
|
select: ['id'],
|
|
|
|
where: { workflowId },
|
|
|
|
})
|
|
|
|
.then((rows) => rows.map(({ id: executionId }) => ({ workflowId, executionId })));
|
2023-10-10 01:06:06 -07:00
|
|
|
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.workflowRepository.delete(workflowId);
|
|
|
|
await this.binaryDataService.deleteMany(idsForDeletion);
|
2023-01-10 00:23:44 -08:00
|
|
|
|
2023-02-21 10:21:56 -08:00
|
|
|
void Container.get(InternalHooks).onWorkflowDeleted(user, workflowId, false);
|
2023-12-18 07:10:30 -08:00
|
|
|
await this.externalHooks.run('workflow.afterDelete', [workflowId]);
|
2023-01-10 00:23:44 -08:00
|
|
|
|
|
|
|
return sharedWorkflow.workflow;
|
|
|
|
}
|
2022-10-11 05:55:05 -07:00
|
|
|
}
|