fix(editor): Correct WF counts on folder with archived filter (no-changelog) (#15195)

This commit is contained in:
Jaakko Husso
2025-05-10 11:38:36 +03:00
committed by GitHub
parent 403f08b6e3
commit 15e62e6dfa
6 changed files with 145 additions and 21 deletions

View File

@@ -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) {

View File

@@ -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 });
}

View File

@@ -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', () => {

View File

@@ -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 };

View File

@@ -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);
}
}

View File

@@ -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`)