Store information about assigned reviews
This commit is contained in:
parent
1a9a1a13d2
commit
7f999022c8
core/src
main
java
modules
se/su/dsv/scipro/reviewing
resources/db/migration
test/java/se/su/dsv/scipro/reviewing
view/src/main/java/se/su/dsv/scipro/crosscutting
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user