mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(editor): Correct WF counts on folder with archived filter (no-changelog) (#15195)
This commit is contained in:
@@ -29,7 +29,7 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
||||
): SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount> {
|
||||
const query = this.createQueryBuilder('folder');
|
||||
|
||||
this.applySelections(query, options.select);
|
||||
this.applySelections(query, options.select, options.filter);
|
||||
this.applyFilters(query, options.filter);
|
||||
this.applySorting(query, options.sortBy);
|
||||
this.applyPagination(query, options);
|
||||
@@ -39,21 +39,41 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
||||
|
||||
private applySelections(
|
||||
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
||||
select?: Record<string, boolean>,
|
||||
select?: ListQuery.Options['select'],
|
||||
filter?: ListQuery.Options['filter'],
|
||||
): void {
|
||||
if (select) {
|
||||
this.applyCustomSelect(query, select);
|
||||
this.applyCustomSelect(query, select, filter);
|
||||
} else {
|
||||
this.applyDefaultSelect(query);
|
||||
this.applyDefaultSelect(query, filter);
|
||||
}
|
||||
}
|
||||
|
||||
private applyDefaultSelect(query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>): void {
|
||||
private applyWorkflowCountSelect(
|
||||
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
||||
filter?: ListQuery.Options['filter'],
|
||||
): void {
|
||||
if (typeof filter?.isArchived === 'boolean') {
|
||||
query.loadRelationCountAndMap('folder.workflowCount', 'folder.workflows', 'workflow', (qb) =>
|
||||
qb.andWhere('workflow.isArchived = :isArchived', {
|
||||
isArchived: filter.isArchived,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
query.loadRelationCountAndMap('folder.workflowCount', 'folder.workflows');
|
||||
}
|
||||
}
|
||||
|
||||
private applyDefaultSelect(
|
||||
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
||||
filter?: ListQuery.Options['filter'],
|
||||
): void {
|
||||
this.applyWorkflowCountSelect(query, filter);
|
||||
|
||||
query
|
||||
.leftJoinAndSelect('folder.homeProject', 'homeProject')
|
||||
.leftJoinAndSelect('folder.parentFolder', 'parentFolder')
|
||||
.leftJoinAndSelect('folder.tags', 'tags')
|
||||
.loadRelationCountAndMap('folder.workflowCount', 'folder.workflows')
|
||||
.loadRelationCountAndMap('folder.subFolderCount', 'folder.subFolders')
|
||||
.select([
|
||||
'folder',
|
||||
@@ -65,17 +85,18 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
||||
|
||||
private applyCustomSelect(
|
||||
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
||||
select?: Record<string, boolean>,
|
||||
select?: ListQuery.Options['select'],
|
||||
filter?: ListQuery.Options['filter'],
|
||||
): void {
|
||||
const selections = ['folder.id'];
|
||||
|
||||
this.addBasicFields(selections, select);
|
||||
this.addRelationFields(query, selections, select);
|
||||
this.addRelationFields(query, selections, select, filter);
|
||||
|
||||
query.select(selections);
|
||||
}
|
||||
|
||||
private addBasicFields(selections: string[], select?: Record<string, boolean>): void {
|
||||
private addBasicFields(selections: string[], select?: ListQuery.Options['select']): void {
|
||||
if (select?.name) selections.push('folder.name');
|
||||
if (select?.createdAt) selections.push('folder.createdAt');
|
||||
if (select?.updatedAt) selections.push('folder.updatedAt');
|
||||
@@ -84,7 +105,8 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
||||
private addRelationFields(
|
||||
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
||||
selections: string[],
|
||||
select?: Record<string, boolean>,
|
||||
select?: ListQuery.Options['select'],
|
||||
filter?: ListQuery.Options['filter'],
|
||||
): void {
|
||||
if (select?.project) {
|
||||
query.leftJoin('folder.homeProject', 'homeProject');
|
||||
@@ -102,7 +124,7 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
||||
}
|
||||
|
||||
if (select?.workflowCount) {
|
||||
query.loadRelationCountAndMap('folder.workflowCount', 'folder.workflows');
|
||||
this.applyWorkflowCountSelect(query, filter);
|
||||
}
|
||||
|
||||
if (select?.subFolderCount) {
|
||||
|
||||
@@ -25,7 +25,6 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
import { AuthenticatedRequest } from '@/requests';
|
||||
import type { ListQuery } from '@/requests';
|
||||
import { FolderService } from '@/services/folder.service';
|
||||
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
||||
|
||||
@@ -124,10 +123,7 @@ export class ProjectController {
|
||||
) {
|
||||
const { projectId } = req.params;
|
||||
|
||||
const [data, count] = await this.folderService.getManyAndCount(
|
||||
projectId,
|
||||
payload as ListQuery.Options,
|
||||
);
|
||||
const [data, count] = await this.folderService.getManyAndCount(projectId, payload);
|
||||
|
||||
res.json({ count, data });
|
||||
}
|
||||
|
||||
@@ -242,6 +242,83 @@ describe('FolderRepository', () => {
|
||||
expect(folders[0].parentFolder?.id).toBe(parentFolder.id);
|
||||
expect(folders[0].tags[0].name).toBe('important');
|
||||
});
|
||||
|
||||
describe('workflowCount', () => {
|
||||
let testFolder: Folder;
|
||||
|
||||
beforeEach(async () => {
|
||||
const parentFolder = await createFolder(project, { name: 'Parent' });
|
||||
testFolder = await createFolder(project, { name: 'Test Folder', parentFolder });
|
||||
|
||||
await createWorkflow({ parentFolder: testFolder, isArchived: false });
|
||||
await createWorkflow({ parentFolder: testFolder, isArchived: false });
|
||||
await createWorkflow({ parentFolder: testFolder, isArchived: true });
|
||||
await createWorkflow({ parentFolder: testFolder, isArchived: true });
|
||||
await createWorkflow({ parentFolder: testFolder, isArchived: true });
|
||||
});
|
||||
|
||||
it('should include archived workflows in the workflow count by default', async () => {
|
||||
const [folders] = await folderRepository.getManyAndCount({
|
||||
select: { workflowCount: true },
|
||||
});
|
||||
|
||||
expect(folders).toHaveLength(2);
|
||||
expect(folders).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: testFolder.id,
|
||||
workflowCount: 5,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: testFolder.parentFolderId,
|
||||
workflowCount: 0,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should include only archived workflows in the workflow count if filtered', async () => {
|
||||
const [folders] = await folderRepository.getManyAndCount({
|
||||
select: { workflowCount: true },
|
||||
filter: { isArchived: true },
|
||||
});
|
||||
|
||||
expect(folders).toHaveLength(2);
|
||||
expect(folders).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: testFolder.id,
|
||||
workflowCount: 3,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: testFolder.parentFolderId,
|
||||
workflowCount: 0,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return only unarchived workflows in the workflow count if filtered', async () => {
|
||||
const [folders] = await folderRepository.getManyAndCount({
|
||||
select: { workflowCount: true },
|
||||
filter: { isArchived: false },
|
||||
});
|
||||
|
||||
expect(folders).toHaveLength(2);
|
||||
expect(folders).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: testFolder.id,
|
||||
workflowCount: 2,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: testFolder.parentFolderId,
|
||||
workflowCount: 0,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('select', () => {
|
||||
|
||||
@@ -287,7 +287,10 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
||||
this.getWorkflowsAndFoldersCount(workflowIds, options),
|
||||
]);
|
||||
|
||||
const { workflows, folders } = await this.fetchExtraData(workflowsAndFolders);
|
||||
const isArchived =
|
||||
typeof options.filter?.isArchived === 'boolean' ? options.filter.isArchived : undefined;
|
||||
|
||||
const { workflows, folders } = await this.fetchExtraData(workflowsAndFolders, isArchived);
|
||||
|
||||
const enrichedWorkflowsAndFolders = this.enrichDataWithExtras(workflowsAndFolders, {
|
||||
workflows,
|
||||
@@ -323,13 +326,16 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
||||
.map((item) => item.id);
|
||||
}
|
||||
|
||||
private async fetchExtraData(workflowsAndFolders: WorkflowFolderUnionRow[]) {
|
||||
private async fetchExtraData(
|
||||
workflowsAndFolders: WorkflowFolderUnionRow[],
|
||||
isArchived?: boolean,
|
||||
) {
|
||||
const workflowIds = this.getWorkflowsIds(workflowsAndFolders);
|
||||
const folderIds = this.getFolderIds(workflowsAndFolders);
|
||||
|
||||
const [workflows, folders] = await Promise.all([
|
||||
this.getMany(workflowIds),
|
||||
this.folderRepository.getMany({ filter: { folderIds } }),
|
||||
this.folderRepository.getMany({ filter: { folderIds, isArchived } }),
|
||||
]);
|
||||
|
||||
return { workflows, folders };
|
||||
|
||||
@@ -253,7 +253,8 @@ export class FolderService {
|
||||
const workflowCountQuery = this.workflowRepository
|
||||
.createQueryBuilder('workflow')
|
||||
.select('COUNT(workflow.id)', 'count')
|
||||
.where((qb) => {
|
||||
.where('workflow.isArchived = :isArchived', { isArchived: false })
|
||||
.andWhere((qb) => {
|
||||
const folderQuery = qb.subQuery().from('folder_path', 'fp').select('fp.id').getQuery();
|
||||
return `workflow.parentFolderId IN ${folderQuery}`;
|
||||
})
|
||||
@@ -279,7 +280,7 @@ export class FolderService {
|
||||
}
|
||||
|
||||
async getManyAndCount(projectId: string, options: ListQuery.Options) {
|
||||
options.filter = { ...options.filter, projectId };
|
||||
options.filter = { ...options.filter, projectId, isArchived: false };
|
||||
return await this.folderRepository.getManyAndCount(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1320,6 +1320,23 @@ describe('GET /projects/:projectId/folders', () => {
|
||||
['Owner Folder 1', 'Owner Folder 2'].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
test('should include workflow count', async () => {
|
||||
const folder = await createFolder(ownerProject, { name: 'Test Folder' });
|
||||
await createWorkflow({ parentFolder: folder, isArchived: false }, ownerProject);
|
||||
await createWorkflow({ parentFolder: folder, isArchived: false }, ownerProject);
|
||||
// Not included in the count
|
||||
await createWorkflow({ parentFolder: folder, isArchived: true }, ownerProject);
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get(`/projects/${ownerProject.id}/folders`)
|
||||
.query({ filter: '{ "name": "test" }' })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.count).toBe(1);
|
||||
expect(response.body.data).toHaveLength(1);
|
||||
expect(response.body.data[0].workflowCount).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /projects/:projectId/folders/content', () => {
|
||||
@@ -1371,6 +1388,11 @@ describe('GET /projects/:projectId/folders/content', () => {
|
||||
await createWorkflow({ parentFolder: personalFolder1 }, ownerProject);
|
||||
await createWorkflow({ parentFolder: personalProjectSubfolder1 }, ownerProject);
|
||||
await createWorkflow({ parentFolder: personalProjectSubfolder2 }, ownerProject);
|
||||
// Not included in the count
|
||||
await createWorkflow(
|
||||
{ parentFolder: personalProjectSubfolder2, isArchived: true },
|
||||
ownerProject,
|
||||
);
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get(`/projects/${ownerProject.id}/folders/${personalFolder1.id}/content`)
|
||||
|
||||
Reference in New Issue
Block a user