Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/apps/work/src/lib/models/Reviewer.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface DefaultReviewer {
memberReviewerCount?: number
opportunityType?: string
phaseId?: string
phaseName?: string
roleId?: string
scorecardId?: string
shouldOpenOpportunity?: boolean
Expand Down
4 changes: 2 additions & 2 deletions src/apps/work/src/lib/services/challenges.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('fetchDefaultReviewers', () => {
{
isMemberReview: true,
memberReviewerCount: 1,
phaseId: 'phase-1',
phaseName: 'Review',
roleId: 'role-1',
scorecardId: 'scorecard-1',
shouldOpenOpportunity: true,
Expand All @@ -119,7 +119,7 @@ describe('fetchDefaultReviewers', () => {
{
isMemberReview: true,
memberReviewerCount: 1,
phaseId: 'phase-1',
phaseName: 'Review',
roleId: 'role-1',
scorecardId: 'scorecard-1',
shouldOpenOpportunity: true,
Expand Down
1 change: 1 addition & 0 deletions src/apps/work/src/lib/services/challenges.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ function normalizeDefaultReviewer(
opportunityType: toOptionalString((reviewer as Record<string, unknown>).opportunityType)
|| toOptionalString((reviewer as Record<string, unknown>).type),
phaseId: toOptionalString(reviewer.phaseId),
phaseName: toOptionalString(reviewer.phaseName),
roleId: toOptionalString(reviewer.roleId),
scorecardId: toOptionalString((reviewer as Record<string, unknown>).scorecardId),
shouldOpenOpportunity: toOptionalBoolean((reviewer as Record<string, unknown>).shouldOpenOpportunity),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ The form uses `challengeBasicInfoSchema` from `src/apps/work/src/lib/schemas/cha
from `is_test_challenge`, and explicitly persists metadata value `true` or `false`. Test
challenges do not generate payments, and authorized modifiers can delete them after they reach
a completed or cancelled status.
- `ReviewersField`: hidden for `Task` and `Marathon Match` challenges because manual reviewer assignment is handled elsewhere. The simplified Design Challenge review section fetches defaults for the selected timeline template, then repairs missing, duplicate, and stale hidden reviewer rows while exposing the Screening and Checkpoint Screening member selectors. Checkpoint Review, Review, and Approval are private and automatically assigned to the selected copilot during save; Design Challenge creation and saving highlight the Copilot field when no copilot is selected. On the full human-review tab, each manual reviewer card keeps the legacy review-type dropdown, backfills missing legacy review-type values from the matching default reviewer or iterative-review phase fallback, and each manual reviewer phase selector hides registration/submission phases and any phase already assigned on another manual reviewer card while preserving the card's current selection. When default reviewer metadata is missing, stale, or already covered by existing rows, `Add reviewer` starts from the next unassigned selectable reviewer phase, preferring review phases before approval or screening phases, so single-round Design schedules add the Approver row instead of a registration/submission or duplicate reviewer row. Manual reviewer counts are capped before rendering member assignment controls so closed public opportunities cannot create an unbounded number of member selectors. The full Design reviewer editor keeps the public review opportunity checkbox disabled and unchecked. Screening and Checkpoint Screening member selectors remain available but are optional so a copilot can assign the Screener or Checkpoint Screener after launch.
- `ReviewersField`: hidden for `Task` and `Marathon Match` challenges because manual reviewer assignment is handled elsewhere. The simplified Design Challenge review section fetches defaults for the selected timeline template, resolves the API's phase-name-only defaults against the challenge phases, then repairs missing, duplicate, and stale hidden reviewer rows while exposing the Screening and Checkpoint Screening member selectors. Checkpoint Review, Review, and Approval are private and automatically assigned to the selected copilot during save; Design Challenge creation and saving highlight the Copilot field when no copilot is selected. On the full human-review tab, each manual reviewer card keeps the legacy review-type dropdown, backfills missing legacy review-type values from the matching default reviewer or iterative-review phase fallback, and each manual reviewer phase selector hides registration/submission phases and any phase already assigned on another manual reviewer card while preserving the card's current selection. When default reviewer metadata is missing, stale, or already covered by existing rows, `Add reviewer` starts from the next unassigned selectable reviewer phase, preferring review phases before approval or screening phases, so single-round Design schedules add the Approver row instead of a registration/submission or duplicate reviewer row. Manual reviewer counts are capped before rendering member assignment controls so closed public opportunities cannot create an unbounded number of member selectors. The full Design reviewer editor keeps the public review opportunity checkbox disabled and unchecked. Screening and Checkpoint Screening member selectors remain available but are optional so a copilot can assign the Screener or Checkpoint Screener after launch.
- `Submission Settings`: shown for Design `Challenge` and Design `First2Finish` types, and contains the final-deliverables, stock-art, and submission-limit compatibility fields.
- `RegisteredMemberDownloadField`: shown in Advanced Options for every created challenge type. The radio group persists `allowAllRegistrantsToDownloadWinningSubmissions` as the exact string `true` for all challenge registrants or `false` for passing submitters only. New Development challenges default to passing submitters; other new challenges, including Design, default to all registrants. Existing challenges without the metadata retain passing-submitter-only access.
- `FinalDeliverablesField`: design-challenge file-type editor that persists the legacy `fileTypes` metadata payload used on challenge draft pages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ describe('HumanReviewTab', () => {
.not.toBeNull()
})

it('repairs hidden legacy rows and assigns private reviewers to the copilot', async () => {
it('resolves phase-name-only defaults and assigns private reviewers to the copilot', async () => {
mockedUseFetchChallengeTracks.mockReturnValue({
tracks: [{
id: 'track-1',
Expand All @@ -1024,35 +1024,35 @@ describe('HumanReviewTab', () => {
{
isMemberReview: true,
memberReviewerCount: 1,
phaseId: 'checkpoint-review-phase-id',
phaseName: 'Checkpoint Review',
scorecardId: 'checkpoint-review-scorecard-id',
shouldOpenOpportunity: false,
},
{
isMemberReview: true,
memberReviewerCount: 1,
phaseId: 'review-phase-id',
phaseName: 'Review',
scorecardId: 'review-scorecard-id',
shouldOpenOpportunity: false,
},
{
isMemberReview: true,
memberReviewerCount: 1,
phaseId: 'checkpoint-screening-phase-id',
phaseName: 'Checkpoint Screening',
scorecardId: 'checkpoint-screening-scorecard-id',
shouldOpenOpportunity: false,
},
{
isMemberReview: true,
memberReviewerCount: 1,
phaseId: 'screening-phase-id',
phaseName: 'Screening',
scorecardId: 'screening-scorecard-id',
shouldOpenOpportunity: false,
},
{
isMemberReview: true,
memberReviewerCount: 1,
phaseId: 'approval-phase-id',
phaseName: 'Approval',
scorecardId: 'approval-scorecard-id',
shouldOpenOpportunity: false,
},
Expand All @@ -1063,27 +1063,27 @@ describe('HumanReviewTab', () => {
{
id: 'checkpoint-review-scorecard-id',
name: 'Checkpoint Review Scorecard',
phaseId: 'checkpoint-review-phase-id',
type: 'CHECKPOINT_REVIEW',
},
{
id: 'review-scorecard-id',
name: 'Review Scorecard',
phaseId: 'review-phase-id',
type: 'REVIEW',
},
{
id: 'checkpoint-screening-scorecard-id',
name: 'Checkpoint Screening Scorecard',
phaseId: 'checkpoint-screening-phase-id',
type: 'CHECKPOINT_SCREENING',
},
{
id: 'screening-scorecard-id',
name: 'Screening Scorecard',
phaseId: 'screening-phase-id',
type: 'SCREENING',
},
{
id: 'approval-scorecard-id',
name: 'Approval Scorecard',
phaseId: 'approval-phase-id',
type: 'APPROVAL',
},
])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,16 +546,26 @@ function getFallbackReviewerPhaseId(
|| getChallengePhaseId(phases[0])
}

/**
* Resolves the challenge phase targeted by a default reviewer row.
*
* @param defaultReviewer default reviewer configuration returned by the challenge API.
* @param phases current challenge phases used to validate ids and resolve phase-name-only defaults.
* @returns the matching challenge phase template id, or a selectable fallback when no match exists.
* @remarks Timeline-specific reviewer defaults commonly provide `phaseName` with a null `phaseId`,
* so the name must be resolved before using the legacy fallback.
* @throws Does not throw.
*/
function getReviewerPhaseId(
defaultReviewer: DefaultReviewer | undefined,
phases: ChallengeEditorFormData['phases'],
): string | undefined {
const phaseRows = Array.isArray(phases)
? phases
: []
const defaultPhaseId = normalizeText(defaultReviewer?.phaseId)

if (defaultPhaseId) {
const phaseRows = Array.isArray(phases)
? phases
: []
const hasMatchingPhase = !phaseRows.length
|| phaseRows.some(phase => getChallengePhaseId(phase) === defaultPhaseId)

Expand All @@ -564,6 +574,18 @@ function getReviewerPhaseId(
}
}

const defaultPhaseName = normalizeKey(defaultReviewer?.phaseName)
if (defaultPhaseName) {
const matchingPhase = phaseRows.find(phase => (
normalizeKey(phase?.name) === defaultPhaseName
))
const matchingPhaseId = getChallengePhaseId(matchingPhase)

if (matchingPhaseId) {
return matchingPhaseId
}
}

return getFallbackReviewerPhaseId(phases)
}

Expand Down
Loading