feat(API): Add publishIfActive option to workflow update endpoint (#35813)

This commit is contained in:
Denisf88
2026-08-07 12:04:19 +02:00
committed by GitHub
parent 98e0952c36
commit 0e22fdfbc1
4 changed files with 79 additions and 3 deletions
+1 -1
View File
@@ -148,7 +148,7 @@ export declare namespace WorkflowRequest {
{ id: string },
{},
WorkflowEntity & { parentFolderId?: string | null },
{}
{ publishIfActive?: boolean }
>;
type Activate = AuthenticatedRequest<
{ id: string },
@@ -54,9 +54,22 @@ put:
tags:
- Workflow
summary: Update a workflow
description: Update a workflow. If the workflow is published, the updated version will be automatically re-published.
description: >-
Update a workflow. If the workflow is published, the updated version will
be automatically re-published unless `publishIfActive` is set to `false`.
parameters:
- $ref: '../schemas/parameters/workflowId.yml'
- name: publishIfActive
in: query
required: false
description: >-
Whether to publish the update if the workflow is currently published.
Set to `false` to save the change as a draft on the existing published
version instead of releasing it. Has no effect on a workflow that
isn't currently published.
schema:
type: boolean
default: true
requestBody:
description: Updated workflow object.
content:
@@ -294,6 +294,10 @@ const workflowHandlers: WorkflowHandlers = {
// null moves the workflow to the project root, (undefined) leaves the current folder untouched
const resolvedParentFolderId = parentFolderId === null ? PROJECT_ROOT : parentFolderId;
// Defaults to true so existing integrations keep publishing on save; callers that want
// to stage a change on an already-published workflow can opt out explicitly.
const { publishIfActive = true } = req.query;
// binaryMode and credentialResolverId are derived, internal settings
// rather than something users are expected to control programmatically;
// strip them so the settings merge in WorkflowService.update preserves
@@ -315,7 +319,7 @@ const workflowHandlers: WorkflowHandlers = {
parentFolderId: resolvedParentFolderId,
forceSave: true, // Skip version conflict check for public API
publicApi: true,
publishIfActive: true,
publishIfActive,
source: 'api',
},
);
@@ -2574,6 +2574,65 @@ describe('PUT /workflows/:id', () => {
expect(versionInTheDb!.nodes).toEqual(updatedPayload.nodes);
});
test('should save as draft without republishing when publishIfActive=false', async () => {
const workflow = await createActiveWorkflow({}, member);
const updatedPayload = {
name: 'Updated active workflow',
nodes: [
{
id: 'uuid-updated',
parameters: { triggerTimes: { item: [{ mode: 'everyMinute' }] } },
name: 'Updated Cron',
type: 'n8n-nodes-base.cron',
typeVersion: 1,
position: [300, 400],
},
],
connections: {},
staticData: workflow.staticData,
settings: workflow.settings,
};
const updateResponse = await authMemberAgent
.put(`/workflows/${workflow.id}?publishIfActive=false`)
.send(updatedPayload);
expect(updateResponse.statusCode).toBe(200);
expect(updateResponse.body.active).toBe(true);
expect(updateResponse.body.activeVersionId).toBe(workflow.versionId);
expect(updateResponse.body.nodes).toEqual(updatedPayload.nodes);
const versionInTheDb = await Container.get(WorkflowHistoryRepository).findOne({
where: {
workflowId: workflow.id,
versionId: Not(workflow.versionId),
},
});
expect(versionInTheDb).not.toBeNull();
expect(versionInTheDb!.nodes).toEqual(updatedPayload.nodes);
const sharedWorkflow = await Container.get(SharedWorkflowRepository).findOne({
where: {
projectId: memberPersonalProject.id,
workflowId: workflow.id,
},
relations: ['workflow'],
});
// The published version itself is untouched: its history record still has the old content.
expect(sharedWorkflow?.workflow.activeVersionId).toBe(workflow.versionId);
const activeVersionInTheDb = await Container.get(WorkflowHistoryRepository).findOne({
where: {
workflowId: workflow.id,
versionId: workflow.versionId,
},
});
expect(activeVersionInTheDb!.nodes).toEqual(workflow.nodes);
expect(activeVersionInTheDb!.nodes).not.toEqual(updatedPayload.nodes);
});
test('should not allow updating active field', async () => {
const workflow = await createWorkflowWithTriggerAndHistory({}, member);