mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat(core): Allow setting folder destination when transferring workflow ownership (#14935)
This commit is contained in:
@@ -4,4 +4,5 @@ import { Z } from 'zod-class';
|
|||||||
export class TransferWorkflowBodyDto extends Z.class({
|
export class TransferWorkflowBodyDto extends Z.class({
|
||||||
destinationProjectId: z.string(),
|
destinationProjectId: z.string(),
|
||||||
shareCredentials: z.array(z.string()).optional(),
|
shareCredentials: z.array(z.string()).optional(),
|
||||||
|
destinationParentFolderId: z.string().optional(),
|
||||||
}) {}
|
}) {}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { WorkflowRepository } from '@/databases/repositories/workflow.repository
|
|||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
import { TransferWorkflowError } from '@/errors/response-errors/transfer-workflow.error';
|
import { TransferWorkflowError } from '@/errors/response-errors/transfer-workflow.error';
|
||||||
|
import { FolderService } from '@/services/folder.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';
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ export class EnterpriseWorkflowService {
|
|||||||
private readonly credentialsFinderService: CredentialsFinderService,
|
private readonly credentialsFinderService: CredentialsFinderService,
|
||||||
private readonly enterpriseCredentialsService: EnterpriseCredentialsService,
|
private readonly enterpriseCredentialsService: EnterpriseCredentialsService,
|
||||||
private readonly workflowFinderService: WorkflowFinderService,
|
private readonly workflowFinderService: WorkflowFinderService,
|
||||||
|
private readonly folderService: FolderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async shareWithProjects(
|
async shareWithProjects(
|
||||||
@@ -265,6 +267,7 @@ export class EnterpriseWorkflowService {
|
|||||||
workflowId: string,
|
workflowId: string,
|
||||||
destinationProjectId: string,
|
destinationProjectId: string,
|
||||||
shareCredentials: string[] = [],
|
shareCredentials: string[] = [],
|
||||||
|
destinationParentFolderId?: string,
|
||||||
) {
|
) {
|
||||||
// 1. get workflow
|
// 1. get workflow
|
||||||
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||||
@@ -303,6 +306,21 @@ export class EnterpriseWorkflowService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let parentFolder = null;
|
||||||
|
|
||||||
|
if (destinationParentFolderId) {
|
||||||
|
try {
|
||||||
|
parentFolder = await this.folderService.findFolderInProjectOrFail(
|
||||||
|
destinationParentFolderId,
|
||||||
|
destinationProjectId,
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
throw new TransferWorkflowError(
|
||||||
|
`The destination folder with id "${destinationParentFolderId}" does not exist in the project "${destinationProject.name}".`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 6. deactivate workflow if necessary
|
// 6. deactivate workflow if necessary
|
||||||
const wasActive = workflow.active;
|
const wasActive = workflow.active;
|
||||||
if (wasActive) {
|
if (wasActive) {
|
||||||
@@ -345,10 +363,10 @@ export class EnterpriseWorkflowService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 9. detach workflow from parent folder in source project
|
// 9. Move workflow to the right folder if any
|
||||||
await this.workflowRepository.update({ id: workflow.id }, { parentFolder: null });
|
await this.workflowRepository.update({ id: workflow.id }, { parentFolder });
|
||||||
|
|
||||||
// 9. try to activate it again if it was active
|
// 10. try to activate it again if it was active
|
||||||
if (wasActive) {
|
if (wasActive) {
|
||||||
try {
|
try {
|
||||||
await this.activeWorkflowManager.add(workflowId, 'update');
|
await this.activeWorkflowManager.add(workflowId, 'update');
|
||||||
|
|||||||
@@ -510,6 +510,7 @@ export class WorkflowsController {
|
|||||||
workflowId,
|
workflowId,
|
||||||
body.destinationProjectId,
|
body.destinationProjectId,
|
||||||
body.shareCredentials,
|
body.shareCredentials,
|
||||||
|
body.destinationParentFolderId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ describe('EnterpriseWorkflowService', () => {
|
|||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
|
mock(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1620,7 +1620,7 @@ describe('PUT /:workflowId/transfer', () => {
|
|||||||
expect(activeWorkflowManager.add).toHaveBeenCalledWith(workflow.id, 'update');
|
expect(activeWorkflowManager.add).toHaveBeenCalledWith(workflow.id, 'update');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should detach workflow from parent folder in source project', async () => {
|
test('should move workflow to project root if `destinationParentFolderId` is not provided', async () => {
|
||||||
//
|
//
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
//
|
//
|
||||||
@@ -1652,6 +1652,72 @@ describe('PUT /:workflowId/transfer', () => {
|
|||||||
expect(workflowFromDB.parentFolder).toBeNull();
|
expect(workflowFromDB.parentFolder).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should move workflow to the parent folder in source project if `destinationParentFolderId` is provided', async () => {
|
||||||
|
//
|
||||||
|
// ARRANGE
|
||||||
|
//
|
||||||
|
const destinationProject = await createTeamProject('Team Project', member);
|
||||||
|
|
||||||
|
const folder = await createFolder(destinationProject, { name: 'Test Folder' });
|
||||||
|
|
||||||
|
const workflow = await createWorkflow({ active: true, parentFolder: folder }, member);
|
||||||
|
|
||||||
|
//
|
||||||
|
// ACT
|
||||||
|
//
|
||||||
|
const response = await testServer
|
||||||
|
.authAgentFor(member)
|
||||||
|
.put(`/workflows/${workflow.id}/transfer`)
|
||||||
|
.send({ destinationProjectId: destinationProject.id, destinationParentFolderId: folder.id })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
//
|
||||||
|
// ASSERT
|
||||||
|
//
|
||||||
|
expect(response.body).toEqual({});
|
||||||
|
|
||||||
|
const workflowFromDB = await workflowRepository.findOneOrFail({
|
||||||
|
where: { id: workflow.id },
|
||||||
|
relations: ['parentFolder'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(workflowFromDB.parentFolder?.id).toBe(folder.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail destination parent folder does not exist in project', async () => {
|
||||||
|
//
|
||||||
|
// ARRANGE
|
||||||
|
//
|
||||||
|
const destinationProject = await createTeamProject('Team Project', member);
|
||||||
|
|
||||||
|
const anotherProject = await createTeamProject('Another Project', member);
|
||||||
|
|
||||||
|
const folderInDestinationProject = await createFolder(destinationProject, {
|
||||||
|
name: 'Test Folder',
|
||||||
|
});
|
||||||
|
|
||||||
|
const anotherFolder = await createFolder(destinationProject, {
|
||||||
|
name: 'Another Test Folder',
|
||||||
|
});
|
||||||
|
|
||||||
|
const workflow = await createWorkflow(
|
||||||
|
{ active: true, parentFolder: folderInDestinationProject },
|
||||||
|
member,
|
||||||
|
);
|
||||||
|
|
||||||
|
//
|
||||||
|
// ACT
|
||||||
|
//
|
||||||
|
await testServer
|
||||||
|
.authAgentFor(member)
|
||||||
|
.put(`/workflows/${workflow.id}/transfer`)
|
||||||
|
.send({
|
||||||
|
destinationProjectId: anotherProject.id,
|
||||||
|
destinationParentFolderId: anotherFolder.id,
|
||||||
|
})
|
||||||
|
.expect(400);
|
||||||
|
});
|
||||||
|
|
||||||
test('deactivates the workflow if it cannot be added to the active workflow manager again and returns the WorkflowActivationError as data', async () => {
|
test('deactivates the workflow if it cannot be added to the active workflow manager again and returns the WorkflowActivationError as data', async () => {
|
||||||
//
|
//
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
|
|||||||
Reference in New Issue
Block a user