Store information about assigned reviews

This commit is contained in:
Andreas Svanberg 2023-12-05 16:43:16 +01:00
parent 1a9a1a13d2
commit 7f999022c8
9 changed files with 119 additions and 58 deletions

@ -25,6 +25,8 @@ import se.su.dsv.scipro.report.GradingReportTemplateRepo;
import se.su.dsv.scipro.report.GradingReportTemplateRepoImpl;
import se.su.dsv.scipro.report.OppositionReportRepo;
import se.su.dsv.scipro.report.OppositionReportRepoImpl;
import se.su.dsv.scipro.reviewing.DecisionRepository;
import se.su.dsv.scipro.reviewing.DecisionRepositoryImpl;
import se.su.dsv.scipro.reviewing.ReviewerTargetRepository;
import se.su.dsv.scipro.reviewing.ReviewerTargetRepositoryImpl;
import se.su.dsv.scipro.system.FooterAddressRepo;
@ -56,5 +58,6 @@ public class RepositoryModule extends AbstractModule {
bind(FooterAddressRepo.class).to(FooterAddressRepoImpl.class);
bind(FinalSeminarRepository.class).to(FinalSeminarRepositoryImpl.class);
bind(ReviewerTargetRepository.class).to(ReviewerTargetRepositoryImpl.class);
bind(DecisionRepository.class).to(DecisionRepositoryImpl.class);
}
}

@ -1,5 +1,6 @@
package se.su.dsv.scipro.reviewing;
import jakarta.persistence.Column;
import jakarta.persistence.GenerationType;
import se.su.dsv.scipro.file.FileReference;
@ -15,7 +16,10 @@ import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import se.su.dsv.scipro.system.User;
import java.time.Instant;
import java.time.LocalDate;
import java.util.*;
@Entity
@ -54,6 +58,13 @@ public class Decision {
@ManyToOne(optional = false)
private ReviewerApproval reviewerApproval;
@ManyToOne
@JoinColumn(name = "assigned_reviewer_id")
private User assignedReviewer;
@Column(name = "assigned_reviewer_date")
private LocalDate reviewerAssignedAt;
protected Decision() {} // JPA
Decision(ReviewerApproval reviewerApproval, final FileReference thesis, final String comment, final Date deadline) {
@ -116,6 +127,22 @@ public class Decision {
this.deadline = deadline;
}
public User getAssignedReviewer() {
return assignedReviewer;
}
public void setAssignedReviewer(User assignedReviewer) {
this.assignedReviewer = assignedReviewer;
}
public LocalDate getReviewerAssignedAt() {
return reviewerAssignedAt;
}
public void setReviewerAssignedAt(LocalDate reviewerAssignedAt) {
this.reviewerAssignedAt = reviewerAssignedAt;
}
void approve(final String reason, final Optional<FileReference> attachment) {
decide(Status.APPROVED, reason, attachment);
}

@ -0,0 +1,9 @@
package se.su.dsv.scipro.reviewing;
import se.su.dsv.scipro.system.User;
import java.time.LocalDate;
public interface DecisionRepository {
int countDecisions(User reviewer, LocalDate fromDate, LocalDate toDate);
}

@ -0,0 +1,25 @@
package se.su.dsv.scipro.reviewing;
import jakarta.persistence.EntityManager;
import se.su.dsv.scipro.system.AbstractRepository;
import se.su.dsv.scipro.system.User;
import javax.inject.Inject;
import javax.inject.Provider;
import java.time.LocalDate;
public class DecisionRepositoryImpl extends AbstractRepository implements DecisionRepository {
@Inject
public DecisionRepositoryImpl(Provider<EntityManager> em) {
super(em);
}
@Override
public int countDecisions(User reviewer, LocalDate fromDate, LocalDate toDate) {
return (int) from(QDecision.decision)
.where(QDecision.decision.assignedReviewer.eq(reviewer)
.and(QDecision.decision.reviewerAssignedAt.goe(fromDate))
.and(QDecision.decision.reviewerAssignedAt.loe(toDate)))
.fetchCount();
}
}

@ -6,6 +6,4 @@ import java.time.LocalDate;
public interface ReviewerCapacityService {
void assignTarget(User reviewer, DateRange dateRange, int target);
int getTarget(User reviewer, LocalDate date);
}
}

@ -7,15 +7,23 @@ import se.su.dsv.scipro.system.UserService;
import javax.inject.Inject;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAssignmentService {
private final ReviewerTargetRepository reviewerTargetRepository;
private final DecisionRepository decisionRepository;
private final UserService userService;
@Inject
ReviewerCapacityServiceImpl(ReviewerTargetRepository reviewerTargetRepository, UserService userService) {
ReviewerCapacityServiceImpl(
ReviewerTargetRepository reviewerTargetRepository,
DecisionRepository decisionRepository,
UserService userService)
{
this.reviewerTargetRepository = reviewerTargetRepository;
this.decisionRepository = decisionRepository;
this.userService = userService;
}
@ -29,13 +37,10 @@ class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAs
reviewerTargetRepository.save(reviewerTarget);
}
@Override
public int getTarget(User reviewer, LocalDate date) {
private Optional<ReviewerTarget> getTarget(User reviewer, LocalDate date) {
return reviewerTargetRepository.getReviewerTargets(reviewer, date)
.stream()
.mapToInt(ReviewerTarget::getTarget)
.max()
.orElse(0);
.max(Comparator.comparing(ReviewerTarget::getTarget));
}
@Override
@ -47,8 +52,9 @@ class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAs
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
Optional<ReviewerTarget> reviewerPeriodTarget = getTarget(reviewer, date);
int target = reviewerPeriodTarget.map(ReviewerTarget::getTarget).orElse(0);
int assigned = reviewerPeriodTarget.map(this::countAssignedReviews).orElse(0);
ReviewerCandidates.Candidate candidate = new ReviewerCandidates.Candidate(reviewer, target, assigned);
if (target > 0) {
@ -73,4 +79,12 @@ class ReviewerCapacityServiceImpl implements ReviewerCapacityService, ReviewerAs
return new ReviewerCandidates(good, mismatched, busy, unavailable);
}
private int countAssignedReviews(ReviewerTarget reviewerTarget) {
return countAssignedReviews(reviewerTarget.getReviewer(), reviewerTarget.getFromDate(), reviewerTarget.getToDate());
}
private int countAssignedReviews(User reviewer, LocalDate fromDate, LocalDate toDate) {
return decisionRepository.countDecisions(reviewer, fromDate, toDate);
}
}

@ -0,0 +1,9 @@
ALTER TABLE `Decision`
ADD COLUMN `assigned_reviewer_id` BIGINT,
ADD COLUMN `assigned_reviewer_date` DATE;
ALTER TABLE `Decision`
ADD CONSTRAINT `FK_decision_reviewer` FOREIGN KEY (`assigned_reviewer_id`) REFERENCES `user` (`id`);
UPDATE `Decision` SET `assigned_reviewer_id` = (SELECT MAX(user_id) FROM project_reviewer WHERE project_id = (SELECT project_id FROM ReviewerApproval WHERE id = reviewerApproval_id));
UPDATE `Decision` SET `assigned_reviewer_date` = DATE_SUB(deadline, INTERVAL 7 DAY) WHERE assigned_reviewer_id IS NOT NULL; -- approximation of 5 work days

@ -15,8 +15,6 @@ 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.*;
@ -69,42 +67,6 @@ class ReviewerCapacityServiceImplTest extends IntegrationTest {
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

@ -8,6 +8,8 @@ import se.su.dsv.scipro.reviewing.*;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.time.Clock;
import java.time.LocalDate;
import java.util.Date;
import java.util.function.Consumer;
@ -17,17 +19,20 @@ public class ReviewerAssignedDeadline {
private final FinalSeminarApprovalService finalSeminarApprovalService;
private final ReviewerDeadlineSettingsService reviewerDeadlineSettingsService;
private final DaysService daysService;
private final Clock clock;
ReviewerAssignedDeadline(
RoughDraftApprovalService roughDraftApprovalService,
FinalSeminarApprovalService finalSeminarApprovalService,
ReviewerDeadlineSettingsService reviewerDeadlineSettingsService,
DaysService daysService)
DaysService daysService,
Clock clock)
{
this.roughDraftApprovalService = roughDraftApprovalService;
this.finalSeminarApprovalService = finalSeminarApprovalService;
this.reviewerDeadlineSettingsService = reviewerDeadlineSettingsService;
this.daysService = daysService;
this.clock = clock;
}
@Inject
@ -36,9 +41,10 @@ public class ReviewerAssignedDeadline {
FinalSeminarApprovalService finalSeminarApprovalService,
ReviewerDeadlineSettingsService reviewerDeadlineSettingsService,
DaysService daysService,
EventBus eventBus)
EventBus eventBus,
Clock clock)
{
this(roughDraftApprovalService, finalSeminarApprovalService, reviewerDeadlineSettingsService, daysService);
this(roughDraftApprovalService, finalSeminarApprovalService, reviewerDeadlineSettingsService, daysService, clock);
eventBus.register(this);
}
@ -48,18 +54,26 @@ public class ReviewerAssignedDeadline {
roughDraftApprovalService.findBy(event.getProject())
.filter(rda -> !rda.isDecided())
.ifPresent(setDeadline(getDeadline(deadlineSettings.getRoughDraftApproval())));
.map(ReviewerApproval::getCurrentDecision)
.ifPresent(currentDecision -> {
Date deadline = getDeadline(deadlineSettings.getRoughDraftApproval());
currentDecision.setDeadline(deadline);
currentDecision.setAssignedReviewer(event.getReviewer());
currentDecision.setReviewerAssignedAt(LocalDate.now(clock));
});
finalSeminarApprovalService.findBy(event.getProject())
.filter(fsa -> !fsa.isDecided())
.ifPresent(setDeadline(getDeadline(deadlineSettings.getFinalSeminarApproval())));
.map(ReviewerApproval::getCurrentDecision)
.ifPresent(currentDecision -> {
Date deadline = getDeadline(deadlineSettings.getFinalSeminarApproval());
currentDecision.setDeadline(deadline);
currentDecision.setAssignedReviewer(event.getReviewer());
currentDecision.setReviewerAssignedAt(LocalDate.now(clock));
});
}
protected Date getDeadline(int days) {
return daysService.workDaysAfter(new Date(), days);
}
private Consumer<ReviewerApproval> setDeadline(Date date) {
return approval -> approval.getCurrentDecision().setDeadline(date);
}
}