mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +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> {
|
): SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount> {
|
||||||
const query = this.createQueryBuilder('folder');
|
const query = this.createQueryBuilder('folder');
|
||||||
|
|
||||||
this.applySelections(query, options.select);
|
this.applySelections(query, options.select, options.filter);
|
||||||
this.applyFilters(query, options.filter);
|
this.applyFilters(query, options.filter);
|
||||||
this.applySorting(query, options.sortBy);
|
this.applySorting(query, options.sortBy);
|
||||||
this.applyPagination(query, options);
|
this.applyPagination(query, options);
|
||||||
@@ -39,21 +39,41 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
|||||||
|
|
||||||
private applySelections(
|
private applySelections(
|
||||||
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
||||||
select?: Record<string, boolean>,
|
select?: ListQuery.Options['select'],
|
||||||
|
filter?: ListQuery.Options['filter'],
|
||||||
): void {
|
): void {
|
||||||
if (select) {
|
if (select) {
|
||||||
this.applyCustomSelect(query, select);
|
this.applyCustomSelect(query, select, filter);
|
||||||
} else {
|
} 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
|
query
|
||||||
.leftJoinAndSelect('folder.homeProject', 'homeProject')
|
.leftJoinAndSelect('folder.homeProject', 'homeProject')
|
||||||
.leftJoinAndSelect('folder.parentFolder', 'parentFolder')
|
.leftJoinAndSelect('folder.parentFolder', 'parentFolder')
|
||||||
.leftJoinAndSelect('folder.tags', 'tags')
|
.leftJoinAndSelect('folder.tags', 'tags')
|
||||||
.loadRelationCountAndMap('folder.workflowCount', 'folder.workflows')
|
|
||||||
.loadRelationCountAndMap('folder.subFolderCount', 'folder.subFolders')
|
.loadRelationCountAndMap('folder.subFolderCount', 'folder.subFolders')
|
||||||
.select([
|
.select([
|
||||||
'folder',
|
'folder',
|
||||||
@@ -65,17 +85,18 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
|||||||
|
|
||||||
private applyCustomSelect(
|
private applyCustomSelect(
|
||||||
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
||||||
select?: Record<string, boolean>,
|
select?: ListQuery.Options['select'],
|
||||||
|
filter?: ListQuery.Options['filter'],
|
||||||
): void {
|
): void {
|
||||||
const selections = ['folder.id'];
|
const selections = ['folder.id'];
|
||||||
|
|
||||||
this.addBasicFields(selections, select);
|
this.addBasicFields(selections, select);
|
||||||
this.addRelationFields(query, selections, select);
|
this.addRelationFields(query, selections, select, filter);
|
||||||
|
|
||||||
query.select(selections);
|
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?.name) selections.push('folder.name');
|
||||||
if (select?.createdAt) selections.push('folder.createdAt');
|
if (select?.createdAt) selections.push('folder.createdAt');
|
||||||
if (select?.updatedAt) selections.push('folder.updatedAt');
|
if (select?.updatedAt) selections.push('folder.updatedAt');
|
||||||
@@ -84,7 +105,8 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
|||||||
private addRelationFields(
|
private addRelationFields(
|
||||||
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
query: SelectQueryBuilder<FolderWithWorkflowAndSubFolderCount>,
|
||||||
selections: string[],
|
selections: string[],
|
||||||
select?: Record<string, boolean>,
|
select?: ListQuery.Options['select'],
|
||||||
|
filter?: ListQuery.Options['filter'],
|
||||||
): void {
|
): void {
|
||||||
if (select?.project) {
|
if (select?.project) {
|
||||||
query.leftJoin('folder.homeProject', 'homeProject');
|
query.leftJoin('folder.homeProject', 'homeProject');
|
||||||
@@ -102,7 +124,7 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (select?.workflowCount) {
|
if (select?.workflowCount) {
|
||||||
query.loadRelationCountAndMap('folder.workflowCount', 'folder.workflows');
|
this.applyWorkflowCountSelect(query, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (select?.subFolderCount) {
|
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 { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
import { AuthenticatedRequest } from '@/requests';
|
import { AuthenticatedRequest } from '@/requests';
|
||||||
import type { ListQuery } from '@/requests';
|
|
||||||
import { FolderService } from '@/services/folder.service';
|
import { FolderService } from '@/services/folder.service';
|
||||||
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
||||||
|
|
||||||
@@ -124,10 +123,7 @@ export class ProjectController {
|
|||||||
) {
|
) {
|
||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
|
|
||||||
const [data, count] = await this.folderService.getManyAndCount(
|
const [data, count] = await this.folderService.getManyAndCount(projectId, payload);
|
||||||
projectId,
|
|
||||||
payload as ListQuery.Options,
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({ count, data });
|
res.json({ count, data });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,6 +242,83 @@ describe('FolderRepository', () => {
|
|||||||
expect(folders[0].parentFolder?.id).toBe(parentFolder.id);
|
expect(folders[0].parentFolder?.id).toBe(parentFolder.id);
|
||||||
expect(folders[0].tags[0].name).toBe('important');
|
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', () => {
|
describe('select', () => {
|
||||||
|
|||||||
@@ -287,7 +287,10 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
|||||||
this.getWorkflowsAndFoldersCount(workflowIds, options),
|
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, {
|
const enrichedWorkflowsAndFolders = this.enrichDataWithExtras(workflowsAndFolders, {
|
||||||
workflows,
|
workflows,
|
||||||
@@ -323,13 +326,16 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
|||||||
.map((item) => item.id);
|
.map((item) => item.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchExtraData(workflowsAndFolders: WorkflowFolderUnionRow[]) {
|
private async fetchExtraData(
|
||||||
|
workflowsAndFolders: WorkflowFolderUnionRow[],
|
||||||
|
isArchived?: boolean,
|
||||||
|
) {
|
||||||
const workflowIds = this.getWorkflowsIds(workflowsAndFolders);
|
const workflowIds = this.getWorkflowsIds(workflowsAndFolders);
|
||||||
const folderIds = this.getFolderIds(workflowsAndFolders);
|
const folderIds = this.getFolderIds(workflowsAndFolders);
|
||||||
|
|
||||||
const [workflows, folders] = await Promise.all([
|
const [workflows, folders] = await Promise.all([
|
||||||
this.getMany(workflowIds),
|
this.getMany(workflowIds),
|
||||||
this.folderRepository.getMany({ filter: { folderIds } }),
|
this.folderRepository.getMany({ filter: { folderIds, isArchived } }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { workflows, folders };
|
return { workflows, folders };
|
||||||
|
|||||||
@@ -253,7 +253,8 @@ export class FolderService {
|
|||||||
const workflowCountQuery = this.workflowRepository
|
const workflowCountQuery = this.workflowRepository
|
||||||
.createQueryBuilder('workflow')
|
.createQueryBuilder('workflow')
|
||||||
.select('COUNT(workflow.id)', 'count')
|
.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();
|
const folderQuery = qb.subQuery().from('folder_path', 'fp').select('fp.id').getQuery();
|
||||||
return `workflow.parentFolderId IN ${folderQuery}`;
|
return `workflow.parentFolderId IN ${folderQuery}`;
|
||||||
})
|
})
|
||||||
@@ -279,7 +280,7 @@ export class FolderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getManyAndCount(projectId: string, options: ListQuery.Options) {
|
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);
|
return await this.folderRepository.getManyAndCount(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1320,6 +1320,23 @@ describe('GET /projects/:projectId/folders', () => {
|
|||||||
['Owner Folder 1', 'Owner Folder 2'].sort(),
|
['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', () => {
|
describe('GET /projects/:projectId/folders/content', () => {
|
||||||
@@ -1371,6 +1388,11 @@ describe('GET /projects/:projectId/folders/content', () => {
|
|||||||
await createWorkflow({ parentFolder: personalFolder1 }, ownerProject);
|
await createWorkflow({ parentFolder: personalFolder1 }, ownerProject);
|
||||||
await createWorkflow({ parentFolder: personalProjectSubfolder1 }, ownerProject);
|
await createWorkflow({ parentFolder: personalProjectSubfolder1 }, ownerProject);
|
||||||
await createWorkflow({ parentFolder: personalProjectSubfolder2 }, ownerProject);
|
await createWorkflow({ parentFolder: personalProjectSubfolder2 }, ownerProject);
|
||||||
|
// Not included in the count
|
||||||
|
await createWorkflow(
|
||||||
|
{ parentFolder: personalProjectSubfolder2, isArchived: true },
|
||||||
|
ownerProject,
|
||||||
|
);
|
||||||
|
|
||||||
const response = await authOwnerAgent
|
const response = await authOwnerAgent
|
||||||
.get(`/projects/${ownerProject.id}/folders/${personalFolder1.id}/content`)
|
.get(`/projects/${ownerProject.id}/folders/${personalFolder1.id}/content`)
|
||||||
|
|||||||
Reference in New Issue
Block a user