Enable creating an API using Spring Web #5
@ -7,7 +7,7 @@ import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface DecisionRepository {
|
||||
int countDecisions(User reviewer, LocalDate fromDate, LocalDate toDate);
|
||||
int countUniqueProjectsWithDecision(User reviewer, LocalDate fromDate, LocalDate toDate);
|
||||
|
||||
List<Decision> findBy(Project project);
|
||||
}
|
||||
|
@ -17,11 +17,13 @@ public class DecisionRepositoryImpl extends AbstractRepository implements Decisi
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countDecisions(User reviewer, LocalDate fromDate, LocalDate toDate) {
|
||||
public int countUniqueProjectsWithDecision(User reviewer, LocalDate fromDate, LocalDate toDate) {
|
||||
return (int) from(QDecision.decision)
|
||||
.select(QDecision.decision.reviewerApproval.id)
|
||||
.where(QDecision.decision.assignedReviewer.eq(reviewer)
|
||||
.and(QDecision.decision.reviewerAssignedAt.goe(fromDate))
|
||||
.and(QDecision.decision.reviewerAssignedAt.loe(toDate)))
|
||||
.distinct()
|
||||
.fetchCount();
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package se.su.dsv.scipro.crosscutting;
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
@ -105,10 +105,8 @@ public class ReviewerCapacityServiceImpl implements ReviewerCapacityService, Rev
|
||||
.limit(3) // get three years
|
||||
.map(historicYear -> {
|
||||
Optional<ReviewerTarget> reviewerTarget = reviewerTargetRepository.getReviewerTarget(reviewer, historicYear);
|
||||
int completedInSpring = decisionRepository.countDecisions(reviewer,
|
||||
startOfSpring(historicYear.getValue()), endOfSpring(historicYear.getValue()));
|
||||
int completedInAutumn = decisionRepository.countDecisions(reviewer,
|
||||
startOfAutumn(historicYear.getValue()), endOfAutumn(historicYear.getValue()));
|
||||
int completedInSpring = countSpringReviews(reviewer, historicYear);
|
||||
int completedInAutumn = countAutumnReviews(reviewer, historicYear);
|
||||
return new TargetHistory(
|
||||
historicYear,
|
||||
reviewerTarget.map(ReviewerTarget::getSpring).orElse(0),
|
||||
@ -123,11 +121,23 @@ public class ReviewerCapacityServiceImpl implements ReviewerCapacityService, Rev
|
||||
@Override
|
||||
public RemainingTargets getRemainingTargets(User reviewer, Year year) {
|
||||
Target target = getTarget(reviewer, year);
|
||||
int springDecisions = decisionRepository.countDecisions(reviewer, startOfSpring(year.getValue()), endOfSpring(year.getValue()));
|
||||
int autumnReviews = decisionRepository.countDecisions(reviewer, startOfAutumn(year.getValue()), endOfAutumn(year.getValue()));
|
||||
int springDecisions = countSpringReviews(reviewer, year);
|
||||
int autumnReviews = countAutumnReviews(reviewer, year);
|
||||
return new RemainingTargets(target.spring() - springDecisions, target.autumn() - autumnReviews);
|
||||
}
|
||||
|
||||
private int countSpringReviews(User reviewer, Year year) {
|
||||
return countReviews(reviewer, startOfSpring(year.getValue()), endOfSpring(year.getValue()));
|
||||
}
|
||||
|
||||
private int countAutumnReviews(User reviewer, Year year) {
|
||||
return countReviews(reviewer, startOfAutumn(year.getValue()), endOfAutumn(year.getValue()));
|
||||
}
|
||||
|
||||
private int countReviews(User reviewer, LocalDate fromDate, LocalDate toDate) {
|
||||
return decisionRepository.countUniqueProjectsWithDecision(reviewer, fromDate, toDate);
|
||||
}
|
||||
|
||||
private Optional<ReviewerTarget> getTarget(User reviewer, LocalDate date) {
|
||||
return reviewerTargetRepository.getReviewerTarget(reviewer, Year.from(date));
|
||||
}
|
||||
@ -242,12 +252,10 @@ public class ReviewerCapacityServiceImpl implements ReviewerCapacityService, Rev
|
||||
|
||||
private int countAssignedReviews(User reviewer, LocalDate fromDate) {
|
||||
if (fromDate.getMonthValue() <= Month.JUNE.getValue()) {
|
||||
return decisionRepository.countDecisions(reviewer,
|
||||
startOfSpring(fromDate.getYear()), endOfSpring(fromDate.getYear()));
|
||||
return countReviews(reviewer, startOfSpring(fromDate.getYear()), endOfSpring(fromDate.getYear()));
|
||||
}
|
||||
else {
|
||||
return decisionRepository.countDecisions(reviewer,
|
||||
startOfAutumn(fromDate.getYear()), endOfAutumn(fromDate.getYear()));
|
||||
return countReviews(reviewer, startOfAutumn(fromDate.getYear()), endOfAutumn(fromDate.getYear()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,10 @@ public class ProjectTypeSettings extends DomainObject {
|
||||
@Basic(optional = false)
|
||||
private int numDaysBeforePeerGetsCancelled = DEFAULT_NUM_DAYS_BEFORE_CANCELLED_PEERS;
|
||||
|
||||
@Basic
|
||||
@Column(name = "review_process_information_url_for_supervisor")
|
||||
private String reviewProcessInformationUrl;
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
@ -164,6 +168,14 @@ public class ProjectTypeSettings extends DomainObject {
|
||||
this.minimumActiveParticipationsToBeGraded = minimumActiveParticipationsToBeGraded;
|
||||
}
|
||||
|
||||
public String getReviewProcessInformationUrl() {
|
||||
return reviewProcessInformationUrl;
|
||||
}
|
||||
|
||||
public void setReviewProcessInformationUrl(String reviewProcessInformationUrl) {
|
||||
this.reviewProcessInformationUrl = reviewProcessInformationUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProjectTypeSettings(id=" + this.getId() + ", projectType=" + this.getProjectType() + ", minAuthors=" + this.getMinAuthors() + ", maxAuthors=" + this.getMaxAuthors() + ", maxFinalSeminarActiveParticipation=" + this.getMaxFinalSeminarActiveParticipation() + ", maxOpponentsOnFinalSeminar=" + this.getMaxOpponentsOnFinalSeminar() + ", minFinalSeminarActiveParticipation=" + this.getMinFinalSeminarActiveParticipation() + ", minOpponentsOnFinalSeminar=" + this.getMinOpponentsOnFinalSeminar() + ", numDaysBetweenPeerReviewsOnSameProject=" + this.getNumDaysBetweenPeerReviewsOnSameProject() + ", numDaysToSubmitPeerReview=" + this.getNumDaysToSubmitPeerReview() + ", numDaysBeforePeerGetsCancelled=" + this.getNumDaysBeforePeerGetsCancelled() + ")";
|
||||
|
@ -0,0 +1,2 @@
|
||||
ALTER TABLE `project_type_settings`
|
||||
ADD COLUMN `review_process_information_url_for_supervisor` VARCHAR(255) NULL;
|
127
core/src/test/java/se/su/dsv/scipro/reviewing/ReviewerTest.java
Normal file
127
core/src/test/java/se/su/dsv/scipro/reviewing/ReviewerTest.java
Normal file
@ -0,0 +1,127 @@
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import se.su.dsv.scipro.file.FileUpload;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerAssignmentService.ReviewerAssignment;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.system.DegreeType;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.MutableFixedClock;
|
||||
import se.su.dsv.scipro.util.Either;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.Year;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ReviewerTest extends ReviewingModuleTest {
|
||||
|
||||
@Inject
|
||||
MutableFixedClock clock;
|
||||
@Inject
|
||||
ReviewerAssignmentService reviewerAssignmentService;
|
||||
@Inject
|
||||
ReviewerCapacityService reviewerCapacityService;
|
||||
@Inject
|
||||
RoughDraftApprovalService roughDraftApprovalService;
|
||||
@Inject
|
||||
ReviewerDecisionService reviewerDecisionService;
|
||||
|
||||
@Test
|
||||
public void an_assigned_reviewer_should_only_consume_one_target_per_project() {
|
||||
// Given
|
||||
clock.setDate(LocalDate.of(2024, Month.MAY, 22)); // some date in spring
|
||||
|
||||
Project project = createProject();
|
||||
User reviewer = createUser();
|
||||
var target = new ReviewerCapacityService.Target(Year.now(clock), 2, 0, "Can review at any time");
|
||||
|
||||
// When
|
||||
reviewerCapacityService.assignTarget(reviewer, target);
|
||||
|
||||
Either<AlreadyRequested, RoughDraftApproval> firstReviewRequest = roughDraftApprovalService.requestApproval(
|
||||
project,
|
||||
dummyFile(),
|
||||
"Some comment");
|
||||
assertTrue(firstReviewRequest.isRight());
|
||||
|
||||
ReviewerAssignment assignment = reviewerAssignmentService.assignReviewer(project,reviewer);
|
||||
assertEquals(ReviewerAssignment.OK, assignment);
|
||||
|
||||
reviewerDecisionService.reject(firstReviewRequest.right(), "Not good enough", Optional.empty());
|
||||
|
||||
Either<AlreadyRequested, RoughDraftApproval> secondReviewRequest = roughDraftApprovalService.requestApproval(
|
||||
project,
|
||||
dummyFile(),
|
||||
"Some new comment");
|
||||
assertTrue(secondReviewRequest.isRight());
|
||||
|
||||
// Then
|
||||
ReviewerCapacityService.RemainingTargets remainingTargets =
|
||||
reviewerCapacityService.getRemainingTargets(reviewer, target.year());
|
||||
assertEquals(1, remainingTargets.spring());
|
||||
}
|
||||
|
||||
private User createUser() {
|
||||
User user = User.builder()
|
||||
.firstName("Edward")
|
||||
.lastName("Employee")
|
||||
.emailAddress("stuart@example.com")
|
||||
.roles(Set.of(Roles.REVIEWER, Roles.SUPERVISOR))
|
||||
.build();
|
||||
return save(user);
|
||||
}
|
||||
|
||||
private Project createProject() {
|
||||
ProjectType bachelor = new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor");
|
||||
save(bachelor);
|
||||
|
||||
User supervisor = createUser();
|
||||
|
||||
Project project = Project.builder()
|
||||
.title("A project")
|
||||
.projectType(bachelor)
|
||||
.startDate(LocalDate.now(clock).minusMonths(1))
|
||||
.headSupervisor(supervisor)
|
||||
.build();
|
||||
return save(project);
|
||||
}
|
||||
|
||||
private FileUpload dummyFile() {
|
||||
return new FileUpload() {
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return "dummy.tmp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUploader() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T handleData(Function<InputStream, T> handler) {
|
||||
return handler.apply(InputStream.nullInputStream());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,7 +1,18 @@
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Module;
|
||||
import se.su.dsv.scipro.test.IntegrationTest;
|
||||
|
||||
public abstract class ReviewingModuleTest extends IntegrationTest {
|
||||
|
||||
@Override
|
||||
protected Module moduleUnderTest() {
|
||||
return new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
install(ReviewingModuleTest.super.moduleUnderTest());
|
||||
bind(ReviewerAssignedDeadline.class).asEagerSingleton();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,12 @@ import org.springframework.context.annotation.FilterType;
|
||||
import se.su.dsv.scipro.forum.AbstractThreadRepositoryImpl;
|
||||
import se.su.dsv.scipro.grading.GradingHistory;
|
||||
import se.su.dsv.scipro.grading.GradingHistoryEventRepository;
|
||||
import se.su.dsv.scipro.misc.DaysService;
|
||||
import se.su.dsv.scipro.oauth.OAuthSettings;
|
||||
import se.su.dsv.scipro.reviewing.FinalSeminarApprovalService;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerAssignedDeadline;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerDeadlineSettingsService;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
|
||||
import se.su.dsv.scipro.security.auth.LocalAuthentication;
|
||||
import se.su.dsv.scipro.sukat.Sukat;
|
||||
import se.su.dsv.scipro.system.CurrentUser;
|
||||
@ -127,5 +132,17 @@ public abstract class SpringTest {
|
||||
public LocalAuthentication localAuthentication(UserService userService, PasswordService passwordService) {
|
||||
return new LocalAuthentication(userService, passwordService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReviewerAssignedDeadline reviewerAssignedDeadline(
|
||||
RoughDraftApprovalService roughDraftApprovalService,
|
||||
FinalSeminarApprovalService finalSeminarApprovalService,
|
||||
ReviewerDeadlineSettingsService reviewerDeadlineSettingsService,
|
||||
DaysService daysService,
|
||||
EventBus eventBus, Clock clock)
|
||||
{
|
||||
return new ReviewerAssignedDeadline(roughDraftApprovalService, finalSeminarApprovalService,
|
||||
reviewerDeadlineSettingsService, daysService, eventBus, clock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,18 @@
|
||||
<label wicket:for="description">Description</label>
|
||||
<textarea class="form-control" wicket:id="description" cols="60" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label wicket:for="minimum_authors">
|
||||
Minimum number of authors per student idea
|
||||
</label>
|
||||
<input class="form-control" type="number" wicket:id="minimum_authors">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label wicket:for="minimum_authors">
|
||||
Maximum number of authors per student idea
|
||||
</label>
|
||||
<input class="form-control" type="number" wicket:id="maximum_authors">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label wicket:for="minimum_oppositions_to_be_graded">
|
||||
Minimum approved final seminar oppositions to be graded
|
||||
@ -34,6 +46,15 @@
|
||||
</label>
|
||||
<input class="form-control" type="number" wicket:id="minimum_active_participations_to_be_graded">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label wicket:for="review_process_information_url">
|
||||
URL with information about the review process
|
||||
</label>
|
||||
<input class="form-control" type="url" wicket:id="review_process_information_url">
|
||||
<small class="text-muted">
|
||||
This link is display for supervisors when they are in the review process.
|
||||
</small>
|
||||
</div>
|
||||
<input class="btn btn-sm btn-success" wicket:id="createButton" type="submit" value="Save" />
|
||||
<input class="btn btn-sm btn-danger" wicket:id="deleteButton" type="submit" value="Inactivate" />
|
||||
</form>
|
||||
|
@ -68,6 +68,30 @@ public class AdminProjectTypePanel extends Panel {
|
||||
minimumActiveParticipationsToBeGraded.setMinimum(0);
|
||||
add(minimumActiveParticipationsToBeGraded);
|
||||
|
||||
NumberTextField<Integer> minimumAuthors = new NumberTextField<>(
|
||||
"minimum_authors",
|
||||
LambdaModel.of(settings, ProjectTypeSettings::getMinAuthors, ProjectTypeSettings::setMinAuthors),
|
||||
Integer.class);
|
||||
minimumAuthors.setMinimum(1);
|
||||
minimumAuthors.setRequired(true);
|
||||
add(minimumAuthors);
|
||||
|
||||
NumberTextField<Integer> maximumAuthors = new NumberTextField<>(
|
||||
"maximum_authors",
|
||||
LambdaModel.of(settings, ProjectTypeSettings::getMaxAuthors, ProjectTypeSettings::setMaxAuthors),
|
||||
Integer.class);
|
||||
maximumAuthors.setMinimum(1);
|
||||
maximumAuthors.setRequired(true);
|
||||
add(maximumAuthors);
|
||||
|
||||
TextField<String> reviewProcessInformationUrl = new UrlTextField(
|
||||
"review_process_information_url",
|
||||
LambdaModel.of(
|
||||
settings,
|
||||
ProjectTypeSettings::getReviewProcessInformationUrl,
|
||||
ProjectTypeSettings::setReviewProcessInformationUrl));
|
||||
add(reviewProcessInformationUrl);
|
||||
|
||||
Button createButton = new Button("createButton") {
|
||||
@Override
|
||||
public void onSubmit() {
|
||||
|
@ -27,7 +27,7 @@ public class SmarterLinkMultiLineLabel extends SmartLinkMultiLineLabel {
|
||||
* with the addition of ";" to the accepted characters in the URL.
|
||||
* This enables proper linking of escaped URLs.
|
||||
*/
|
||||
private static final String urlPattern = "([a-zA-Z]+://[\\w\\.\\-\\:\\/~]+)[\\w\\.:\\-/?&=%;]*";
|
||||
private static final String urlPattern = "([a-zA-Z]+://[\\w\\.\\-\\:\\/~]+)[\\w\\.:\\-/?!&=%;]*";
|
||||
|
||||
private EscapedLinkParser() {
|
||||
addLinkRenderStrategy(emailPattern, DefaultLinkParser.EMAIL_RENDER_STRATEGY);
|
||||
|
@ -100,9 +100,7 @@ public class SendToExaminer extends GenericPanel<Project> {
|
||||
add(form);
|
||||
|
||||
WebMarkupContainer sendButton = new WebMarkupContainer("send");
|
||||
if (confirmationMessage.getObject() != null) {
|
||||
sendButton.add(new JavascriptEventConfirmation("click", confirmationMessage));
|
||||
}
|
||||
sendButton.add(new JavascriptEventConfirmation("click", confirmationMessage));
|
||||
form.add(sendButton);
|
||||
|
||||
TextField<LocalDate> examinationDateField = new TextField<>("examinationDate", examinationDate, LocalDate.class);
|
||||
|
@ -11,6 +11,7 @@ import org.apache.wicket.markup.html.form.Button;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.form.TextArea;
|
||||
import org.apache.wicket.markup.html.form.upload.FileUploadField;
|
||||
import org.apache.wicket.markup.html.link.ExternalLink;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
import org.apache.wicket.markup.html.list.ListView;
|
||||
import org.apache.wicket.model.IModel;
|
||||
@ -36,6 +37,8 @@ import se.su.dsv.scipro.reviewing.ReviewerDecisionService;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerInteractionService;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
|
||||
import se.su.dsv.scipro.supervisor.panels.FinalSeminarApprovalProcessPanel;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ProjectTypeSettings;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.Either;
|
||||
|
||||
@ -92,6 +95,17 @@ public class RoughDraftApprovalDecisionPage extends ReviewerPage {
|
||||
add(new EnumLabel<>("title", approval.map(ReviewerApproval::getStep)));
|
||||
add(new TimelinePanel("timeline", project));
|
||||
add(new FencedFeedbackPanel("feedback", this));
|
||||
IModel<String> moreInformationUrl = project
|
||||
.map(Project::getProjectType)
|
||||
.map(ProjectType::getProjectTypeSettings)
|
||||
.map(ProjectTypeSettings::getReviewProcessInformationUrl);
|
||||
add(new ExternalLink("review_process_information", moreInformationUrl) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(moreInformationUrl.getObject() != null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -10,7 +10,16 @@
|
||||
<div class="col-12 col-xl-3 col-lg-6">
|
||||
<div class="card bg-light">
|
||||
<h4 class="card-header">Rough draft approval</h4>
|
||||
<div class="card-body" wicket:id="roughDraftApproval"></div>
|
||||
<div class="card-body">
|
||||
<wicket:enclosure>
|
||||
<p>
|
||||
<a href="https://example.com" wicket:id="review_process_information">
|
||||
See more information about the review process and any important dates.
|
||||
</a>
|
||||
</p>
|
||||
</wicket:enclosure>
|
||||
<div wicket:id="roughDraftApproval"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6 col-lg-6">
|
||||
|
@ -3,6 +3,8 @@ package se.su.dsv.scipro.supervisor.pages;
|
||||
import org.apache.wicket.markup.head.IHeaderResponse;
|
||||
import org.apache.wicket.markup.head.OnEventHeaderItem;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.link.ExternalLink;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightSupervisorMyProjects;
|
||||
import se.su.dsv.scipro.file.FileService;
|
||||
@ -18,6 +20,8 @@ import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.supervisor.panels.RoughDraftApprovalPanel;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ProjectTypeSettings;
|
||||
|
||||
@Authorization(authorizedRoles = Roles.SUPERVISOR)
|
||||
public class SupervisorInteractWithReviewerPage extends AbstractSupervisorProjectDetailsPage implements MenuHighlightSupervisorMyProjects {
|
||||
@ -41,6 +45,17 @@ public class SupervisorInteractWithReviewerPage extends AbstractSupervisorProjec
|
||||
}
|
||||
});
|
||||
|
||||
IModel<String> moreInformationUrl = projectModel
|
||||
.map(Project::getProjectType)
|
||||
.map(ProjectType::getProjectTypeSettings)
|
||||
.map(ProjectTypeSettings::getReviewProcessInformationUrl);
|
||||
add(new ExternalLink("review_process_information", moreInformationUrl) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(moreInformationUrl.getObject() != null);
|
||||
}
|
||||
});
|
||||
ForumThread<Project> reviewerThread = new ReviewerInteractionForumThread(reviewerInteractionService);
|
||||
add(new RoughDraftApprovalPanel("roughDraftApproval", projectModel));
|
||||
add(new SubmitForumReplyPanel<>("communication", projectModel, reviewerThread));
|
||||
|
@ -37,18 +37,20 @@ public class JavascriptEventConfirmation extends Behavior {
|
||||
public void renderHead(Component component, IHeaderResponse response) {
|
||||
super.renderHead(component, response);
|
||||
final String confirmationMsg = getConfirmationMsg(component);
|
||||
if (confirmationMsg == null) return;
|
||||
|
||||
String confirmScript = "var conf = confirm('" + confirmationMsg.replaceAll("'", "\\\\'") + "'); " +
|
||||
"if (!conf) event.preventDefault();";
|
||||
response.render(OnEventHeaderItem.forComponent(component, event, confirmScript));
|
||||
}
|
||||
|
||||
private String getConfirmationMsg(Component component) {
|
||||
IModel<?> model = msgModel;
|
||||
if (model instanceof IComponentAssignedModel)
|
||||
IModel<String> model = msgModel;
|
||||
if (model instanceof IComponentAssignedModel<String> icam)
|
||||
{
|
||||
model = ((IComponentAssignedModel<?>)model).wrapOnAssignment(component);
|
||||
model = icam.wrapOnAssignment(component);
|
||||
}
|
||||
return String.valueOf(model.getObject());
|
||||
return model.getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -16,6 +16,13 @@
|
||||
<div class="row">
|
||||
<div class="col-12 col-xl-6 mb-3">
|
||||
<h4><span wicket:id="title"></span></h4>
|
||||
<wicket:enclosure>
|
||||
<p>
|
||||
<a href="https://example.com" wicket:id="review_process_information">
|
||||
See more information about the review process and any important dates.
|
||||
</a>
|
||||
</p>
|
||||
</wicket:enclosure>
|
||||
<div wicket:id="details"></div>
|
||||
<div wicket:id="feedback"></div>
|
||||
<form wicket:id="decision">
|
||||
|
@ -40,4 +40,32 @@ public class AdminProjectTypePanelTest extends SciProTest {
|
||||
|
||||
assertEquals(DegreeType.values()[index], captor.getValue().getDegreeType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void set_min_max_authors() {
|
||||
FormTester formTester = tester.newFormTester(path(panel, "projectTypeForm"));
|
||||
formTester.setValue("name", "bachelor");
|
||||
formTester.setValue("minimum_authors", "17");
|
||||
formTester.setValue("maximum_authors", "29");
|
||||
formTester.submit("createButton");
|
||||
|
||||
ArgumentCaptor<ProjectType> captor = ArgumentCaptor.forClass(ProjectType.class);
|
||||
verify(projectTypeService).save(captor.capture());
|
||||
|
||||
assertEquals(17, captor.getValue().getProjectTypeSettings().getMinAuthors());
|
||||
assertEquals(29, captor.getValue().getProjectTypeSettings().getMaxAuthors());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void set_review_process_information_url() {
|
||||
FormTester formTester = tester.newFormTester(path(panel, "projectTypeForm"));
|
||||
formTester.setValue("name", "bachelor");
|
||||
formTester.setValue("review_process_information_url", "https://example.com");
|
||||
formTester.submit("createButton");
|
||||
|
||||
ArgumentCaptor<ProjectType> captor = ArgumentCaptor.forClass(ProjectType.class);
|
||||
verify(projectTypeService).save(captor.capture());
|
||||
|
||||
assertEquals("https://example.com", captor.getValue().getProjectTypeSettings().getReviewProcessInformationUrl());
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import se.su.dsv.scipro.system.DegreeType;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.Either;
|
||||
import se.su.dsv.scipro.util.JavascriptEventConfirmation;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
@ -152,6 +153,55 @@ public class SendToExaminerTest extends SciProTest {
|
||||
verify(gradingService).reportGrade(TOKEN, project.getIdentifier(), biden.getIdentifier(), gw.id(), grade.name(), finalThesis.getUploadDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void author_without_active_participations_done_should_generate_confirmation_on_sending_to_examiner() {
|
||||
ProjectType bachelor = new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor");
|
||||
User obama = User.builder()
|
||||
.firstName("Barack")
|
||||
.lastName("Obama")
|
||||
.emailAddress("obama@example.com")
|
||||
.identifier(44)
|
||||
.build();
|
||||
User biden = User.builder()
|
||||
.firstName("Joe")
|
||||
.lastName("Biden")
|
||||
.emailAddress("joe@example.com")
|
||||
.identifier(46)
|
||||
.build();
|
||||
biden.setId(46L);
|
||||
Project project = Project.builder()
|
||||
.title("My project")
|
||||
.projectType(bachelor)
|
||||
.startDate(LocalDate.of(2023, Month.JANUARY, 6))
|
||||
.headSupervisor(obama)
|
||||
.projectParticipants(Set.of(biden))
|
||||
.identifier(1888)
|
||||
.build();
|
||||
project.setId(8L);
|
||||
|
||||
Examination gw = new Examination(
|
||||
2,
|
||||
new Name("Examensarbete", "Graduation work"),
|
||||
"KX1E",
|
||||
BigDecimal.valueOf(9),
|
||||
List.of(
|
||||
new Grade(Grade.Type.PASSING, "A"),
|
||||
new Grade(Grade.Type.PASSING, "B"),
|
||||
new Grade(Grade.Type.PASSING, "C"),
|
||||
new Grade(Grade.Type.PASSING, "D"),
|
||||
new Grade(Grade.Type.PASSING, "E"),
|
||||
new Grade(Grade.Type.FAILING, "F")));
|
||||
|
||||
when(gradingService.getExaminations(any(), anyLong(), anyLong()))
|
||||
.thenReturn(List.of(gw));
|
||||
when(gradingService.getResult(any(), anyLong(), anyLong(), anyLong()))
|
||||
.thenReturn(Either.right(Optional.empty()));
|
||||
|
||||
tester.startComponentInPage(new SendToExaminer("id", () -> project, () -> biden, () -> "please check active participations"));
|
||||
|
||||
tester.assertBehavior(path("id", "form", "send"), JavascriptEventConfirmation.class);
|
||||
}
|
||||
|
||||
private static Thesis daisyThesis() {
|
||||
Unit unit = new Unit();
|
||||
unit.setId(1);
|
||||
|
@ -21,7 +21,6 @@ import se.su.dsv.scipro.checklist.ChecklistServiceImpl;
|
||||
import se.su.dsv.scipro.checklist.ChecklistTemplateService;
|
||||
import se.su.dsv.scipro.checklist.ChecklistTemplateServiceImpl;
|
||||
import se.su.dsv.scipro.crosscutting.ForwardPhase2Feedback;
|
||||
import se.su.dsv.scipro.crosscutting.ReviewerAssignedDeadline;
|
||||
import se.su.dsv.scipro.crosscutting.ReviewerAssignedNotifications;
|
||||
import se.su.dsv.scipro.crosscutting.ReviewerSupportMailer;
|
||||
import se.su.dsv.scipro.crosscutting.ReviewingNotifications;
|
||||
@ -161,6 +160,7 @@ import se.su.dsv.scipro.reviewing.DecisionRepository;
|
||||
import se.su.dsv.scipro.reviewing.FinalSeminarApprovalService;
|
||||
import se.su.dsv.scipro.reviewing.FinalSeminarApprovalServiceImpl;
|
||||
import se.su.dsv.scipro.reviewing.ProjectFinalSeminarStatisticsServiceImpl;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerAssignedDeadline;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerCapacityServiceImpl;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerDeadlineFollowupServiceImpl;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerDeadlineSettingsRepository;
|
||||
|
Loading…
x
Reference in New Issue
Block a user