mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-01 05:38:33 +08:00
feat(API): Add publishIfActive option to workflow update endpoint (#35813)
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user