mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(core): Update endpoint to update a workflow, to support updating the workflow parent folder (no-chagelog) (#13906)
This commit is contained in:
@@ -5,6 +5,7 @@ import type { Scope } from '@n8n/permissions';
|
|||||||
import type { EntityManager } from '@n8n/typeorm';
|
import type { EntityManager } from '@n8n/typeorm';
|
||||||
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
||||||
import { In } from '@n8n/typeorm';
|
import { In } from '@n8n/typeorm';
|
||||||
|
import type { QueryDeepPartialEntity } from '@n8n/typeorm/query-builder/QueryPartialEntity';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import { BinaryDataService, Logger } from 'n8n-core';
|
import { BinaryDataService, Logger } from 'n8n-core';
|
||||||
@@ -27,6 +28,7 @@ import { EventService } from '@/events/event.service';
|
|||||||
import { ExternalHooks } from '@/external-hooks';
|
import { ExternalHooks } from '@/external-hooks';
|
||||||
import { validateEntity } from '@/generic-helpers';
|
import { validateEntity } from '@/generic-helpers';
|
||||||
import { hasSharing, type ListQuery } from '@/requests';
|
import { hasSharing, type ListQuery } from '@/requests';
|
||||||
|
import { FolderService } from '@/services/folder.service';
|
||||||
import { OrchestrationService } from '@/services/orchestration.service';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { OwnershipService } from '@/services/ownership.service';
|
import { OwnershipService } from '@/services/ownership.service';
|
||||||
import { ProjectService } from '@/services/project.service.ee';
|
import { ProjectService } from '@/services/project.service.ee';
|
||||||
@@ -57,6 +59,7 @@ export class WorkflowService {
|
|||||||
private readonly executionRepository: ExecutionRepository,
|
private readonly executionRepository: ExecutionRepository,
|
||||||
private readonly eventService: EventService,
|
private readonly eventService: EventService,
|
||||||
private readonly globalConfig: GlobalConfig,
|
private readonly globalConfig: GlobalConfig,
|
||||||
|
private readonly folderService: FolderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getMany(
|
async getMany(
|
||||||
@@ -179,6 +182,7 @@ export class WorkflowService {
|
|||||||
workflowUpdateData: WorkflowEntity,
|
workflowUpdateData: WorkflowEntity,
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
tagIds?: string[],
|
tagIds?: string[],
|
||||||
|
parentFolderId?: string,
|
||||||
forceSave?: boolean,
|
forceSave?: boolean,
|
||||||
): Promise<WorkflowEntity> {
|
): Promise<WorkflowEntity> {
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
||||||
@@ -263,20 +267,25 @@ export class WorkflowService {
|
|||||||
await validateEntity(workflowUpdateData);
|
await validateEntity(workflowUpdateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.workflowRepository.update(
|
const updatePayload: QueryDeepPartialEntity<WorkflowEntity> = pick(workflowUpdateData, [
|
||||||
workflowId,
|
'name',
|
||||||
pick(workflowUpdateData, [
|
'active',
|
||||||
'name',
|
'nodes',
|
||||||
'active',
|
'connections',
|
||||||
'nodes',
|
'meta',
|
||||||
'connections',
|
'settings',
|
||||||
'meta',
|
'staticData',
|
||||||
'settings',
|
'pinData',
|
||||||
'staticData',
|
'versionId',
|
||||||
'pinData',
|
]);
|
||||||
'versionId',
|
|
||||||
]),
|
if (parentFolderId) {
|
||||||
);
|
const project = await this.sharedWorkflowRepository.getWorkflowOwningProject(workflow.id);
|
||||||
|
await this.folderService.findFolderInProjectOrFail(parentFolderId, project?.id ?? '');
|
||||||
|
updatePayload.parentFolder = { id: parentFolderId };
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.workflowRepository.update(workflowId, updatePayload);
|
||||||
|
|
||||||
const tagsDisabled = this.globalConfig.tags.disabled;
|
const tagsDisabled = this.globalConfig.tags.disabled;
|
||||||
|
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ export class WorkflowsController {
|
|||||||
const forceSave = req.query.forceSave === 'true';
|
const forceSave = req.query.forceSave === 'true';
|
||||||
|
|
||||||
let updateData = new WorkflowEntity();
|
let updateData = new WorkflowEntity();
|
||||||
const { tags, ...rest } = req.body;
|
const { tags, parentFolderId, ...rest } = req.body;
|
||||||
Object.assign(updateData, rest);
|
Object.assign(updateData, rest);
|
||||||
|
|
||||||
const isSharingEnabled = this.license.isSharingEnabled();
|
const isSharingEnabled = this.license.isSharingEnabled();
|
||||||
@@ -366,6 +366,7 @@ export class WorkflowsController {
|
|||||||
updateData,
|
updateData,
|
||||||
workflowId,
|
workflowId,
|
||||||
tags,
|
tags,
|
||||||
|
parentFolderId,
|
||||||
isSharingEnabled ? forceSave : true,
|
isSharingEnabled ? forceSave : true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ beforeAll(async () => {
|
|||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
|
mock(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2044,6 +2044,48 @@ describe('PATCH /workflows/:workflowId', () => {
|
|||||||
expect(updatedWorkflow.id).toBe(workflow.id);
|
expect(updatedWorkflow.id).toBe(workflow.id);
|
||||||
expect(updatedWorkflow.meta).toEqual(payload.meta);
|
expect(updatedWorkflow.meta).toEqual(payload.meta);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should update workflow parent folder', async () => {
|
||||||
|
const ownerPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
|
||||||
|
const folder1 = await createFolder(ownerPersonalProject, { name: 'folder1' });
|
||||||
|
|
||||||
|
const workflow = await createWorkflow({}, owner);
|
||||||
|
const payload = {
|
||||||
|
versionId: workflow.versionId,
|
||||||
|
parentFolderId: folder1.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.patch(`/workflows/${workflow.id}`).send(payload);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
|
||||||
|
const updatedWorkflow = await Container.get(WorkflowRepository).findOneOrFail({
|
||||||
|
where: { id: workflow.id },
|
||||||
|
relations: ['parentFolder'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedWorkflow.parentFolder?.id).toBe(folder1.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail if trying update workflow parent folder with a folder that does not belong to project', async () => {
|
||||||
|
const ownerPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
|
||||||
|
const memberPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(
|
||||||
|
member.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
await createFolder(ownerPersonalProject, { name: 'folder1' });
|
||||||
|
const folder2 = await createFolder(memberPersonalProject, { name: 'folder2' });
|
||||||
|
|
||||||
|
const workflow = await createWorkflow({}, owner);
|
||||||
|
const payload = {
|
||||||
|
versionId: workflow.versionId,
|
||||||
|
parentFolderId: folder2.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await authOwnerAgent.patch(`/workflows/${workflow.id}`).send(payload);
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(500);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /workflows/:workflowId/run', () => {
|
describe('POST /workflows/:workflowId/run', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user