3321 PO Separate reviewers mismatched on language/research area.

Reviewers that can review in all languages will never be mismatched on language.
Added a note for projects that have not specified language to communicate with administrators doing assignment why a reviewer is classified as potentially unsuitable.
This commit is contained in:
Andreas Svanberg 2024-01-22 11:09:04 +01:00
parent 3836a03b7f
commit 1355dbb92c
5 changed files with 65 additions and 14 deletions
core/src
main/java/se/su/dsv/scipro/reviewing
test/java/se/su/dsv/scipro/reviewing
view/src/main/java/se/su/dsv/scipro/admin/pages

@ -7,13 +7,15 @@ import java.util.List;
/**
* Candidates that can review a project.
* @param good reviewers have not met their quota, supervises the language, and matches the projects research area
* @param mismatched reviewers have not met their quota, but does not match the projects research area or language
* @param wrongResearchArea reviewers have not met their quota, but does not match the projects research area
* @param wrongLanguage reviewers have not met their quota, but does not match the projects language
* @param busy reviewers that have met their quota
* @param unavailable reviewers that are not available
*/
public record ReviewerCandidates(
List<Candidate> good,
List<Candidate> mismatched,
List<Candidate> wrongResearchArea,
List<Candidate> wrongLanguage,
List<Candidate> busy,
List<Candidate> unavailable)
{

@ -5,6 +5,7 @@ import se.su.dsv.scipro.project.Project;
import se.su.dsv.scipro.project.ProjectService;
import se.su.dsv.scipro.project.ReviewerAssignedEvent;
import se.su.dsv.scipro.security.auth.roles.Roles;
import se.su.dsv.scipro.system.Language;
import se.su.dsv.scipro.system.Unit;
import se.su.dsv.scipro.system.User;
import se.su.dsv.scipro.system.UserService;
@ -15,6 +16,7 @@ import java.time.Month;
import java.time.Year;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@ -132,7 +134,8 @@ class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAs
List<User> reviewers = userService.findActiveReviewers();
List<ReviewerCandidates.Candidate> good = new ArrayList<>();
List<ReviewerCandidates.Candidate> mismatched = new ArrayList<>();
List<ReviewerCandidates.Candidate> wrongLanguage = new ArrayList<>();
List<ReviewerCandidates.Candidate> wrongResearchArea = new ArrayList<>();
List<ReviewerCandidates.Candidate> busy = new ArrayList<>();
List<ReviewerCandidates.Candidate> unavailable = new ArrayList<>();
for (User reviewer : reviewers) {
@ -144,13 +147,17 @@ class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAs
if (target > 0) {
if (assigned < target) {
boolean canSuperviseProjectsLanguage = reviewer.getLanguages().contains(project.getLanguage());
boolean canReviewAllLanguages = reviewer.getLanguages().containsAll(EnumSet.allOf(Language.class));
boolean canSuperviseProjectsLanguage = canReviewAllLanguages || reviewer.getLanguages().contains(project.getLanguage());
boolean matchingResearchArea = reviewer.getResearchAreas().contains(project.getResearchArea());
if (canSuperviseProjectsLanguage && matchingResearchArea) {
good.add(candidate);
}
else {
mismatched.add(candidate);
else if (!canSuperviseProjectsLanguage) {
wrongLanguage.add(candidate);
}
else if (!matchingResearchArea) {
wrongResearchArea.add(candidate);
}
}
else {
@ -162,7 +169,7 @@ class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAs
}
}
return new ReviewerCandidates(good, mismatched, busy, unavailable);
return new ReviewerCandidates(good, wrongResearchArea, wrongLanguage, busy, unavailable);
}
private int getPeriodTarget(ReviewerTarget reviewerTarget, LocalDate date) {

@ -91,7 +91,7 @@ class ReviewerCapacityServiceImplTest extends IntegrationTest {
ReviewerCandidates candidates = service.getCandidatesToReview(project, SOME_DATE_IN_AUTUMN);
// then
assertTrue(candidates.mismatched().stream().anyMatch(c -> c.reviewer().equals(reviewer)));
assertTrue(candidates.wrongLanguage().stream().anyMatch(c -> c.reviewer().equals(reviewer)));
}
@Test
@ -105,7 +105,7 @@ class ReviewerCapacityServiceImplTest extends IntegrationTest {
ReviewerCandidates candidates = service.getCandidatesToReview(project, SOME_DATE_IN_SPRING);
// then
assertTrue(candidates.mismatched().stream().anyMatch(c -> c.reviewer().equals(reviewer)));
assertTrue(candidates.wrongResearchArea().stream().anyMatch(c -> c.reviewer().equals(reviewer)));
}
@Test
@ -137,6 +137,21 @@ class ReviewerCapacityServiceImplTest extends IntegrationTest {
assertEquals(2, candidates);
}
@Test
void a_reviewer_that_can_review_all_languages_is_suitable_even_if_no_language_is_set_on_the_project() {
// given
reviewer.setLanguages(EnumSet.allOf(Language.class));
reviewer.addResearchArea(researchArea);
project.setLanguage(null);
service.assignTarget(reviewer, springTarget(1));
// when
ReviewerCandidates candidates = service.getCandidatesToReview(project, SOME_DATE_IN_SPRING);
// then
assertTrue(candidates.good().stream().anyMatch(c -> c.reviewer().equals(reviewer)));
}
private static Target springTarget(final int target) {
return new Target(YEAR_2024, target, 0, "");
}

@ -36,12 +36,26 @@
</div>
</wicket:enclosure>
<wicket:enclosure child="mismatched_candidates">
<h3>Mismatched candidates</h3>
<wicket:enclosure child="wrong_research_area">
<h3>Unsuitable due to research area</h3>
<p>
These reviewers have not met their review quota, but their language or research areas does not match the project's.
These reviewers have not met their review quota, but their research areas does not match the project's.
</p>
<div class="card bg-warning bg-opacity-50 mb-3" wicket:id="mismatched_candidates">
<div class="card bg-warning bg-opacity-50 mb-3" wicket:id="wrong_research_area">
<wicket:container wicket:id="details"/>
</div>
</wicket:enclosure>
<wicket:enclosure child="wrong_language">
<h3>Unsuitable due to language</h3>
<p>
These reviewers have not met their review quota, but they cannot supervise in the thesis language.
</p>
<p class="alert alert-warning" wicket:id="no_language_set">
The language of the thesis has not been specified. SciPro can not determine if the reviewer is
suitable or not and therefore classifies them as unsuitable.
</p>
<div class="card bg-danger text-white opacity-50 mb-3" wicket:id="wrong_language">
<wicket:container wicket:id="details"/>
</div>
</wicket:enclosure>

@ -94,7 +94,20 @@ public class AdminAssignReviewerPage extends AbstractAdminProjectPage {
item.add(new ReviewerCard("details", item.getModel()));
}
});
add(new AutoHidingListView<>("mismatched_candidates", reviewerCandidates.map(ReviewerCandidates::mismatched)) {
add(new AutoHidingListView<>("wrong_research_area", reviewerCandidates.map(ReviewerCandidates::wrongResearchArea)) {
@Override
protected void populateItem(ListItem<ReviewerCandidates.Candidate> item) {
item.add(new ReviewerCard("details", item.getModel()));
}
});
add(new WebMarkupContainer("no_language_set") {
@Override
protected void onConfigure() {
super.onConfigure();
setVisible(projectModel.getObject().getLanguage() == null);
}
});
add(new AutoHidingListView<>("wrong_language", reviewerCandidates.map(ReviewerCandidates::wrongLanguage)) {
@Override
protected void populateItem(ListItem<ReviewerCandidates.Candidate> item) {
item.add(new ReviewerCard("details", item.getModel()));