Basic GUI for assigning reviewers
This commit is contained in:
parent
33e0e033b5
commit
1a9a1a13d2
core/src
main
java/se/su/dsv/scipro
reviewing
ReviewerAssignmentService.javaReviewerCandidates.javaReviewerCapacityServiceImpl.javaReviewerTargetRepository.javaReviewerTargetRepositoryImpl.java
system
resources/db/migration
test/java/se/su/dsv/scipro/reviewing
view/src/main/java/se/su/dsv/scipro/admin/pages
@ -2,6 +2,8 @@ package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public interface ReviewerAssignmentService {
|
||||
ReviewerCandidates getCandidatesToReview(Project project);
|
||||
ReviewerCandidates getCandidatesToReview(Project project, LocalDate date);
|
||||
}
|
||||
|
@ -4,7 +4,19 @@ import se.su.dsv.scipro.system.User;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ReviewerCandidates(List<Candidate> goodCandidates) {
|
||||
/**
|
||||
* 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 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> busy,
|
||||
List<Candidate> unavailable)
|
||||
{
|
||||
public record Candidate(User reviewer, int target, int assigned) {
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,21 @@ package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.system.UserService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAssignmentService {
|
||||
private final ReviewerTargetRepository reviewerTargetRepository;
|
||||
private final UserService userService;
|
||||
|
||||
@Inject
|
||||
ReviewerCapacityServiceImpl(ReviewerTargetRepository reviewerTargetRepository) {
|
||||
ReviewerCapacityServiceImpl(ReviewerTargetRepository reviewerTargetRepository, UserService userService) {
|
||||
this.reviewerTargetRepository = reviewerTargetRepository;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -35,8 +39,38 @@ class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAs
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewerCandidates getCandidatesToReview(Project project) {
|
||||
return new ReviewerCandidates(
|
||||
List.of(new ReviewerCandidates.Candidate(project.getHeadSupervisor(), 5, 2)));
|
||||
public ReviewerCandidates getCandidatesToReview(Project project, LocalDate date) {
|
||||
List<User> reviewers = userService.findActiveReviewers();
|
||||
|
||||
List<ReviewerCandidates.Candidate> good = new ArrayList<>();
|
||||
List<ReviewerCandidates.Candidate> mismatched = new ArrayList<>();
|
||||
List<ReviewerCandidates.Candidate> busy = new ArrayList<>();
|
||||
List<ReviewerCandidates.Candidate> unavailable = new ArrayList<>();
|
||||
for (User reviewer : reviewers) {
|
||||
int target = getTarget(reviewer, date);
|
||||
int assigned = 1; //TODO
|
||||
ReviewerCandidates.Candidate candidate = new ReviewerCandidates.Candidate(reviewer, target, assigned);
|
||||
|
||||
if (target > 0) {
|
||||
if (assigned < target) {
|
||||
boolean canSuperviseProjectsLanguage = reviewer.getLanguages().contains(project.getLanguage());
|
||||
boolean matchingResearchArea = reviewer.getResearchAreas().contains(project.getResearchArea());
|
||||
if (canSuperviseProjectsLanguage && matchingResearchArea) {
|
||||
good.add(candidate);
|
||||
}
|
||||
else {
|
||||
mismatched.add(candidate);
|
||||
}
|
||||
}
|
||||
else {
|
||||
busy.add(candidate);
|
||||
}
|
||||
}
|
||||
else {
|
||||
unavailable.add(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
return new ReviewerCandidates(good, mismatched, busy, unavailable);
|
||||
}
|
||||
}
|
||||
|
@ -9,4 +9,6 @@ public interface ReviewerTargetRepository {
|
||||
void save(ReviewerTarget reviewerTarget);
|
||||
|
||||
List<ReviewerTarget> getReviewerTargets(User reviewer, LocalDate date);
|
||||
|
||||
List<ReviewerTarget> getAllReviewerTargets(LocalDate date);
|
||||
}
|
||||
|
@ -33,4 +33,12 @@ public class ReviewerTargetRepositoryImpl extends AbstractRepository implements
|
||||
.and(QReviewerTarget.reviewerTarget.toDate.goe(date)))
|
||||
.fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ReviewerTarget> getAllReviewerTargets(LocalDate date) {
|
||||
return from(QReviewerTarget.reviewerTarget)
|
||||
.where(QReviewerTarget.reviewerTarget.fromDate.loe(date)
|
||||
.and(QReviewerTarget.reviewerTarget.toDate.goe(date)))
|
||||
.fetch();
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,15 @@ import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public interface UserService extends GenericService<User, Long> ,FilteredService<User, Long, UserService.Filter> {
|
||||
User findByUsername(String username);
|
||||
User findByExternalIdentifier(Integer externalIdentifier);
|
||||
|
||||
List<User> findActiveReviewers();
|
||||
|
||||
class Filter implements Serializable {
|
||||
private Collection<Roles> roles = EnumSet.allOf(Roles.class);
|
||||
private String name;
|
||||
|
@ -7,6 +7,7 @@ import org.springframework.data.domain.Pageable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -39,6 +40,11 @@ public class UserServiceImpl extends AbstractServiceImpl<User,Long> implements U
|
||||
return findOne(QUser.user.identifier.eq(externalIdentifier));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> findActiveReviewers() {
|
||||
return findAll(QUser.user.roles.any().eq(Roles.REVIEWER).and(QUser.user.activeAsSupervisor.isTrue()));
|
||||
}
|
||||
|
||||
private static Predicate fromParams(Filter filter) {
|
||||
BooleanBuilder booleanBuilder = new BooleanBuilder();
|
||||
if (!filter.getRoles().isEmpty()) {
|
||||
|
@ -0,0 +1,12 @@
|
||||
CREATE TABLE IF NOT EXISTS `reviewer_target` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`dateCreated` DATETIME NOT NULL,
|
||||
`lastModified` DATETIME NOT NULL,
|
||||
`version` INT NOT NULL,
|
||||
`reviewer_id` BIGINT NOT NULL,
|
||||
`target` INT NOT NULL,
|
||||
`from_date` DATE NOT NULL,
|
||||
`to_date` DATE NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `FK_reviewer_target_user` FOREIGN KEY (`reviewer_id`) REFERENCES `user` (`id`)
|
||||
);
|
@ -0,0 +1,158 @@
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.system.DegreeType;
|
||||
import se.su.dsv.scipro.system.Language;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ResearchArea;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.IntegrationTest;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ReviewerCapacityServiceImplTest extends IntegrationTest {
|
||||
|
||||
private static DateRange VT24 = new DateRange(LocalDate.of(2024, Month.JANUARY, 1), LocalDate.of(2024, Month.JUNE, 30));
|
||||
|
||||
@Inject
|
||||
ReviewerCapacityServiceImpl service;
|
||||
|
||||
private User reviewer;
|
||||
private Project project;
|
||||
private ResearchArea researchArea;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
User reviewer = User.builder()
|
||||
.firstName("John")
|
||||
.lastName("Doe")
|
||||
.emailAddress("john@example.com")
|
||||
.roles(EnumSet.of(Roles.REVIEWER))
|
||||
.build();
|
||||
reviewer.setActiveAsSupervisor(true);
|
||||
this.reviewer = save(reviewer);
|
||||
|
||||
User supervisor = User.builder()
|
||||
.firstName("Bob")
|
||||
.lastName("Doe")
|
||||
.emailAddress("bob@example.com")
|
||||
.roles(EnumSet.of(Roles.SUPERVISOR))
|
||||
.build();
|
||||
supervisor.setActiveAsSupervisor(true);
|
||||
save(supervisor);
|
||||
|
||||
ResearchArea researchArea = new ResearchArea();
|
||||
researchArea.setTitle("AI");
|
||||
this.researchArea = save(researchArea);
|
||||
|
||||
ProjectType bachelor = new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor");
|
||||
save(bachelor);
|
||||
|
||||
Project project = Project.builder()
|
||||
.title("A project")
|
||||
.projectType(bachelor)
|
||||
.startDate(LocalDate.of(2024, Month.JANUARY, 1))
|
||||
.headSupervisor(supervisor)
|
||||
.build();
|
||||
project.setLanguage(Language.SWEDISH);
|
||||
project.setResearchArea(researchArea);
|
||||
this.project = save(project);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saves_assigned_targets() {
|
||||
// when
|
||||
int target = 8;
|
||||
|
||||
service.assignTarget(reviewer, VT24, target);
|
||||
|
||||
// then
|
||||
assertEquals(target, service.getTarget(reviewer, VT24.from()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void given_multiple_overlapping_period_the_target_is_the_highest() {
|
||||
// given
|
||||
int target = 8;
|
||||
int higherTarget = 10;
|
||||
|
||||
service.assignTarget(reviewer, VT24, target);
|
||||
service.assignTarget(reviewer, VT24, higherTarget);
|
||||
|
||||
// when
|
||||
int actual = service.getTarget(reviewer, VT24.from());
|
||||
|
||||
// then
|
||||
assertEquals(higherTarget, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void target_is_zero_if_nothing_is_assigned() {
|
||||
// when
|
||||
int actual = service.getTarget(reviewer, VT24.from());
|
||||
|
||||
// then
|
||||
assertEquals(0, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void reviewer_is_unavailable_without_target() {
|
||||
// when
|
||||
ReviewerCandidates candidates = service.getCandidatesToReview(project, VT24.from());
|
||||
|
||||
// then
|
||||
assertTrue(candidates.unavailable().stream().anyMatch(c -> c.reviewer().equals(reviewer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reviewer_with_target_but_wrong_language_is_mismatched() {
|
||||
// given
|
||||
reviewer.setLanguages(EnumSet.noneOf(Language.class));
|
||||
reviewer.addResearchArea(researchArea);
|
||||
service.assignTarget(reviewer, VT24, 3);
|
||||
|
||||
// when
|
||||
ReviewerCandidates candidates = service.getCandidatesToReview(project, VT24.from());
|
||||
|
||||
// then
|
||||
assertTrue(candidates.mismatched().stream().anyMatch(c -> c.reviewer().equals(reviewer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reviewer_with_target_but_wrong_research_area_is_mismatched() {
|
||||
// given
|
||||
reviewer.getResearchAreas().clear();
|
||||
reviewer.setLanguages(EnumSet.allOf(Language.class));
|
||||
service.assignTarget(reviewer, VT24, 3);
|
||||
|
||||
// when
|
||||
ReviewerCandidates candidates = service.getCandidatesToReview(project, VT24.from());
|
||||
|
||||
// then
|
||||
assertTrue(candidates.mismatched().stream().anyMatch(c -> c.reviewer().equals(reviewer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reviewer_with_target_matching_language_and_research_area_is_good() {
|
||||
// given
|
||||
reviewer.setLanguages(EnumSet.allOf(Language.class));
|
||||
reviewer.addResearchArea(researchArea);
|
||||
service.assignTarget(reviewer, VT24, 3);
|
||||
|
||||
// when
|
||||
ReviewerCandidates candidates = service.getCandidatesToReview(project, VT24.from());
|
||||
|
||||
// then
|
||||
assertTrue(candidates.good().stream().anyMatch(c -> c.reviewer().equals(reviewer)));
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.IntegrationTest;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ReviewerCapacityServiceTest extends IntegrationTest {
|
||||
|
||||
private static DateRange VT24 = new DateRange(LocalDate.of(2024, Month.JANUARY, 1), LocalDate.of(2024, Month.JUNE, 30));
|
||||
|
||||
@Inject
|
||||
ReviewerCapacityService service;
|
||||
|
||||
private User reviewer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
User reviewer = User.builder()
|
||||
.firstName("John")
|
||||
.lastName("Doe")
|
||||
.emailAddress("john@example.com")
|
||||
.build();
|
||||
this.reviewer = save(reviewer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saves_assigned_targets() {
|
||||
// when
|
||||
int target = 8;
|
||||
|
||||
service.assignTarget(reviewer, VT24, target);
|
||||
|
||||
// then
|
||||
assertEquals(target, service.getTarget(reviewer, VT24.from()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void given_multiple_overlapping_period_the_target_is_the_highest() {
|
||||
// given
|
||||
int target = 8;
|
||||
int higherTarget = 10;
|
||||
|
||||
service.assignTarget(reviewer, VT24, target);
|
||||
service.assignTarget(reviewer, VT24, higherTarget);
|
||||
|
||||
// when
|
||||
int actual = service.getTarget(reviewer, VT24.from());
|
||||
|
||||
// then
|
||||
assertEquals(higherTarget, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void target_is_zero_if_nothing_is_assigned() {
|
||||
// when
|
||||
int actual = service.getTarget(reviewer, VT24.from());
|
||||
|
||||
// then
|
||||
assertEquals(0, actual);
|
||||
}
|
||||
|
||||
}
|
@ -2,9 +2,10 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<wicket:extend>
|
||||
<h2>Reviewer assignment</h2>
|
||||
<div class="row">
|
||||
<div class="col-12 col-xl-4" wicket:id="project_details">
|
||||
<dl>
|
||||
<dl class="sticky-xl-top">
|
||||
<dt>Project</dt>
|
||||
<dd wicket:id="title"></dd>
|
||||
|
||||
@ -20,24 +21,121 @@
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-4 col-md-6" wicket:id="reviewers">
|
||||
<div class="card bg-success text-white bg-opacity-50" wicket:id="good_candidates">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<img class="img-fluid rounded-start" wicket:id="image">
|
||||
</div>
|
||||
<div class="col my-auto">
|
||||
<div class="card-body p-0">
|
||||
<h4 class="card-title text-white" wicket:id="user"></h4>
|
||||
<span wicket:id="assigned"></span> / <span wicket:id="target"></span>
|
||||
<wicket:enclosure child="good_candidates">
|
||||
<h3>Good candidates</h3>
|
||||
<p>
|
||||
These reviewers have not met their review quota, can supervise in the thesis language,
|
||||
and their research area matches the project's.
|
||||
</p>
|
||||
<div class="card bg-success text-white bg-opacity-50 mb-3" wicket:id="good_candidates">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<img class="img-fluid rounded-start" wicket:id="image">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto my-auto">
|
||||
<div class="card-body py-0">
|
||||
<button class="btn btn-success">Assign</button>
|
||||
<div class="col my-auto">
|
||||
<div class="card-body p-0">
|
||||
<h4 class="card-title text-white" wicket:id="user"></h4>
|
||||
<span wicket:id="assigned"></span> / <span wicket:id="target"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto my-auto">
|
||||
<div class="card-body py-0">
|
||||
<button class="btn btn-success">Assign</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:enclosure>
|
||||
|
||||
<wicket:enclosure child="mismatched_candidates">
|
||||
<h3>Mismatched candidates</h3>
|
||||
<p>
|
||||
These reviewers have not met their review quota, but their language or research areas does not match the project's.
|
||||
</p>
|
||||
<div class="card bg-warning opacity-50 mb-3" wicket:id="mismatched_candidates">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<img class="img-fluid rounded-start" wicket:id="image">
|
||||
</div>
|
||||
<div class="col my-auto">
|
||||
<div class="card-body p-0">
|
||||
<h4 class="card-title" wicket:id="user"></h4>
|
||||
<span wicket:id="assigned"></span> / <span wicket:id="target"></span>
|
||||
<div>
|
||||
Research areas:
|
||||
<ul class="csv">
|
||||
<li wicket:id="research_areas">
|
||||
<wicket:container wicket:id="research_area"></wicket:container>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
Languages:
|
||||
<ul class="csv">
|
||||
<li wicket:id="languages">
|
||||
<wicket:container wicket:id="language"></wicket:container>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto my-auto">
|
||||
<div class="card-body py-0">
|
||||
<button class="btn btn-success">Assign</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:enclosure>
|
||||
|
||||
<wicket:enclosure child="busy_candidates">
|
||||
<h3>Busy candidates</h3>
|
||||
<p>
|
||||
These reviewers have already met their assigned review quota.
|
||||
</p>
|
||||
<div class="card bg-danger text-white opacity-50 mb-3" wicket:id="busy_candidates">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<img class="img-fluid rounded-start" wicket:id="image">
|
||||
</div>
|
||||
<div class="col my-auto">
|
||||
<div class="card-body p-0">
|
||||
<h4 class="card-title text-white" wicket:id="user"></h4>
|
||||
<span wicket:id="assigned"></span> / <span wicket:id="target"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto my-auto">
|
||||
<div class="card-body py-0">
|
||||
<button class="btn btn-success">Assign</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:enclosure>
|
||||
|
||||
<wicket:enclosure child="unavailable_candidates">
|
||||
<h3>Unavailable candidates</h3>
|
||||
<p>
|
||||
These reviewers have not been assigned a review quota at all.
|
||||
</p>
|
||||
<div class="card bg-secondary text-white opacity-50 mb-3" wicket:id="unavailable_candidates">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<img class="img-fluid rounded-start" wicket:id="image">
|
||||
</div>
|
||||
<div class="col my-auto">
|
||||
<div class="card-body p-0">
|
||||
<h4 class="card-title text-white" wicket:id="user"></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto my-auto">
|
||||
<div class="card-body py-0">
|
||||
<button class="btn btn-secondary">Assign</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:enclosure>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:extend>
|
||||
|
@ -10,6 +10,7 @@ import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import org.apache.wicket.util.string.StringValueConversionException;
|
||||
import se.su.dsv.scipro.components.AutoHidingListView;
|
||||
import se.su.dsv.scipro.data.DetachableServiceModel;
|
||||
import se.su.dsv.scipro.profile.UserLabel;
|
||||
import se.su.dsv.scipro.profile.UserLinkPanel;
|
||||
@ -18,9 +19,14 @@ import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerCandidates;
|
||||
import se.su.dsv.scipro.system.Language;
|
||||
import se.su.dsv.scipro.system.ResearchArea;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AdminAssignReviewerPage extends AbstractAdminProjectPage {
|
||||
@Inject
|
||||
@ -64,14 +70,16 @@ public class AdminAssignReviewerPage extends AbstractAdminProjectPage {
|
||||
private static class AvailableReviewersPanel extends WebMarkupContainer {
|
||||
@Inject
|
||||
private ReviewerAssignmentService reviewerAssignmentService;
|
||||
@Inject
|
||||
private Clock clock;
|
||||
|
||||
public AvailableReviewersPanel(String id, IModel<Project> projectModel) {
|
||||
super(id, projectModel);
|
||||
|
||||
IModel<ReviewerCandidates> reviewerCandidates = LoadableDetachableModel.of(() ->
|
||||
reviewerAssignmentService.getCandidatesToReview(projectModel.getObject()));
|
||||
reviewerAssignmentService.getCandidatesToReview(projectModel.getObject(), LocalDate.now(clock)));
|
||||
|
||||
add(new ListView<>("good_candidates", reviewerCandidates.map(ReviewerCandidates::goodCandidates)) {
|
||||
add(new AutoHidingListView<>("good_candidates", reviewerCandidates.map(ReviewerCandidates::good)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<ReviewerCandidates.Candidate> item) {
|
||||
item.add(new UserProfileImage("image", item.getModel().map(ReviewerCandidates.Candidate::reviewer), UserProfileImage.Size.MEDIUM));
|
||||
@ -80,6 +88,44 @@ public class AdminAssignReviewerPage extends AbstractAdminProjectPage {
|
||||
item.add(new Label("assigned", item.getModel().map(ReviewerCandidates.Candidate::assigned)));
|
||||
}
|
||||
});
|
||||
add(new AutoHidingListView<>("mismatched_candidates", reviewerCandidates.map(ReviewerCandidates::mismatched)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<ReviewerCandidates.Candidate> item) {
|
||||
IModel<User> reviewerModel = item.getModel().map(ReviewerCandidates.Candidate::reviewer);
|
||||
item.add(new UserProfileImage("image", reviewerModel, UserProfileImage.Size.MEDIUM));
|
||||
item.add(new UserLabel("user", reviewerModel));
|
||||
item.add(new Label("target", item.getModel().map(ReviewerCandidates.Candidate::target)));
|
||||
item.add(new Label("assigned", item.getModel().map(ReviewerCandidates.Candidate::assigned)));
|
||||
item.add(new ListView<>("research_areas", reviewerModel.map(User::getResearchAreas).map(ArrayList::new)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<ResearchArea> item) {
|
||||
item.add(new Label("research_area", item.getModel().map(ResearchArea::getTitle)));
|
||||
}
|
||||
});
|
||||
item.add(new ListView<>("languages", reviewerModel.map(User::getLanguages).map(ArrayList::new)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<Language> item) {
|
||||
item.add(new EnumLabel<>("language", item.getModel()));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
add(new AutoHidingListView<>("busy_candidates", reviewerCandidates.map(ReviewerCandidates::busy)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<ReviewerCandidates.Candidate> item) {
|
||||
item.add(new UserProfileImage("image", item.getModel().map(ReviewerCandidates.Candidate::reviewer), UserProfileImage.Size.MEDIUM));
|
||||
item.add(new UserLabel("user", item.getModel().map(ReviewerCandidates.Candidate::reviewer)));
|
||||
item.add(new Label("target", item.getModel().map(ReviewerCandidates.Candidate::target)));
|
||||
item.add(new Label("assigned", item.getModel().map(ReviewerCandidates.Candidate::assigned)));
|
||||
}
|
||||
});
|
||||
add(new AutoHidingListView<>("unavailable_candidates", reviewerCandidates.map(ReviewerCandidates::unavailable)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<ReviewerCandidates.Candidate> item) {
|
||||
item.add(new UserProfileImage("image", item.getModel().map(ReviewerCandidates.Candidate::reviewer), UserProfileImage.Size.MEDIUM));
|
||||
item.add(new UserLabel("user", item.getModel().map(ReviewerCandidates.Candidate::reviewer)));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user