Skip to content

Commit 51a73cc

Browse files
committed
updated from develop
2 parents 6c59dc9 + 9a732de commit 51a73cc

File tree

10 files changed

+540
-17
lines changed

10 files changed

+540
-17
lines changed

.circleci/config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ workflows:
7575
only:
7676
- develop
7777
- feat/ai-workflows
78-
- pm-1782
78+
- pm-1792
79+
7980

8081
- 'build-prod':
8182
context: org-global

prisma/challenge-schema.prisma

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Prisma client for Challenge DB (subset used for counters)
1+
// Prisma client for Challenge DB (subset used for counters and read-only projections)
22

33
generator client {
44
provider = "prisma-client-js"
@@ -12,10 +12,77 @@ datasource db {
1212
url = env("CHALLENGE_DB_URL")
1313
}
1414

15-
// Minimal challenge model shape to increment/decrement counters
15+
enum ChallengeStatusEnum {
16+
NEW
17+
DRAFT
18+
APPROVED
19+
ACTIVE
20+
COMPLETED
21+
DELETED
22+
CANCELLED
23+
CANCELLED_FAILED_REVIEW
24+
CANCELLED_FAILED_SCREENING
25+
CANCELLED_ZERO_SUBMISSIONS
26+
CANCELLED_WINNER_UNRESPONSIVE
27+
CANCELLED_CLIENT_REQUEST
28+
CANCELLED_REQUIREMENTS_INFEASIBLE
29+
CANCELLED_ZERO_REGISTRATIONS
30+
CANCELLED_PAYMENT_FAILED
31+
}
32+
33+
enum ReviewOpportunityTypeEnum {
34+
REGULAR_REVIEW
35+
COMPONENT_DEV_REVIEW
36+
SPEC_REVIEW
37+
ITERATIVE_REVIEW
38+
SCENARIOS_REVIEW
39+
}
40+
41+
// Minimal challenge model shape expanded with read-only fields required by Review API
1642
model Challenge {
17-
id String @id
18-
numOfSubmissions Int @default(0)
19-
numOfCheckpointSubmissions Int @default(0)
43+
id String @id @db.Uuid
44+
name String
45+
status ChallengeStatusEnum
46+
numOfRegistrants Int @default(0)
47+
numOfSubmissions Int @default(0)
48+
numOfCheckpointSubmissions Int @default(0)
49+
submissionEndDate DateTime?
50+
51+
// Relations required for summary aggregations
52+
phases ChallengePhase[]
53+
reviewers ChallengeReviewer[]
54+
}
55+
56+
model ChallengePhase {
57+
id String @id @db.Uuid
58+
challengeId String @db.Uuid
59+
phaseId String @db.Uuid
60+
name String
61+
isOpen Boolean?
62+
scheduledStartDate DateTime?
63+
scheduledEndDate DateTime?
64+
actualStartDate DateTime?
65+
actualEndDate DateTime?
66+
67+
challenge Challenge @relation(fields: [challengeId], references: [id])
68+
69+
@@index([challengeId])
2070
}
2171

72+
model ChallengeReviewer {
73+
id String @id @db.Uuid
74+
challengeId String @db.Uuid
75+
scorecardId String
76+
isMemberReview Boolean
77+
memberReviewerCount Int?
78+
phaseId String @db.Uuid
79+
basePayment Float?
80+
incrementalPayment Float?
81+
type ReviewOpportunityTypeEnum?
82+
isAIReviewer Boolean
83+
84+
challenge Challenge @relation(fields: [challengeId], references: [id])
85+
86+
@@index([challengeId])
87+
@@index([phaseId])
88+
}

src/api/ai-workflow/ai-workflow.controller.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
CreateAiWorkflowRunItemsDto,
2626
UpdateAiWorkflowRunDto,
2727
CreateRunItemCommentDto,
28+
UpdateAiWorkflowRunItemDto,
2829
} from '../../dto/aiWorkflow.dto';
2930
import { Scopes } from 'src/shared/decorators/scopes.decorator';
3031
import { UserRole } from 'src/shared/enums/userRole.enum';
@@ -266,6 +267,52 @@ export class AiWorkflowController {
266267
);
267268
}
268269

270+
@Patch('/:workflowId/runs/:runId/items/:itemId')
271+
@Scopes(Scope.UpdateWorkflowRun)
272+
@Roles(
273+
UserRole.Admin,
274+
UserRole.Copilot,
275+
UserRole.ProjectManager,
276+
UserRole.Reviewer,
277+
UserRole.Submitter,
278+
UserRole.User,
279+
)
280+
@ApiOperation({ summary: 'Update an AIWorkflowRunItem by id' })
281+
@ApiParam({ name: 'workflowId', description: 'The ID of the AI workflow' })
282+
@ApiParam({ name: 'runId', description: 'The ID of the AI workflow run' })
283+
@ApiParam({
284+
name: 'itemId',
285+
description: 'The ID of the AI workflow run item',
286+
})
287+
@ApiBody({
288+
description: 'AIWorkflowRunItem update data',
289+
type: UpdateAiWorkflowRunItemDto,
290+
})
291+
@ApiResponse({
292+
status: 200,
293+
description: 'AIWorkflowRunItem updated successfully.',
294+
})
295+
@ApiResponse({ status: 400, description: 'Bad Request.' })
296+
@ApiResponse({ status: 401, description: 'Unauthorized.' })
297+
@ApiResponse({ status: 403, description: 'Forbidden.' })
298+
@ApiResponse({ status: 404, description: 'Workflow, Run or Item not found.' })
299+
async updateRunItem(
300+
@Param('workflowId') workflowId: string,
301+
@Param('runId') runId: string,
302+
@Param('itemId') itemId: string,
303+
@Body(new ValidationPipe({ whitelist: true, transform: true }))
304+
patchData: UpdateAiWorkflowRunItemDto,
305+
@User() user: JwtUser,
306+
) {
307+
return this.aiWorkflowService.updateRunItem(
308+
workflowId,
309+
runId,
310+
itemId,
311+
patchData,
312+
user,
313+
);
314+
}
315+
269316
@Get('/:workflowId/runs/:runId/items')
270317
@Roles(
271318
UserRole.Admin,

src/api/ai-workflow/ai-workflow.service.ts

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
CreateRunItemCommentDto,
1414
UpdateAiWorkflowDto,
1515
UpdateAiWorkflowRunDto,
16+
UpdateAiWorkflowRunItemDto,
1617
} from '../../dto/aiWorkflow.dto';
1718
import { ScorecardStatus } from 'src/dto/scorecard.dto';
1819
import { JwtUser } from 'src/shared/modules/global/jwt.service';
@@ -489,12 +490,12 @@ export class AiWorkflowService {
489490
UserRole.Submitter,
490491
].map((r) => r.toLowerCase());
491492

492-
const memberRoles = (
493-
await this.resourceApiService.getMemberResourcesRoles(
494-
challengeId,
495-
user.userId,
496-
)
497-
).filter((resource) =>
493+
const userRoles = await this.resourceApiService.getMemberResourcesRoles(
494+
challengeId,
495+
user.userId,
496+
);
497+
498+
const memberRoles = userRoles.filter((resource) =>
498499
requiredRoles.some(
499500
(role) =>
500501
resource.roleName!.toLowerCase().indexOf(role.toLowerCase()) >= 0,
@@ -531,4 +532,82 @@ export class AiWorkflowService {
531532

532533
return items;
533534
}
535+
536+
async updateRunItem(
537+
workflowId: string,
538+
runId: string,
539+
itemId: string,
540+
patchData: UpdateAiWorkflowRunItemDto,
541+
user: JwtUser,
542+
) {
543+
const workflow = await this.prisma.aiWorkflow.findUnique({
544+
where: { id: workflowId },
545+
});
546+
if (!workflow) {
547+
this.logger.error(`Workflow with id ${workflowId} not found.`);
548+
throw new NotFoundException(`Workflow with id ${workflowId} not found.`);
549+
}
550+
551+
const run = await this.prisma.aiWorkflowRun.findUnique({
552+
where: { id: runId },
553+
});
554+
if (!run || run.workflowId !== workflowId) {
555+
this.logger.error(
556+
`Run with id ${runId} not found or does not belong to workflow ${workflowId}.`,
557+
);
558+
throw new NotFoundException(
559+
`Run with id ${runId} not found or does not belong to workflow ${workflowId}.`,
560+
);
561+
}
562+
563+
const runItem = await this.prisma.aiWorkflowRunItem.findUnique({
564+
where: { id: itemId },
565+
});
566+
if (!runItem || runItem.workflowRunId !== runId) {
567+
this.logger.error(
568+
`Run item with id ${itemId} not found or does not belong to run ${runId}.`,
569+
);
570+
throw new NotFoundException(
571+
`Run item with id ${itemId} not found or does not belong to run ${runId}.`,
572+
);
573+
}
574+
575+
const updateData: any = {};
576+
577+
if (patchData.upVotes !== undefined) {
578+
updateData.upVotes = patchData.upVotes;
579+
}
580+
if (patchData.downVotes !== undefined) {
581+
updateData.downVotes = patchData.downVotes;
582+
}
583+
584+
if (!user.isMachine) {
585+
const keys = Object.keys(patchData);
586+
const prohibitedKeys = ['content', 'questionScore'];
587+
if (keys.some((key) => prohibitedKeys.includes(key))) {
588+
throw new BadRequestException(
589+
`Users cannot update one of these properties - ${prohibitedKeys.join(',')}`,
590+
);
591+
}
592+
}
593+
594+
// Update properties which can be updated only via m2m
595+
if (user.isMachine) {
596+
if (patchData.content) {
597+
updateData.content = patchData.content;
598+
}
599+
600+
if (patchData.questionScore) {
601+
updateData.questionScore = patchData.questionScore;
602+
}
603+
}
604+
605+
return this.prisma.aiWorkflowRunItem.update({
606+
where: { id: itemId },
607+
include: {
608+
comments: true,
609+
},
610+
data: updateData,
611+
});
612+
}
534613
}

src/api/review-opportunity/reviewOpportunity.controller.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
CreateReviewOpportunityDto,
2323
QueryReviewOpportunityDto,
2424
ReviewOpportunityResponseDto,
25+
ReviewOpportunitySummaryDto,
2526
UpdateReviewOpportunityDto,
2627
} from 'src/dto/reviewOpportunity.dto';
2728
import { UserRole } from 'src/shared/enums/userRole.enum';
@@ -183,6 +184,27 @@ export class ReviewOpportunityController {
183184
return OkResponse(await this.service.create(authUser, dto));
184185
}
185186

187+
@ApiOperation({
188+
summary: 'Get review opportunity summary',
189+
description:
190+
'Roles: Admin | Scopes: read:review_opportunity, all:review_opportunity',
191+
})
192+
@ApiResponse({
193+
status: 200,
194+
description: 'Review opportunity summary list',
195+
type: ResponseDto<ReviewOpportunitySummaryDto[]>,
196+
})
197+
@ApiResponse({ status: 401, description: 'Unauthorized' })
198+
@ApiResponse({ status: 403, description: 'Forbidden' })
199+
@ApiResponse({ status: 500, description: 'Internal Error' })
200+
@Get('/summary')
201+
@ApiBearerAuth()
202+
@Roles(UserRole.Admin)
203+
@Scopes(Scope.ReadReviewOpportunity, Scope.AllReviewOpportunity)
204+
async summary() {
205+
return OkResponse(await this.service.getSummary());
206+
}
207+
186208
@ApiOperation({
187209
summary: 'Get review opportunity by id',
188210
description:

0 commit comments

Comments
 (0)