mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
feat(core): Update list folders endpoint to support filter excludeFolderIdAndDescendants (no-changelog) (#13880)
This commit is contained in:
@@ -29,6 +29,7 @@ export const filterSchema = z
|
|||||||
parentFolderId: z.string().optional(),
|
parentFolderId: z.string().optional(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
tags: z.array(z.string()).optional(),
|
tags: z.array(z.string()).optional(),
|
||||||
|
excludeFolderIdAndDescendants: z.string().optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,13 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
|||||||
|
|
||||||
this.applyBasicFilters(query, filter);
|
this.applyBasicFilters(query, filter);
|
||||||
this.applyTagsFilter(query, Array.isArray(filter?.tags) ? filter.tags : undefined);
|
this.applyTagsFilter(query, Array.isArray(filter?.tags) ? filter.tags : undefined);
|
||||||
|
|
||||||
|
if (
|
||||||
|
filter?.excludeFolderIdAndDescendants &&
|
||||||
|
typeof filter.excludeFolderIdAndDescendants === 'string'
|
||||||
|
) {
|
||||||
|
this.applyExcludeFolderFilter(query, filter.excludeFolderIdAndDescendants);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyBasicFilters(
|
private applyBasicFilters(
|
||||||
@@ -293,4 +300,38 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyExcludeFolderFilter(
|
||||||
|
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
||||||
|
excludeFolderIdAndDescendants: string,
|
||||||
|
): void {
|
||||||
|
// Exclude the specific folder by ID
|
||||||
|
query.andWhere('folder.id != :excludeFolderIdAndDescendants', {
|
||||||
|
excludeFolderIdAndDescendants,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use a WITH RECURSIVE CTE to find all child folders of the excluded folder
|
||||||
|
const baseQuery = this.createQueryBuilder('f')
|
||||||
|
.select('f.id', 'id')
|
||||||
|
.addSelect('f.parentFolderId', 'parentFolderId')
|
||||||
|
.where('f.id = :excludeFolderIdAndDescendants', { excludeFolderIdAndDescendants });
|
||||||
|
|
||||||
|
const recursiveQuery = this.createQueryBuilder('child')
|
||||||
|
.select('child.id', 'id')
|
||||||
|
.addSelect('child.parentFolderId', 'parentFolderId')
|
||||||
|
.innerJoin('folder_tree', 'parent', 'child.parentFolderId = parent.id');
|
||||||
|
|
||||||
|
const subQuery = this.createQueryBuilder()
|
||||||
|
.select('tree.id')
|
||||||
|
.addCommonTableExpression(
|
||||||
|
`${baseQuery.getQuery()} UNION ALL ${recursiveQuery.getQuery()}`,
|
||||||
|
'folder_tree',
|
||||||
|
{ recursive: true },
|
||||||
|
)
|
||||||
|
.from('folder_tree', 'tree')
|
||||||
|
.setParameters({ excludeFolderIdAndDescendants });
|
||||||
|
|
||||||
|
// Exclude all children of the specified folder
|
||||||
|
query.andWhere(`folder.id NOT IN (${subQuery.getQuery()})`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1014,6 +1014,39 @@ describe('GET /projects/:projectId/folders', () => {
|
|||||||
expect(response.body.data[0].name).toBe('Folder 3');
|
expect(response.body.data[0].name).toBe('Folder 3');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should filter folders by excludeFolderIdAndDescendants', async () => {
|
||||||
|
const folder1 = await createFolder(ownerProject, { name: 'folder level 1' });
|
||||||
|
await createFolder(ownerProject, {
|
||||||
|
name: 'folder level 1.1',
|
||||||
|
parentFolder: folder1,
|
||||||
|
});
|
||||||
|
const folder12 = await createFolder(ownerProject, {
|
||||||
|
name: 'folder level 1.2',
|
||||||
|
parentFolder: folder1,
|
||||||
|
});
|
||||||
|
await createFolder(ownerProject, {
|
||||||
|
name: 'folder level 1.2.1',
|
||||||
|
parentFolder: folder12,
|
||||||
|
});
|
||||||
|
const folder122 = await createFolder(ownerProject, {
|
||||||
|
name: 'folder level 1.2.2',
|
||||||
|
parentFolder: folder12,
|
||||||
|
});
|
||||||
|
await createFolder(ownerProject, {
|
||||||
|
name: 'folder level 1.2.2.1',
|
||||||
|
parentFolder: folder122,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await authOwnerAgent
|
||||||
|
.get(`/projects/${ownerProject.id}/folders`)
|
||||||
|
.query({ filter: `{ "excludeFolderIdAndDescendants": "${folder122.id}" }` });
|
||||||
|
|
||||||
|
expect(response.body.data.length).toBe(4);
|
||||||
|
expect(response.body.data.map((f: any) => f.name).sort()).toEqual(
|
||||||
|
['folder level 1', 'folder level 1.1', 'folder level 1.2.1', 'folder level 1.2'].sort(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('should apply pagination with take parameter', async () => {
|
test('should apply pagination with take parameter', async () => {
|
||||||
// Create folders with consistent timestamps
|
// Create folders with consistent timestamps
|
||||||
for (let i = 1; i <= 5; i++) {
|
for (let i = 1; i <= 5; i++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user