Conversation
…M-3926_ai-screening-phase
…M-3906_ai-review-escalation
| } | ||
| } | ||
|
|
||
| :global(.TableCell) { |
There was a problem hiding this comment.
[maintainability]
Using :global(.TableCell) for styling might lead to unintended side effects if .TableCell is used elsewhere in the application. Consider scoping this style more narrowly if possible.
|
|
||
| .statusLocked { | ||
| @extend .resultPill; | ||
| background-color: $black-5; |
There was a problem hiding this comment.
[accessibility]
Ensure that $black-5 and $red-100 provide sufficient contrast for accessibility. Consider using a tool to verify color contrast ratios.
| .statusLocked { | ||
| @extend .resultPill; | ||
| background-color: $black-5; | ||
| color: $red-100; |
There was a problem hiding this comment.
[accessibility]
Ensure that $red-100 provides sufficient contrast against $black-5 for accessibility. Consider using a tool to verify color contrast ratios.
| } | ||
|
|
||
| .resultPending { | ||
| @extend .resultPill; |
There was a problem hiding this comment.
[accessibility]
Ensure that $yellow-25 and $yellow-140 provide sufficient contrast for accessibility. Consider using a tool to verify color contrast ratios.
|
|
||
| .resultPending { | ||
| @extend .resultPill; | ||
| background-color: $yellow-25; |
There was a problem hiding this comment.
[accessibility]
Ensure that $yellow-140 provides sufficient contrast against $yellow-25 for accessibility. Consider using a tool to verify color contrast ratios.
| } from 'react' | ||
| import { Link } from 'react-router-dom' | ||
| import { toast } from 'react-toastify' | ||
| import { useSWRConfig } from 'swr' |
There was a problem hiding this comment.
[correctness]
Ensure that useSWRConfig is used correctly and that the mutate function is called with valid cache keys. Improper usage can lead to cache inconsistencies.
| [aggregatedRows], | ||
| ) | ||
|
|
||
| const escalationDecisionBySubmissionId = useMemo<Map<string, AiReviewEscalationDecision>>( |
There was a problem hiding this comment.
[correctness]
The use of useMemo with a Map can lead to unexpected behavior if the map is mutated after creation. Consider using an immutable data structure or ensure the map is not modified.
| [aggregatedRows, aiReviewDecisionsBySubmissionId, challengeInfo?.id], | ||
| ) | ||
|
|
||
| const isSubmissionAiLocked = useCallback(( |
There was a problem hiding this comment.
[❗❗ correctness]
The isSubmissionAiLocked function relies on the status property being exactly 'AI_FAILED_REVIEW'. Ensure that this status is consistently set and checked throughout the application.
| ) | ||
| } | ||
|
|
||
| const buildEscalateAction = (): JSX.Element | undefined => { |
There was a problem hiding this comment.
[readability]
The buildEscalateAction function contains nested conditions that could be refactored for clarity. Consider breaking it into smaller functions or using early returns to simplify the logic.
| submissionId: args.submissionId, | ||
| submissionLocked: args.submissionLocked, | ||
| }), | ||
| isPaused: () => !args.challengeId && !args.submissionId && !args.aiReviewDecisionId, |
There was a problem hiding this comment.
[correctness]
The isPaused function checks if challengeId, submissionId, and aiReviewDecisionId are all undefined, which pauses the fetch. This could lead to unexpected behavior if the user intends to fetch data with only one of these parameters. Consider revising this logic to ensure it aligns with the intended use cases.
| finalizedAt: string | null | ||
| createdAt: string | ||
| updatedAt: string | ||
| escalations?: AiReviewDecisionEscalation[] |
There was a problem hiding this comment.
[maintainability]
Consider using a non-nullable array for escalations if it is always expected to be an array, even if empty. This can simplify handling of the property by avoiding null checks.
| * @param status - The status value from backend (can be number or string) | ||
| * @returns Normalized status string | ||
| */ | ||
| function normalizeSubmissionStatus(status: BackendSubmissionStatus | string | undefined): string | undefined { |
There was a problem hiding this comment.
[correctness]
The function normalizeSubmissionStatus returns undefined if the input status is neither a number nor a string. Consider whether this behavior is intentional and if it should be handled explicitly in the calling code to avoid potential issues with unexpected undefined values.
| if (params.submissionId) query.set('submissionId', params.submissionId) | ||
| if (params.aiReviewDecisionId) query.set('aiReviewDecisionId', params.aiReviewDecisionId) | ||
| if (params.status) query.set('status', params.status) | ||
| if (params.submissionLocked !== undefined) query.set('submissionLocked', String(params.submissionLocked)) |
There was a problem hiding this comment.
[correctness]
Using String(params.submissionLocked) to convert a boolean to a string might lead to unexpected results if params.submissionLocked is null or undefined. Consider explicitly checking for true or false values to ensure the query parameter is correctly set.
There was a problem hiding this comment.
Pull request overview
Adds UI + client-side wiring in the Review app to support AI review escalation/unlock flows for AI-failed submissions (PM-3906), including new API service helpers, models, and table actions/modals.
Changes:
- Introduce AI review escalation service + hook and export them from the library.
- Extend AI review/submission models to include escalation data and the new
AI_FAILED_REVIEWstatus. - Update
TableReviewto display an “AI Locked” state and provide Escalate/Verify/Unlock actions via new modals.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/apps/review/src/lib/services/index.ts | Exposes the new escalation service from the services barrel. |
| src/apps/review/src/lib/services/aiReviewEscalation.service.ts | Adds list/create/update escalation API calls and cache key builder. |
| src/apps/review/src/lib/models/SubmissionInfo.model.ts | Adds normalized submission status onto the frontend submission model. |
| src/apps/review/src/lib/models/BackendSubmissionStatus.enum.ts | Adds AI_FAILED_REVIEW backend submission status. |
| src/apps/review/src/lib/models/AiReview.model.ts | Adds escalation types and escalations on AiReviewDecision. |
| src/apps/review/src/lib/hooks/useFetchAiReviewEscalations.ts | Adds SWR hook to fetch escalation decisions. |
| src/apps/review/src/lib/hooks/index.ts | Exposes the new hook from the hooks barrel. |
| src/apps/review/src/lib/components/TableReview/TableReview.tsx | Adds AI lock detection + new actions and renders escalation modals. |
| src/apps/review/src/lib/components/TableReview/TableReview.module.scss | Adds styles for AI Locked pill and escalation modals; tweaks spacing. |
| src/apps/review/src/lib/components/TableReview/EscalationModals.tsx | Implements Escalate/Verify/Unlock modal flows and API calls. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
|
|
||
| export type AiReviewEscalationStatus = 'PENDING_APPROVAL' | 'APPROVED' | 'REJECTED' | ||
|
|
||
| export interface AiReviewDecisionEscalation { | ||
| id: string | ||
| aiReviewDecisionId: string | ||
| escalationNotes: string | null | ||
| approverNotes: string | null | ||
| status: AiReviewEscalationStatus | ||
| createdAt: string | ||
| createdBy: string | null | ||
| updatedAt: string | ||
| updatedBy: string | null | ||
| } | ||
|
|
||
| export interface AiReviewEscalationDecision { | ||
| aiReviewDecisionId: string | ||
| submissionId: string | ||
| challengeId: string | undefined | ||
| decisionStatus: string | ||
| submissionLocked: boolean | ||
| escalations: AiReviewDecisionEscalation[] | ||
| } | ||
|
|
There was a problem hiding this comment.
@vas3a please let's use only one location definintion for those
| closeEscalateDialog() | ||
| if (props.challengeId) { | ||
| await refreshChallengeReviewData(props.challengeId) | ||
| } | ||
|
|
||
| await props.revalidateEscalationData() | ||
| } catch (error) { |
| map.set(String(resource.memberId), { | ||
| color: (resource as any).handleColor ?? undefined, |
|
|
||
| export type AiReviewEscalationStatus = 'PENDING_APPROVAL' | 'APPROVED' | 'REJECTED' | ||
|
|
||
| export interface AiReviewDecisionEscalation { | ||
| id: string | ||
| aiReviewDecisionId: string | ||
| escalationNotes: string | null | ||
| approverNotes: string | null | ||
| status: AiReviewEscalationStatus | ||
| createdAt: string | ||
| createdBy: string | null | ||
| updatedAt: string | ||
| updatedBy: string | null | ||
| } | ||
|
|
||
| export interface AiReviewEscalationDecision { | ||
| aiReviewDecisionId: string | ||
| submissionId: string | ||
| challengeId: string | undefined | ||
| decisionStatus: string | ||
| submissionLocked: boolean | ||
| escalations: AiReviewDecisionEscalation[] | ||
| } | ||
|
|
There was a problem hiding this comment.
@vas3a please let's use only one location definintion for those
Related JIRA Ticket:
https://topcoder.atlassian.net/browse/PM-3906
What's in this PR?
Related PR: topcoder-platform/review-api-v6#226