Skip to content

Commit 5178c02

Browse files
authored
Merge pull request #80 from topcoder-platform/fix/issue-58
#58 Refactor the GET reviews logic and change the review status to an enum
2 parents 2953873 + 05e9df6 commit 5178c02

File tree

4 files changed

+89
-95
lines changed

4 files changed

+89
-95
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
Warnings:
3+
4+
- The `status` column on the `review` table would be dropped and recreated. This will lead to data loss if there is data in the column.
5+
6+
*/
7+
-- CreateEnum
8+
CREATE TYPE "ReviewStatus" AS ENUM ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED');
9+
10+
-- AlterTable
11+
ALTER TABLE "review" DROP COLUMN "status",
12+
ADD COLUMN "status" "ReviewStatus";
13+
14+
-- CreateIndex
15+
CREATE INDEX "review_status_idx" ON "review"("status");

prisma/schema.prisma

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ model scorecardQuestion {
141141
@@index([sortOrder]) // Index for ordering questions
142142
}
143143

144+
enum ReviewStatus {
145+
PENDING
146+
IN_PROGRESS
147+
COMPLETED
148+
DRAFT
149+
SUBMITTED
150+
APPROVED
151+
REJECTED
152+
}
153+
144154
model review {
145155
id String @id @default(dbgenerated("gen_random_uuid()")) @db.VarChar(36)
146156
legacyId String?
@@ -154,7 +164,7 @@ model review {
154164
initialScore Float?
155165
typeId String?
156166
metadata Json?
157-
status String?
167+
status ReviewStatus?
158168
reviewDate DateTime?
159169
createdAt DateTime @default(now())
160170
createdBy String
@@ -170,6 +180,7 @@ model review {
170180
@@index([resourceId]) // Index for filtering by resource (reviewer)
171181
@@index([phaseId]) // Index for filtering by phase
172182
@@index([scorecardId]) // Index for joining with scorecard table
183+
@@index([status]) // Index for filtering by review status
173184
}
174185

175186
model reviewItem {

src/api/review/review.controller.ts

Lines changed: 46 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ import {
3333
ReviewItemRequestDto,
3434
ReviewItemResponseDto,
3535
ReviewProgressResponseDto,
36+
ReviewStatus,
3637
mapReviewRequestToDto,
3738
mapReviewItemRequestToDto,
3839
} from 'src/dto/review.dto';
3940
import { PrismaService } from '../../shared/modules/global/prisma.service';
40-
import { ScorecardStatus } from '../../dto/scorecard.dto';
4141
import { LoggerService } from '../../shared/modules/global/logger.service';
4242
import { PaginatedResponse, PaginationDto } from '../../dto/pagination.dto';
4343
import { PrismaErrorService } from '../../shared/modules/global/prisma-error.service';
@@ -337,16 +337,16 @@ export class ReviewController {
337337
@Roles(UserRole.Reviewer, UserRole.Copilot, UserRole.Admin, UserRole.User)
338338
@Scopes(Scope.ReadReview)
339339
@ApiOperation({
340-
summary: 'Search for pending reviews',
340+
summary: 'Search for reviews',
341341
description:
342342
'Roles: Reviewer, Copilot, Admin, User. For User, only applies to their own review, until challenge completion. | Scopes: read:review',
343343
})
344344
@ApiQuery({
345345
name: 'status',
346346
description: 'The review status to filter by',
347-
example: 'pending',
347+
enum: ReviewStatus,
348+
example: ReviewStatus.PENDING,
348349
required: false,
349-
enum: ScorecardStatus,
350350
})
351351
@ApiQuery({
352352
name: 'challengeId',
@@ -360,17 +360,17 @@ export class ReviewController {
360360
})
361361
@ApiResponse({
362362
status: 200,
363-
description: 'List of pending reviews.',
363+
description: 'List of reviews.',
364364
type: [ReviewResponseDto],
365365
})
366-
async getPendingReviews(
367-
@Query('status') status?: ScorecardStatus,
366+
async getReviews(
367+
@Query('status') status?: ReviewStatus,
368368
@Query('challengeId') challengeId?: string,
369369
@Query('submissionId') submissionId?: string,
370370
@Query() paginationDto?: PaginationDto,
371371
): Promise<PaginatedResponse<ReviewResponseDto>> {
372372
this.logger.log(
373-
`Getting pending reviews with filters - status: ${status}, challengeId: ${challengeId}, submissionId: ${submissionId}`,
373+
`Getting reviews with filters - status: ${status}, challengeId: ${challengeId}, submissionId: ${submissionId}`,
374374
);
375375

376376
const { page = 1, perPage = 10 } = paginationDto || {};
@@ -379,115 +379,71 @@ export class ReviewController {
379379
try {
380380
// Build the where clause for reviews based on available filter parameters
381381
const reviewWhereClause: any = {};
382+
382383
if (submissionId) {
383384
reviewWhereClause.submissionId = submissionId;
384385
}
385386

386-
// Get reviews by status if provided
387-
let reviews: any[] = [];
388-
let totalCount = 0;
389-
390387
if (status) {
391-
this.logger.debug(`Fetching reviews by scorecard status: ${status}`);
392-
const scorecards = await this.prisma.scorecard.findMany({
393-
where: {
394-
status: ScorecardStatus[status as keyof typeof ScorecardStatus],
395-
},
396-
include: {
397-
reviews: {
398-
where: reviewWhereClause,
399-
skip,
400-
take: perPage,
401-
},
402-
},
403-
});
404-
405-
reviews = scorecards.flatMap((d) => d.reviews);
406-
407-
// Count total reviews matching the filter for pagination metadata
408-
const scorecardIds = scorecards.map((s) => s.id);
409-
totalCount = await this.prisma.review.count({
410-
where: {
411-
...reviewWhereClause,
412-
scorecardId: { in: scorecardIds },
413-
},
414-
});
388+
reviewWhereClause.status = status;
415389
}
416390

417391
// Get reviews by challengeId if provided
418392
if (challengeId) {
419393
this.logger.debug(`Fetching reviews by challengeId: ${challengeId}`);
420-
const challengeResults = await this.prisma.challengeResult.findMany({
394+
// Get submissions for this challenge directly (consistent with POST /reviews)
395+
const submissions = await this.prisma.submission.findMany({
421396
where: { challengeId },
397+
select: { id: true },
422398
});
423399

424-
const submissionIds = challengeResults.map((c) => c.submissionId);
400+
const submissionIds = submissions.map((s) => s.id);
425401

426402
if (submissionIds.length > 0) {
427-
const challengeReviews = await this.prisma.review.findMany({
428-
where: {
429-
submissionId: { in: submissionIds },
430-
...reviewWhereClause,
431-
},
432-
skip,
433-
take: perPage,
434-
include: {
435-
reviewItems: {
436-
include: {
437-
reviewItemComments: true,
438-
},
439-
},
440-
},
441-
});
442-
443-
reviews = [...reviews, ...challengeReviews];
444-
445-
// Count total for this condition separately
446-
const challengeReviewCount = await this.prisma.review.count({
447-
where: {
448-
submissionId: { in: submissionIds },
449-
...reviewWhereClause,
403+
reviewWhereClause.submissionId = { in: submissionIds };
404+
} else {
405+
// No submissions found for this challenge, return empty result
406+
return {
407+
data: [],
408+
meta: {
409+
page,
410+
perPage,
411+
totalCount: 0,
412+
totalPages: 0,
450413
},
451-
});
452-
453-
totalCount += challengeReviewCount;
414+
};
454415
}
455416
}
456417

457-
// If no specific filters, get all reviews with pagination
458-
if (!status && !challengeId && !submissionId) {
459-
this.logger.debug('Fetching all reviews with pagination');
460-
reviews = await this.prisma.review.findMany({
461-
skip,
462-
take: perPage,
463-
include: {
464-
reviewItems: {
465-
include: {
466-
reviewItemComments: true,
467-
},
418+
// Get reviews with the built where clause
419+
this.logger.debug(
420+
`Fetching reviews with where clause:`,
421+
reviewWhereClause,
422+
);
423+
const reviews = await this.prisma.review.findMany({
424+
where: reviewWhereClause,
425+
skip,
426+
take: perPage,
427+
include: {
428+
reviewItems: {
429+
include: {
430+
reviewItemComments: true,
468431
},
469432
},
470-
});
471-
472-
totalCount = await this.prisma.review.count();
473-
}
433+
},
434+
});
474435

475-
// Deduplicate reviews by ID
476-
const uniqueReviews = Object.values(
477-
reviews.reduce((acc: Record<string, any>, review) => {
478-
if (!acc[review.id]) {
479-
acc[review.id] = review;
480-
}
481-
return acc;
482-
}, {}),
483-
);
436+
// Count total reviews matching the filter for pagination metadata
437+
const totalCount = await this.prisma.review.count({
438+
where: reviewWhereClause,
439+
});
484440

485441
this.logger.log(
486-
`Found ${uniqueReviews.length} reviews (page ${page} of ${Math.ceil(totalCount / perPage)})`,
442+
`Found ${reviews.length} reviews (page ${page} of ${Math.ceil(totalCount / perPage)})`,
487443
);
488444

489445
return {
490-
data: uniqueReviews as ReviewResponseDto[],
446+
data: reviews as ReviewResponseDto[],
491447
meta: {
492448
page,
493449
perPage,

src/dto/review.dto.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ export enum ReviewItemCommentType {
2424
SPECIFICATION_REVIEW_COMMENT = 'SPECIFICATION_REVIEW_COMMENT',
2525
}
2626

27+
export enum ReviewStatus {
28+
PENDING = 'PENDING',
29+
IN_PROGRESS = 'IN_PROGRESS',
30+
COMPLETED = 'COMPLETED',
31+
DRAFT = 'DRAFT',
32+
SUBMITTED = 'SUBMITTED',
33+
APPROVED = 'APPROVED',
34+
REJECTED = 'REJECTED',
35+
}
36+
2737
export class ReviewItemCommentBaseDto {
2838
@ApiProperty({
2939
description: 'Content of the comment',
@@ -222,11 +232,12 @@ export class ReviewBaseDto {
222232

223233
@ApiProperty({
224234
description: 'Status for the review',
225-
example: 'Review',
235+
enum: ReviewStatus,
236+
example: ReviewStatus.PENDING,
226237
})
227238
@IsString()
228239
@IsNotEmpty()
229-
status: string;
240+
status: ReviewStatus;
230241

231242
@ApiProperty({
232243
description: 'Review date for the review',
@@ -316,12 +327,13 @@ export class ReviewPatchRequestDto {
316327

317328
@ApiProperty({
318329
description: 'Status for the review',
319-
example: 'Review',
330+
enum: ReviewStatus,
331+
example: ReviewStatus.PENDING,
320332
})
321333
@IsOptional()
322334
@IsString()
323335
@IsNotEmpty()
324-
status?: string;
336+
status?: ReviewStatus;
325337

326338
@ApiProperty({
327339
description: 'Review date for the review',

0 commit comments

Comments
 (0)