stat filter
Gitea Actions Demo / Deploy (push) Successful in 28s

This commit is contained in:
vadim committed 2026-09-28 17:35:03 +03:00
1 parent 40d579cdb9
commit 382cafa152
8 files changed
+25 -9

No files matched your search

@@ -44,4 +44,13 @@ describe('GetIssueStatsUseCase', () => {
expect(stats.total).toBe(0);
expect(stats.byStatus.every((entry) => entry.count === 0)).toBe(true);
});
it('passes projectId through to the repository to filter stats by project', async () => {
vi.mocked(repository.countByStatus).mockResolvedValue([{ status: 'OPEN', count: 3 }]);
const stats = await useCase.execute('user-1', 'project-1');
expect(repository.countByStatus).toHaveBeenCalledWith('user-1', 'project-1');
expect(stats.total).toBe(3);
});
});
@@ -10,8 +10,8 @@ export class GetIssueStatsUseCase {
private readonly issueRepository: IIssueRepository,
) {}
async execute(userId: string): Promise<IssueStatsDto> {
const counts = await this.issueRepository.countByStatus(userId);
async execute(userId: string, projectId?: string): Promise<IssueStatsDto> {
const counts = await this.issueRepository.countByStatus(userId, projectId);
const countByStatus = new Map(counts.map((entry) => [entry.status, entry.count]));
// Каждый известный статус попадает в ответ, даже если по нему 0 задач
@@ -17,7 +17,7 @@ export interface IIssueRepository {
assigneeId?: string;
projectId?: string;
}): Promise<{ list: Issue[]; meta: PaginationMeta }>;
countByStatus(userId: string): Promise<Array<{ status: string; count: number }>>;
countByStatus(userId: string, projectId?: string): Promise<Array<{ status: string; count: number }>>;
create(issue: Issue): Promise<Issue>;
update(issue: Issue): Promise<void>;
delete(id: number): Promise<void>;
@@ -1,5 +1,5 @@
export const ISSUE_STATUSES = [
{ name: 'Open', alias: 'OPEN', color: 'surface-light' },
{ name: 'Open', alias: 'OPEN', color: 'default' },
{ name: 'In progress', alias: 'IN_PROGRESS', color: 'warning' },
{ name: 'Done', alias: 'DONE', color: 'success' },
{ name: 'Cancelled', alias: 'CANCELLED', color: 'primary' },
@@ -85,10 +85,10 @@ export class IssuePrismaRepository implements IIssueRepository {
};
}
async countByStatus(userId: string): Promise<Array<{ status: string; count: number }>> {
async countByStatus(userId: string, projectId?: string): Promise<Array<{ status: string; count: number }>> {
const groups = await this.prisma.issue.groupBy({
by: ['status'],
where: { OR: ownedByUser(userId) },
where: { AND: [{ OR: ownedByUser(userId) }, ...(projectId ? [{ projectId }] : [])] },
_count: { _all: true },
});
@@ -32,6 +32,7 @@ import { UpdateIssueRequestDto } from '../dto/update-issue.dto.js';
import { IssueNotFoundException } from '../../domain/exceptions/issue-not-found.exception.js';
import { IssueProjectNotFoundException } from '../../domain/exceptions/issue-project-not-found.exception.js';
import { FilterIssueDto } from '../dto/filter-issue.dto.js';
import { IssueStatsQueryDto } from '../dto/issue-stats-query.dto.js';
import { GetIssueByCkeyUseCase } from '../../application/use-cases/get-issue-by-ckey/get-issue-by-ckey.use-case.js';
import { GetIssueByCkeyQuery } from '../../application/use-cases/get-issue-by-ckey/get-issue-by-ckey.query.js';
import { IssueEventsService } from '../../application/services/issue-events.service.js';
@@ -84,8 +85,8 @@ export class IssueController {
}
@Get('stats')
stats(@Session() session: UserSession) {
return this.getIssueStatsUseCase.execute(session.user.id);
stats(@Query() query: IssueStatsQueryDto, @Session() session: UserSession) {
return this.getIssueStatsUseCase.execute(session.user.id, query.projectId);
}
/**
@@ -0,0 +1,6 @@
import { StringDecorator } from '../../../../common/decorators/validators.js';
export class IssueStatsQueryDto {
@StringDecorator({ isOptional: true })
projectId?: string;
}