Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Update PATCH /projects/:projectId/folders/:folderId to support tags (no-changelog) #13456

Merged
merged 47 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
410d0c8
firs commit create folders
RicardoE105 Feb 22, 2025
7edb3d2
Add endpoint to create folders
RicardoE105 Feb 23, 2025
e632cc5
fix typing issues
RicardoE105 Feb 23, 2025
db67264
fix import order
RicardoE105 Feb 23, 2025
bbc02f2
load folder.controller when bootstrapping the server
RicardoE105 Feb 23, 2025
3a3303d
fix test
RicardoE105 Feb 23, 2025
b1b4818
add folder creation logic to service
RicardoE105 Feb 23, 2025
72fa3f6
fix typing issues
RicardoE105 Feb 23, 2025
fa68941
add extra test to increase coverage
RicardoE105 Feb 23, 2025
92ea62c
update test
RicardoE105 Feb 23, 2025
da1a8cc
fix typing
RicardoE105 Feb 24, 2025
a2a12c4
asasas
RicardoE105 Feb 23, 2025
b8778c5
asas
RicardoE105 Feb 23, 2025
d55bba1
asas
RicardoE105 Feb 23, 2025
bfc8b4c
Add endpoint to return folder tree
RicardoE105 Feb 23, 2025
79158f7
sync master
RicardoE105 Feb 24, 2025
34abfa6
Update POST `workflows` to link folder
RicardoE105 Feb 23, 2025
7e1c077
fix imports
RicardoE105 Feb 23, 2025
a187042
fix issue with test in postgres
RicardoE105 Feb 24, 2025
2869f93
sync master
RicardoE105 Feb 24, 2025
5a086e4
fix test
RicardoE105 Feb 24, 2025
bdbd9d6
Add endpoint to update folder
RicardoE105 Feb 24, 2025
465fd37
remove unused import
RicardoE105 Feb 24, 2025
494f78a
remove .only in tests
RicardoE105 Feb 24, 2025
1b81c21
Update PATCH `/projects/:projectId/folders/:folderId` to support tags
RicardoE105 Feb 24, 2025
0a0da8d
fix import order
RicardoE105 Feb 24, 2025
9548350
delete children tables first
RicardoE105 Feb 25, 2025
327fb4a
update truncation order
RicardoE105 Feb 25, 2025
0f3bb42
update order
RicardoE105 Feb 25, 2025
d13ebd1
delete also users
RicardoE105 Feb 25, 2025
ac64e1f
small change
RicardoE105 Feb 25, 2025
8265455
asasas
RicardoE105 Feb 25, 2025
ba59ec6
add more debugging
RicardoE105 Feb 25, 2025
f8a4744
asas
RicardoE105 Feb 25, 2025
0a173c1
log data
RicardoE105 Feb 25, 2025
f167582
update message
RicardoE105 Feb 25, 2025
7a62467
more debug data
RicardoE105 Feb 25, 2025
dffa2d0
remove debugging code
RicardoE105 Feb 25, 2025
0f619b8
remove more debugging code
RicardoE105 Feb 25, 2025
c99b631
fix typing issue
RicardoE105 Feb 25, 2025
648565d
sync upstream
RicardoE105 Feb 25, 2025
8a1251c
async upstream
RicardoE105 Feb 25, 2025
2f8d17d
Merge branch 'ado-3168-be-add-endpoint-to-rename-folder' into ado-317…
RicardoE105 Feb 25, 2025
3c41f86
sync master
RicardoE105 Feb 25, 2025
781508f
Merge branch 'ado-3168-be-add-endpoint-to-rename-folder' into ado-317…
RicardoE105 Feb 25, 2025
f37e63c
sync upstream
RicardoE105 Feb 26, 2025
ba86ccd
remove duplicated test
RicardoE105 Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@ describe('UpdateFolderDto', () => {
describe('Valid requests', () => {
test.each([
{
name: 'name without parentId',
name: 'name',
request: {
name: 'test',
},
},
{
name: 'tagIds',
request: {
tagIds: ['1', '2'],
},
},
{
name: 'empty tagIds',
request: {
tagIds: [],
},
},
])('should validate $name', ({ request }) => {
const result = UpdateFolderDto.safeParse(request);
expect(result.success).toBe(true);
Expand All @@ -18,8 +30,10 @@ describe('UpdateFolderDto', () => {
describe('Invalid requests', () => {
test.each([
{
name: 'missing name',
request: {},
name: 'empty name',
request: {
name: '',
},
expectedErrorPath: ['name'],
},
{
Expand All @@ -29,13 +43,27 @@ describe('UpdateFolderDto', () => {
},
expectedErrorPath: ['name'],
},
{
name: 'non string tagIds',
request: {
tagIds: [0],
},
expectedErrorPath: ['tagIds'],
},
{
name: 'non array tagIds',
request: {
tagIds: 0,
},
expectedErrorPath: ['tagIds'],
},
])('should fail validation for $name', ({ request, expectedErrorPath }) => {
const result = UpdateFolderDto.safeParse(request);

expect(result.success).toBe(false);

if (expectedErrorPath) {
expect(result.error?.issues[0].path).toEqual(expectedErrorPath);
expect(result.error?.issues[0].path[0]).toEqual(expectedErrorPath[0]);
}
});
});
Expand Down
5 changes: 3 additions & 2 deletions packages/@n8n/api-types/src/dto/folders/update-folder.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { z } from 'zod';
import { Z } from 'zod-class';

import { folderNameSchema } from '../../schemas/folder.schema';

export class UpdateFolderDto extends Z.class({
name: folderNameSchema,
name: folderNameSchema.optional(),
tagIds: z.array(z.string().max(24)).optional(),
}) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Service } from '@n8n/di';
import { DataSource, Repository } from '@n8n/typeorm';

import { FolderTagMapping } from '../entities/folder-tag-mapping';

@Service()
export class FolderTagMappingRepository extends Repository<FolderTagMapping> {
constructor(dataSource: DataSource) {
super(FolderTagMapping, dataSource.manager);
}

async overwriteTags(folderId: string, tagIds: string[]) {
return await this.manager.transaction(async (tx) => {
await tx.delete(FolderTagMapping, { folderId });

const tags = tagIds.map((tagId) => this.create({ folderId, tagId }));

return await tx.insert(FolderTagMapping, tags);
});
}
}
15 changes: 12 additions & 3 deletions packages/cli/src/services/folder.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Service } from '@n8n/di';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import type { EntityManager } from '@n8n/typeorm';

import { FolderTagMappingRepository } from '@/databases/repositories/folder-tag-mapping.repository';
import { FolderRepository } from '@/databases/repositories/folder.repository';
import { FolderNotFoundError } from '@/errors/folder-not-found.error';

Expand All @@ -20,7 +21,10 @@ interface FolderPathRow {

@Service()
export class FolderService {
constructor(private readonly folderRepository: FolderRepository) {}
constructor(
private readonly folderRepository: FolderRepository,
private readonly folderTagMappingRepository: FolderTagMappingRepository,
) {}

async createFolder({ parentFolderId, name }: CreateFolderDto, projectId: string) {
let parentFolder = null;
Expand All @@ -39,9 +43,14 @@ export class FolderService {
return folder;
}

async updateFolder(folderId: string, projectId: string, { name }: UpdateFolderDto) {
async updateFolder(folderId: string, projectId: string, { name, tagIds }: UpdateFolderDto) {
await this.getFolderInProject(folderId, projectId);
return await this.folderRepository.update({ id: folderId }, { name });
if (name) {
await this.folderRepository.update({ id: folderId }, { name });
}
if (tagIds) {
await this.folderTagMappingRepository.overwriteTags(folderId, tagIds);
}
}

async getFolderInProject(folderId: string, projectId: string, em?: EntityManager) {
Expand Down
54 changes: 54 additions & 0 deletions packages/cli/test/integration/folder/folder.controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { User } from '@/databases/entities/user';
import { FolderRepository } from '@/databases/repositories/folder.repository';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { createFolder } from '@test-integration/db/folders';
import { createTag } from '@test-integration/db/tags';

import { createTeamProject, linkUserToProject } from '../shared/db/projects';
import { createOwner, createMember } from '../shared/db/users';
Expand Down Expand Up @@ -408,4 +409,57 @@ describe('PATCH /projects/:projectId/folders/:folderId', () => {
const folderInDb = await folderRepository.findOneBy({ id: folder.id });
expect(folderInDb?.name).toBe('Updated Folder Name');
});

test('should update folder tags', async () => {
const project = await createTeamProject('test project', owner);
const folder = await createFolder(project, { name: 'Test Folder' });
const tag1 = await createTag({ name: 'Tag 1' });
const tag2 = await createTag({ name: 'Tag 2' });

const payload = {
tagIds: [tag1.id, tag2.id],
};

await authOwnerAgent
.patch(`/projects/${project.id}/folders/${folder.id}`)
.send(payload)
.expect(200);

const folderWithTags = await folderRepository.findOne({
where: { id: folder.id },
relations: ['tags'],
});

expect(folderWithTags?.tags).toHaveLength(2);
expect(folderWithTags?.tags.map((t) => t.id).sort()).toEqual([tag1.id, tag2.id].sort());
});

test('should replace existing folder tags with new ones', async () => {
const project = await createTeamProject(undefined, owner);
const tag1 = await createTag({ name: 'Tag 1' });
const tag2 = await createTag({ name: 'Tag 2' });
const tag3 = await createTag({ name: 'Tag 3' });

const folder = await createFolder(project, {
name: 'Test Folder',
tags: [tag1, tag2],
});

const payload = {
tagIds: [tag3.id],
};

await authOwnerAgent
.patch(`/projects/${project.id}/folders/${folder.id}`)
.send(payload)
.expect(200);

const folderWithTags = await folderRepository.findOne({
where: { id: folder.id },
relations: ['tags'],
});

expect(folderWithTags?.tags).toHaveLength(1);
expect(folderWithTags?.tags[0].id).toBe(tag3.id);
});
});
Loading