From 64c952545e70589637384c5387bf89740e49226d Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 4 Dec 2025 15:54:15 +0100 Subject: [PATCH 1/3] fix: show the duplicate reviewer config error on saving --- .../ChallengeReviewer-Field/index.js | 35 ---------------- src/components/ChallengeEditor/index.js | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js b/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js index 288d4d1f..5fbafd29 100644 --- a/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js +++ b/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js @@ -444,41 +444,6 @@ class ChallengeReviewerField extends Component { const isAIReviewer = this.isAIReviewer(defaultTrackReviewer) - // Prevent adding a second manual (member) reviewer for the same phase. - // If the default phase already has a manual reviewer, attempt to find another - // suitable review phase that does not yet have a manual reviewer and use it. - if (!isAIReviewer && defaultPhaseId) { - const existsManualForPhase = currentReviewers.some(r => (r.isMemberReview !== false) && (r.phaseId === defaultPhaseId)) - if (existsManualForPhase) { - const possibleAlternatePhase = (challenge.phases || []).find(p => { - const rawName = p.name ? p.name : '' - const phaseName = rawName.toLowerCase() - const phaseWithoutHyphens = phaseName.replace(/[-\s]/g, '') - const acceptedPhases = ['review', 'screening', 'checkpointscreening', 'approval', 'postmortem'] - const isSubmissionPhase = phaseName.includes('submission') - const acceptable = acceptedPhases.includes(phaseWithoutHyphens) && !isSubmissionPhase - - if (!acceptable) return false - - const phaseId = p.phaseId || p.id - const used = currentReviewers.some(r => (r.isMemberReview !== false) && (r.phaseId === phaseId)) - return !used - }) - - if (possibleAlternatePhase) { - defaultPhaseId = possibleAlternatePhase.phaseId || possibleAlternatePhase.id - if (this.state.error) this.setState({ error: null }) - } else { - const phase = (challenge.phases || []).find(p => (p.id === defaultPhaseId) || (p.phaseId === defaultPhaseId)) - const phaseName = phase ? (phase.name || defaultPhaseId) : defaultPhaseId - this.setState({ - error: `A manual reviewer configuration already exists for phase '${phaseName}'` - }) - return - } - } - } - // For AI reviewers, get scorecardId from the workflow if available let scorecardId = '' if (isAIReviewer) { diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index da51eb04..73fe1f46 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -897,11 +897,52 @@ class ChallengeEditor extends Component { return !(isRequiredMissing || _.isEmpty(this.state.currentTemplate)) } + // Return array of phase names that have more than one manual (member) reviewer configured. + // If none, returns empty array. + getDuplicateManualReviewerPhases () { + const { challenge } = this.state + const reviewers = (challenge && challenge.reviewers) || [] + const phases = (challenge && challenge.phases) || [] + + const counts = {} + reviewers.forEach(r => { + // count only manual (member) reviewers; AI reviewers have isMemberReview === false + if (r && (r.isMemberReview !== false) && r.phaseId) { + const pid = String(r.phaseId) + counts[pid] = (counts[pid] || 0) + 1 + } + }) + + const duplicatedPhaseIds = Object.keys(counts).filter(pid => counts[pid] > 1) + if (duplicatedPhaseIds.length === 0) return [] + + return duplicatedPhaseIds.map(pid => { + const p = phases.find(ph => String(ph.phaseId || ph.id) === pid) + return p ? (p.name || pid) : pid + }) + } + validateChallenge () { + // First run the existing validations if (this.isValidChallenge()) { + // Additional validation: block saving draft if there are duplicate manual reviewer configs per phase + const duplicates = this.getDuplicateManualReviewerPhases() + if (duplicates && duplicates.length > 0) { + const message = `Duplicate manual reviewer configuration found for phase${duplicates.length > 1 ? 's' : ''}: ${duplicates.join(', ')}. Only one manual reviewer configuration is allowed per phase.` + // Surface the message to the top-level error so the UI shows it and prevent saving + this.setState({ hasValidationErrors: true, error: message }) + return false + } + + // clear any previous error related to this validation + if (this.state.error) { + this.setState({ error: null }) + } this.setState({ hasValidationErrors: false }) return true } + + // If base validation failed, mark submitTriggered so fields show their validation errors this.setState(prevState => ({ ...prevState, challenge: { From 060554c73cd037d9176406365280a3d370b3a2f9 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 4 Dec 2025 15:56:54 +0100 Subject: [PATCH 2/3] fix: show the duplicate reviewer config error on saving --- src/components/ChallengeEditor/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 73fe1f46..7a318537 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -906,7 +906,6 @@ class ChallengeEditor extends Component { const counts = {} reviewers.forEach(r => { - // count only manual (member) reviewers; AI reviewers have isMemberReview === false if (r && (r.isMemberReview !== false) && r.phaseId) { const pid = String(r.phaseId) counts[pid] = (counts[pid] || 0) + 1 @@ -923,18 +922,15 @@ class ChallengeEditor extends Component { } validateChallenge () { - // First run the existing validations if (this.isValidChallenge()) { // Additional validation: block saving draft if there are duplicate manual reviewer configs per phase const duplicates = this.getDuplicateManualReviewerPhases() if (duplicates && duplicates.length > 0) { const message = `Duplicate manual reviewer configuration found for phase${duplicates.length > 1 ? 's' : ''}: ${duplicates.join(', ')}. Only one manual reviewer configuration is allowed per phase.` - // Surface the message to the top-level error so the UI shows it and prevent saving this.setState({ hasValidationErrors: true, error: message }) return false } - // clear any previous error related to this validation if (this.state.error) { this.setState({ error: null }) } @@ -942,7 +938,6 @@ class ChallengeEditor extends Component { return true } - // If base validation failed, mark submitTriggered so fields show their validation errors this.setState(prevState => ({ ...prevState, challenge: { From a72fcab0c316feab83a90f61f6908562e957c7eb Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 4 Dec 2025 16:43:27 +0100 Subject: [PATCH 3/3] fix: show the duplicate reviewer config error on saving --- src/components/ChallengeEditor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 7a318537..6516403f 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -926,7 +926,7 @@ class ChallengeEditor extends Component { // Additional validation: block saving draft if there are duplicate manual reviewer configs per phase const duplicates = this.getDuplicateManualReviewerPhases() if (duplicates && duplicates.length > 0) { - const message = `Duplicate manual reviewer configuration found for phase${duplicates.length > 1 ? 's' : ''}: ${duplicates.join(', ')}. Only one manual reviewer configuration is allowed per phase.` + const message = `Duplicate manual reviewer configuration found for phase(s): ${duplicates.join(', ')}. Only one manual reviewer configuration is allowed per phase.` this.setState({ hasValidationErrors: true, error: message }) return false }