From 9d88c373f2bc8bf991e08cda473325301578505e Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Mon, 13 Jan 2025 16:24:47 +0100 Subject: [PATCH 01/23] GUI to request improvements --- .../finalseminar/SeminarOppositionPanel.html | 14 ++++ .../finalseminar/SeminarOppositionPanel.java | 67 ++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html index 4467d0a779..07e9b23129 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html @@ -34,6 +34,20 @@ + + Request improvements + + + +
+
+ + +
+ + Cancel
diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java index 5315c6ff3d..6683d90aeb 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java @@ -6,6 +6,7 @@ import java.util.Date; import java.util.List; import java.util.Objects; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; import org.apache.wicket.feedback.FencedFeedbackPanel; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -22,6 +23,7 @@ import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LambdaModel; +import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.validation.validator.RangeValidator; import org.apache.wicket.validation.validator.StringValidator; @@ -75,6 +77,9 @@ public class SeminarOppositionPanel extends Panel { private final WebMarkupContainer oppositionContainer; private final ListView opponents; + private FinalSeminarOppositionForm gradeForm; + private RequestImprovementsForm requestImprovementsForm; + public SeminarOppositionPanel(String id, final IModel seminar) { super(id, seminar); this.seminar = seminar; @@ -107,6 +112,12 @@ public class SeminarOppositionPanel extends Panel { private ListView getOpponentsList(final IModel> oppositions) { return new ListView<>(OPPONENTS, oppositions) { + { + // Need to reuse child list items since they contain form components + // and if they're recreated all the state and error messages are lost + setReuseItems(true); + } + @Override protected void populateItem(final ListItem item) { final FinalSeminarOpposition opposition = item.getModelObject(); @@ -121,7 +132,14 @@ public class SeminarOppositionPanel extends Panel { item.add(getRemoveLink(item.getModel())); - item.add(getFinalSeminarOppositionForm(item)); + gradeForm = getFinalSeminarOppositionForm(item); + gradeForm.setOutputMarkupPlaceholderTag(true); + item.add(gradeForm); + + requestImprovementsForm = new RequestImprovementsForm("request_improvements", item.getModel()); + requestImprovementsForm.setVisible(false); + requestImprovementsForm.setOutputMarkupPlaceholderTag(true); + item.add(requestImprovementsForm); if (gradingModuleIsOnForProjectType()) { item.add(new SeminarOppositionReportPanel("report", item.getModel())); @@ -277,6 +295,23 @@ public class SeminarOppositionPanel extends Panel { } } ); + + add( + new AjaxLink("request_improvements") { + @Override + public void onClick(AjaxRequestTarget target) { + requestImprovementsForm.setVisible(true); + target.add(requestImprovementsForm); + gradeForm.setVisible(false); + target.add(gradeForm); + target.appendJavaScript( + "document.getElementById('" + + requestImprovementsForm.get("feedback_to_opponent").getMarkupId() + + "').focus();" + ); + } + } + ); } @Override @@ -298,4 +333,34 @@ public class SeminarOppositionPanel extends Panel { private boolean hasSubmittedOppositionReport(FinalSeminarOpposition opposition) { return oppositionReportService.findOrCreateReport(opposition).isSubmitted(); } + + private class RequestImprovementsForm extends Form { + + private final Model feedbackToOpponentModel = new Model<>(); + + public RequestImprovementsForm(String id, IModel model) { + super(id, model); + TextArea feedbackToOpponentField = new TextArea<>("feedback_to_opponent", feedbackToOpponentModel); + feedbackToOpponentField.setRequired(true); + add(feedbackToOpponentField); + + add( + new AjaxLink("cancel") { + @Override + public void onClick(AjaxRequestTarget target) { + requestImprovementsForm.setVisible(false); + target.add(requestImprovementsForm); + gradeForm.setVisible(true); + target.add(gradeForm); + } + } + ); + } + + @Override + protected void onSubmit() { + System.out.println("Request improvements submitted"); + System.out.println("Feedback to opponent: " + feedbackToOpponentModel.getObject()); + } + } } -- 2.39.5 From 8ce045b33c132417b50b72fc5ef718b72ca54b69 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Mon, 13 Jan 2025 16:25:11 +0100 Subject: [PATCH 02/23] Create test data --- .../se/su/dsv/scipro/DataInitializer.java | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java index 7ab805cd7a..80f06e8e64 100644 --- a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java +++ b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java @@ -7,8 +7,11 @@ import jakarta.transaction.Transactional; import java.time.LocalDate; import java.time.LocalTime; import java.time.Month; +import java.time.ZonedDateTime; import java.util.*; import se.su.dsv.scipro.checklist.ChecklistCategory; +import se.su.dsv.scipro.finalseminar.FinalSeminar; +import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; import se.su.dsv.scipro.match.ApplicationPeriod; import se.su.dsv.scipro.match.Keyword; import se.su.dsv.scipro.milestones.dataobjects.MilestoneActivityTemplate; @@ -75,6 +78,8 @@ public class DataInitializer implements Lifecycle { private ResearchArea researchArea2; private ProjectType masterClass; private ProjectType magisterClass; + private Project project1; + private Project project2; @Transactional @Override @@ -89,12 +94,29 @@ public class DataInitializer implements Lifecycle { createMilestonesIfNotDone(); createUsers(); createProjects(); + createPastFinalSeminar(); } if (profile.getCurrentProfile() == Profiles.DEV && noAdminUser()) { createAdmin(); } } + private void createPastFinalSeminar() { + FinalSeminar finalSeminar = new FinalSeminar(); + finalSeminar.setStartDate(Date.from(ZonedDateTime.now().minusDays(1).toInstant())); + finalSeminar.setProject(project1); + finalSeminar.setRoom("zoom"); + finalSeminar.setPresentationLanguage(Language.ENGLISH); + + FinalSeminarOpposition opponent = new FinalSeminarOpposition(); + opponent.setProject(project2); + opponent.setFinalSeminar(finalSeminar); + opponent.setUser(sid_student); + finalSeminar.addOpposition(opponent); + + save(finalSeminar); + } + @Override public void stop() {} @@ -145,11 +167,11 @@ public class DataInitializer implements Lifecycle { } private void createProjects() { - createProject(PROJECT_1, eric_employee, sture_student, stina_student, eve_employee); - createProject(PROJECT_2, eve_employee, sid_student, simon_student, eric_employee); + project1 = createProject(PROJECT_1, eric_employee, sture_student, stina_student, eve_employee); + project2 = createProject(PROJECT_2, eve_employee, sid_student, simon_student, eric_employee); } - private void createProject(String title, User headSupervisor, User student1, User student2, User reviewer) { + private Project createProject(String title, User headSupervisor, User student1, User student2, User reviewer) { Project project = Project.builder() .title(title) .projectType(bachelorClass) @@ -159,7 +181,7 @@ public class DataInitializer implements Lifecycle { project.addProjectParticipant(student2); project.addProjectParticipant(student1); project.addReviewer(reviewer); - save(project); + return save(project); } private void createUsers() { -- 2.39.5 From 5e3f6c138346f6fcdb5c0b8f877cc63c4f734870 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Tue, 14 Jan 2025 15:29:33 +0100 Subject: [PATCH 03/23] Opposition grading is now based on the opponents grading report criteria --- .../java/se/su/dsv/scipro/CoreConfig.java | 8 +++- .../FinalSeminarOppositionGrading.java | 7 ++++ .../FinalSeminarOppositionService.java | 3 ++ .../FinalSeminarOppositionServiceImpl.java | 14 ++++++- .../finalseminar/OppositionCriterion.java | 3 ++ .../report/GradingReportServiceImpl.java | 18 ++++++++- .../finalseminar/SeminarOppositionPanel.html | 2 +- .../finalseminar/SeminarOppositionPanel.java | 38 +++++++++---------- 8 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionGrading.java create mode 100644 core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriterion.java diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java index 43c4d7ed10..ee89a28365 100644 --- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java +++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java @@ -31,6 +31,7 @@ import se.su.dsv.scipro.finalseminar.AuthorRepository; import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationRepository; import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationServiceImpl; import se.su.dsv.scipro.finalseminar.FinalSeminarCreationSubscribers; +import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionGrading; import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionRepo; import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionServiceImpl; import se.su.dsv.scipro.finalseminar.FinalSeminarRepository; @@ -430,8 +431,11 @@ public class CoreConfig { } @Bean - public FinalSeminarOppositionServiceImpl finalSeminarOppositionService(Provider em) { - return new FinalSeminarOppositionServiceImpl(em); + public FinalSeminarOppositionServiceImpl finalSeminarOppositionService( + Provider em, + FinalSeminarOppositionGrading finalSeminarOppositionGrading + ) { + return new FinalSeminarOppositionServiceImpl(em, finalSeminarOppositionGrading); } @Bean diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionGrading.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionGrading.java new file mode 100644 index 0000000000..0ac5bba547 --- /dev/null +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionGrading.java @@ -0,0 +1,7 @@ +package se.su.dsv.scipro.finalseminar; + +import java.util.List; + +public interface FinalSeminarOppositionGrading { + List oppositionCriteria(FinalSeminarOpposition opposition); +} diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java index 60eeacc1a9..8394531675 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java @@ -1,8 +1,11 @@ package se.su.dsv.scipro.finalseminar; +import java.util.List; import se.su.dsv.scipro.system.GenericService; public interface FinalSeminarOppositionService extends GenericService { @Override void delete(Long aLong); + + List getCriteriaForOpposition(FinalSeminarOpposition opposition); } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java index 27550bb3ac..47bc869a32 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java @@ -3,14 +3,26 @@ package se.su.dsv.scipro.finalseminar; import jakarta.inject.Inject; import jakarta.inject.Provider; import jakarta.persistence.EntityManager; +import java.util.List; import se.su.dsv.scipro.system.AbstractServiceImpl; public class FinalSeminarOppositionServiceImpl extends AbstractServiceImpl implements FinalSeminarOppositionService { + private final FinalSeminarOppositionGrading finalSeminarOppositionGrading; + @Inject - public FinalSeminarOppositionServiceImpl(Provider em) { + public FinalSeminarOppositionServiceImpl( + Provider em, + FinalSeminarOppositionGrading finalSeminarOppositionGrading + ) { super(em, FinalSeminarOpposition.class, QFinalSeminarOpposition.finalSeminarOpposition); + this.finalSeminarOppositionGrading = finalSeminarOppositionGrading; + } + + @Override + public List getCriteriaForOpposition(FinalSeminarOpposition opposition) { + return finalSeminarOppositionGrading.oppositionCriteria(opposition); } } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriterion.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriterion.java new file mode 100644 index 0000000000..9915cf55f1 --- /dev/null +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriterion.java @@ -0,0 +1,3 @@ +package se.su.dsv.scipro.finalseminar; + +public record OppositionCriterion(int points, String requirement) {} diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java index bc945b6443..f8486cf0fb 100644 --- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java @@ -8,6 +8,8 @@ import java.time.Instant; import java.time.LocalDate; import java.util.*; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; +import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionGrading; +import se.su.dsv.scipro.finalseminar.OppositionCriterion; import se.su.dsv.scipro.grading.GradingBasis; import se.su.dsv.scipro.grading.GradingReportTemplateService; import se.su.dsv.scipro.grading.GradingReportTemplateUpdate; @@ -20,7 +22,8 @@ import se.su.dsv.scipro.system.ProjectTypeService; import se.su.dsv.scipro.system.User; import se.su.dsv.scipro.util.Either; -public class GradingReportServiceImpl implements GradingReportTemplateService, GradingReportService { +public class GradingReportServiceImpl + implements GradingReportTemplateService, GradingReportService, FinalSeminarOppositionGrading { private final EventBus eventBus; private final ThesisSubmissionHistoryService thesisSubmissionHistoryService; @@ -289,4 +292,17 @@ public class GradingReportServiceImpl implements GradingReportTemplateService, G return gradingReportTemplateRepo.createTemplate(projectType, update); } + + @Override + @Transactional + public List oppositionCriteria(FinalSeminarOpposition opposition) { + return getSupervisorGradingReport(opposition.getProject(), opposition.getUser()) + .getIndividualCriteria() + .stream() + .filter(individualCriterion -> individualCriterion.getFlag() == AbstractGradingCriterion.Flag.OPPOSITION) + .map(GradingCriterion::getGradingCriterionPoints) + .flatMap(Collection::stream) + .map(gcp -> new OppositionCriterion(gcp.getPoint(), gcp.getDescription(Language.ENGLISH))) + .toList(); + } } diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html index 07e9b23129..e5e8c5ba16 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html @@ -25,7 +25,7 @@
- +
diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java index 6683d90aeb..e9f5ed0c33 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java @@ -11,10 +11,11 @@ import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; import org.apache.wicket.feedback.FencedFeedbackPanel; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.markup.html.form.LambdaChoiceRenderer; import org.apache.wicket.markup.html.form.TextArea; -import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; @@ -23,11 +24,12 @@ import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LambdaModel; +import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; -import org.apache.wicket.validation.validator.RangeValidator; import org.apache.wicket.validation.validator.StringValidator; import se.su.dsv.scipro.components.ListAdapterModel; +import se.su.dsv.scipro.components.StatelessModel; import se.su.dsv.scipro.profile.UserLinkPanel; import se.su.dsv.scipro.report.GradingReportService; import se.su.dsv.scipro.report.OppositionReportService; @@ -229,29 +231,25 @@ public class SeminarOppositionPanel extends Panel { private class FinalSeminarOppositionForm extends Form { + private IModel pointsModel = new StatelessModel<>(); + private IModel feedbackModel = new Model<>(); + public FinalSeminarOppositionForm(String id, final IModel finalSeminarOpposition) { super(id, finalSeminarOpposition); - FormComponent pointsField = new TextField<>( + IModel> criteriaModel = LoadableDetachableModel.of(() -> + finalSeminarOppositionService.getCriteriaForOpposition(finalSeminarOpposition.getObject()) + ); + + FormComponent pointsField = new DropDownChoice<>( POINTS, - LambdaModel.of( - finalSeminarOpposition, - FinalSeminarOpposition::getPoints, - FinalSeminarOpposition::setPoints - ) - ) - .add(RangeValidator.range(MIN_POINTS, MAX_POINTS)) - .setType(Integer.class) - .setRequired(true); + pointsModel, + criteriaModel, + new LambdaChoiceRenderer<>(OppositionCriterion::points) + ); + pointsField.setRequired(true); add(pointsField); - TextArea feedback = new TextArea<>( - GRADING_FEEDBACK, - LambdaModel.of( - finalSeminarOpposition, - FinalSeminarOpposition::getFeedback, - FinalSeminarOpposition::setFeedback - ) - ); + TextArea feedback = new TextArea<>(GRADING_FEEDBACK, feedbackModel); feedback.add(StringValidator.maximumLength(FEEDBACK_MAX_LENGTH)); feedback.setRequired(true); add(feedback); -- 2.39.5 From 2261096f0af03f17226efbdb0ab6a892ea797709 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Tue, 14 Jan 2025 15:33:19 +0100 Subject: [PATCH 04/23] Refactored away business logic from the Wicket panel Changed how data is moved from the opposition to the grading report Fixed a completely broken integration test --- .../FinalSeminarOppositionService.java | 2 ++ .../FinalSeminarOppositionServiceImpl.java | 7 ++++ .../scipro/report/GradingReportService.java | 3 -- .../report/GradingReportServiceImpl.java | 14 ++++++-- ...adingReportServiceImplIntegrationTest.java | 25 ++++++++------- .../finalseminar/SeminarOppositionPanel.java | 32 +++---------------- 6 files changed, 39 insertions(+), 44 deletions(-) diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java index 8394531675..a751149db1 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java @@ -8,4 +8,6 @@ public interface FinalSeminarOppositionService extends GenericService getCriteriaForOpposition(FinalSeminarOpposition opposition); + + void gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback); } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java index 47bc869a32..4d902f540f 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java @@ -25,4 +25,11 @@ public class FinalSeminarOppositionServiceImpl public List getCriteriaForOpposition(FinalSeminarOpposition opposition) { return finalSeminarOppositionGrading.oppositionCriteria(opposition); } + + @Override + public void gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback) { + // TODO: + // * save assessment + // * post events + } } diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportService.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportService.java index 1c38af9b44..409bf6385d 100644 --- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportService.java +++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportService.java @@ -2,7 +2,6 @@ package se.su.dsv.scipro.report; import java.time.Instant; import java.util.List; -import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; import se.su.dsv.scipro.grading.GradingBasis; import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.system.User; @@ -19,8 +18,6 @@ public interface GradingReportService { SupervisorGradingReport supervisorGradingReport ); - boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition); - GradingBasis getGradingBasis(Project project); GradingBasis updateGradingBasis(Project project, GradingBasis gradingBasis); diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java index f8486cf0fb..9afe32c8ab 100644 --- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java @@ -1,6 +1,7 @@ package se.su.dsv.scipro.report; import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import java.time.Clock; @@ -9,6 +10,7 @@ import java.time.LocalDate; import java.util.*; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionGrading; +import se.su.dsv.scipro.finalseminar.OppositionApprovedEvent; import se.su.dsv.scipro.finalseminar.OppositionCriterion; import se.su.dsv.scipro.grading.GradingBasis; import se.su.dsv.scipro.grading.GradingReportTemplateService; @@ -47,11 +49,11 @@ public class GradingReportServiceImpl this.supervisorGradingReportRepository = supervisorGradingReportRepository; this.gradingReportTemplateRepo = gradingReportTemplateRepo; this.projectTypeService = projectTypeService; + + eventBus.register(this); } - @Override - @Transactional - public boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition) { + private boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition) { for (GradingCriterion gradingCriterion : report.getIndividualCriteria()) { boolean isOppositionCriterion = gradingCriterion.getFlag() == GradingCriterion.Flag.OPPOSITION; boolean betterGrade = @@ -293,6 +295,12 @@ public class GradingReportServiceImpl return gradingReportTemplateRepo.createTemplate(projectType, update); } + @Subscribe + public void opponentApproved(OppositionApprovedEvent event) { + SupervisorGradingReport report = getSupervisorGradingReport(event.getProject(), event.getStudent()); + updateOppositionCriteria(report, event.getOpposition()); + } + @Override @Transactional public List oppositionCriteria(FinalSeminarOpposition opposition) { diff --git a/core/src/test/java/se/su/dsv/scipro/report/GradingReportServiceImplIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/report/GradingReportServiceImplIntegrationTest.java index 4eec189c37..597396bbea 100644 --- a/core/src/test/java/se/su/dsv/scipro/report/GradingReportServiceImplIntegrationTest.java +++ b/core/src/test/java/se/su/dsv/scipro/report/GradingReportServiceImplIntegrationTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.common.eventbus.EventBus; import jakarta.inject.Inject; import java.time.LocalDate; import java.time.Month; @@ -13,6 +14,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import se.su.dsv.scipro.finalseminar.FinalSeminar; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; +import se.su.dsv.scipro.finalseminar.OppositionApprovedEvent; import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.security.auth.roles.Roles; import se.su.dsv.scipro.system.DegreeType; @@ -31,6 +33,9 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest { @Inject private GradingReportServiceImpl gradingReportService; + @Inject + private EventBus eventBus; + private ProjectType projectType; private GradingReportTemplate gradingReportTemplate; private Project project; @@ -45,7 +50,6 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest { project = createProject(projectType, 30); gradingReportTemplate = createProjectGradingCriterion(gradingReportTemplate, 2); gradingReportTemplate = createIndividualGradingCriterion(gradingReportTemplate, 2); - gradingReport = createGradingReport(project, student); } @Test @@ -68,6 +72,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest { @Test public void submit_supervisor_grading_report_flags_report_as_submitted() { + gradingReport = createGradingReport(project, student); assessAllCriteria(gradingReport); Either, SupervisorGradingReport> result = gradingReportService.submitReport( gradingReport @@ -77,6 +82,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest { @Test public void submitting_supervisor_report_throws_exception_if_report_is_not_finished() { + gradingReport = createGradingReport(project, student); Either, SupervisorGradingReport> result = gradingReportService.submitReport( gradingReport ); @@ -86,38 +92,35 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest { @Test public void update_opposition_criterion() { addOppositionCriterion(); - boolean updated = updateOppositionCriterion(); + updateOppositionCriterion(); GradingCriterion oppositionCriterion = findOppositionCriterion(); assert oppositionCriterion != null; assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback()); assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints()); - assertTrue(updated); } @Test public void update_opposition_if_title_matches_english_title() { addOppositionCriterion(); - boolean updated = updateOppositionCriterion(); + updateOppositionCriterion(); GradingCriterion oppositionCriterion = findEnglishOppositionCriterion("Ö1 Opposition report"); assert oppositionCriterion != null; assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback()); assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints()); - assertTrue(updated); } @Test public void updating_opposition_criterion_does_nothing_if_criterion_already_has_values() { addOppositionCriterion(); assessAllCriteria(gradingReport); - boolean updated = updateOppositionCriterion(); + updateOppositionCriterion(); GradingCriterion oppositionCriterion = findOppositionCriterion(); assert oppositionCriterion != null; assertEquals(FEEDBACK, oppositionCriterion.getFeedback()); assertEquals((Integer) oppositionCriterion.getMaxPoints(), oppositionCriterion.getPoints()); - assertFalse(updated); } @Test @@ -151,9 +154,9 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest { gradingReport = createGradingReport(project, student); } - private boolean updateOppositionCriterion() { + private void updateOppositionCriterion() { FinalSeminarOpposition opposition = createFinalSeminarOpposition(); - return gradingReportService.updateOppositionCriteria(gradingReport, opposition); + eventBus.post(new OppositionApprovedEvent(opposition)); } private GradingCriterion findOppositionCriterion() { @@ -176,8 +179,8 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest { private FinalSeminarOpposition createFinalSeminarOpposition() { FinalSeminarOpposition finalSeminarOpposition = new FinalSeminarOpposition(); - finalSeminarOpposition.setProject(createProject(projectType, 30)); - finalSeminarOpposition.setUser(createStudent()); + finalSeminarOpposition.setProject(project); + finalSeminarOpposition.setUser(student); finalSeminarOpposition.setFinalSeminar(createFinalSeminar()); finalSeminarOpposition.setFeedback(FEEDBACK_ON_OPPOSITION); diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java index e9f5ed0c33..5e2799b0b4 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java @@ -33,7 +33,6 @@ import se.su.dsv.scipro.components.StatelessModel; import se.su.dsv.scipro.profile.UserLinkPanel; import se.su.dsv.scipro.report.GradingReportService; import se.su.dsv.scipro.report.OppositionReportService; -import se.su.dsv.scipro.report.SupervisorGradingReport; import se.su.dsv.scipro.security.auth.roles.Roles; import se.su.dsv.scipro.session.SciProSession; import se.su.dsv.scipro.system.ProjectModule; @@ -47,8 +46,6 @@ public class SeminarOppositionPanel extends Panel { public static final String REMOVE = "remove"; public static final String FORM = "form"; public static final String POINTS = "points"; - public static final int MIN_POINTS = 0; - public static final int MAX_POINTS = 2; public static final String GRADING_FEEDBACK = "gradingFeedback"; public static final int FEEDBACK_MAX_LENGTH = 2000; public static final String SUBMIT = "submit"; @@ -258,31 +255,12 @@ public class SeminarOppositionPanel extends Panel { new AjaxSubmitLink(SUBMIT) { @Override protected void onSubmit(AjaxRequestTarget target) { - if (getModelObject().getPoints().equals(0)) { - finalSeminarOpposition.getObject().setGrade(FinalSeminarGrade.NOT_APPROVED); - eventBus.post(new OppositionFailedEvent(finalSeminarOpposition.getObject())); - } else { - finalSeminarOpposition.getObject().setGrade(FinalSeminarGrade.APPROVED); - eventBus.post(new OppositionApprovedEvent(finalSeminarOpposition.getObject())); - } - finalSeminarOppositionService.save(finalSeminarOpposition.getObject()); - boolean updated = true; - if (gradingModuleIsOnForProjectType()) { - SupervisorGradingReport report = gradingReportService.getSupervisorGradingReport( - finalSeminarOpposition.getObject().getProject(), - finalSeminarOpposition.getObject().getUser() - ); - updated = gradingReportService.updateOppositionCriteria( - report, - finalSeminarOpposition.getObject() - ); - } - success( - getString( - updated ? "feedback.opponent.updated" : "feedback.opponent.not.updated", - finalSeminarOpposition - ) + finalSeminarOppositionService.gradeOpponent( + finalSeminarOpposition.getObject(), + pointsModel.getObject().points(), + feedbackModel.getObject() ); + success(getString("feedback.opponent.updated", finalSeminarOpposition)); target.add(feedbackPanel); target.add(oppositionContainer); } -- 2.39.5 From 35b60df1cb55d194dae751707a0326b7c84d0f9c Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Thu, 16 Jan 2025 14:55:42 +0100 Subject: [PATCH 05/23] Use the same project type for opponent and seminar --- .../FinalSeminarOppositionServiceImplIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java index 9b27323fd4..cee56e581b 100644 --- a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java +++ b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java @@ -93,7 +93,7 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio FinalSeminarOpposition opposition = new FinalSeminarOpposition(); opposition.setFinalSeminar(finalSeminar); opposition.setUser(student); - opposition.setProject(createProject(createProjectType())); + opposition.setProject(createProject(projectType)); return save(opposition); } } -- 2.39.5 From cebd005f88a69aae8e37b530a60fae54f597c1bb Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Thu, 16 Jan 2025 14:59:46 +0100 Subject: [PATCH 06/23] Verify that opposition criteria are based on the grading report template --- ...rOppositionServiceImplIntegrationTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java index cee56e581b..053ccee8d8 100644 --- a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java +++ b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java @@ -6,9 +6,12 @@ import jakarta.inject.Inject; import java.time.LocalDate; import java.time.Month; import java.util.Date; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import se.su.dsv.scipro.project.Project; +import se.su.dsv.scipro.report.AbstractGradingCriterion; +import se.su.dsv.scipro.report.GradingCriterionPointTemplate; import se.su.dsv.scipro.report.GradingReportTemplate; import se.su.dsv.scipro.report.OppositionReport; import se.su.dsv.scipro.system.DegreeType; @@ -46,6 +49,28 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio assertEquals(0, finalSeminarOppositionService.count()); } + @Test + public void opposition_criteria_are_taken_from_the_grading_report_template() { + FinalSeminarOpposition opposition = createOpposition(finalSeminar, createUser()); + GradingReportTemplate gradingReportTemplate = createGradingReportTemplate(); + + GradingCriterionPointTemplate failingCriterion = new GradingCriterionPointTemplate(); + failingCriterion.setPoint(0); + GradingCriterionPointTemplate passingCriterion = new GradingCriterionPointTemplate(); + passingCriterion.setPoint(1); + + gradingReportTemplate.addIndividualCriterion( + "Criterion 1", + "Criterion 1", + 1, + List.of(failingCriterion, passingCriterion), + AbstractGradingCriterion.Flag.OPPOSITION + ); + save(gradingReportTemplate); + + assertEquals(2, finalSeminarOppositionService.getCriteriaForOpposition(opposition).size()); + } + private void createOppositionReport(FinalSeminarOpposition opposition) { OppositionReport report = new OppositionReport(createGradingReportTemplate(), opposition); opposition.setOppositionReport(report); -- 2.39.5 From 12338425116bdfaccd080ad4116a24f578dd13e7 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Fri, 17 Jan 2025 12:36:08 +0100 Subject: [PATCH 07/23] Implement gradeOpponent --- .../java/se/su/dsv/scipro/CoreConfig.java | 11 +++- .../finalseminar/AbstractOppositionEvent.java | 14 +++++ .../FinalSeminarOppositionGrading.java | 2 +- .../FinalSeminarOppositionService.java | 6 +- .../FinalSeminarOppositionServiceImpl.java | 39 +++++++++++-- .../finalseminar/OppositionCriteria.java | 7 +++ .../finalseminar/OppositionCriterion.java | 3 - .../finalseminar/PointNotValidException.java | 27 +++++++++ .../report/GradingReportServiceImpl.java | 19 +++++-- ...rOppositionServiceImplIntegrationTest.java | 57 ++++++++++++++++++- .../se/su/dsv/scipro/test/SpringTest.java | 23 ++++++++ .../finalseminar/SeminarOppositionPanel.java | 31 +++++----- .../SeminarOppositionPanel.properties | 1 + .../SeminarOppositionPanelTest.java | 9 +++ 14 files changed, 215 insertions(+), 34 deletions(-) create mode 100644 core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriteria.java delete mode 100644 core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriterion.java create mode 100644 core/src/main/java/se/su/dsv/scipro/finalseminar/PointNotValidException.java diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java index ee89a28365..537fdb9976 100644 --- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java +++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java @@ -433,9 +433,16 @@ public class CoreConfig { @Bean public FinalSeminarOppositionServiceImpl finalSeminarOppositionService( Provider em, - FinalSeminarOppositionGrading finalSeminarOppositionGrading + FinalSeminarOppositionGrading finalSeminarOppositionGrading, + EventBus eventBus, + FinalSeminarOppositionRepo finalSeminarOppositionRepository ) { - return new FinalSeminarOppositionServiceImpl(em, finalSeminarOppositionGrading); + return new FinalSeminarOppositionServiceImpl( + em, + finalSeminarOppositionGrading, + eventBus, + finalSeminarOppositionRepository + ); } @Bean diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/AbstractOppositionEvent.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/AbstractOppositionEvent.java index e60a7b2bc7..2143479147 100644 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/AbstractOppositionEvent.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/AbstractOppositionEvent.java @@ -1,5 +1,6 @@ package se.su.dsv.scipro.finalseminar; +import java.util.Objects; import se.su.dsv.scipro.project.Project; import se.su.dsv.scipro.system.User; @@ -26,4 +27,17 @@ class AbstractOppositionEvent { public FinalSeminarOpposition getOpposition() { return opposition; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AbstractOppositionEvent that = (AbstractOppositionEvent) o; + return Objects.equals(opposition, that.opposition); + } + + @Override + public int hashCode() { + return Objects.hashCode(opposition); + } } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionGrading.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionGrading.java index 0ac5bba547..2962d1b25e 100644 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionGrading.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionGrading.java @@ -3,5 +3,5 @@ package se.su.dsv.scipro.finalseminar; import java.util.List; public interface FinalSeminarOppositionGrading { - List oppositionCriteria(FinalSeminarOpposition opposition); + OppositionCriteria oppositionCriteria(FinalSeminarOpposition opposition); } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java index a751149db1..e320c1da2e 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java @@ -1,13 +1,13 @@ package se.su.dsv.scipro.finalseminar; -import java.util.List; import se.su.dsv.scipro.system.GenericService; public interface FinalSeminarOppositionService extends GenericService { @Override void delete(Long aLong); - List getCriteriaForOpposition(FinalSeminarOpposition opposition); + OppositionCriteria getCriteriaForOpposition(FinalSeminarOpposition opposition); - void gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback); + FinalSeminarOpposition gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback) + throws PointNotValidException; } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java index 4d902f540f..abf7e61246 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java @@ -1,8 +1,10 @@ package se.su.dsv.scipro.finalseminar; +import com.google.common.eventbus.EventBus; import jakarta.inject.Inject; import jakarta.inject.Provider; import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; import java.util.List; import se.su.dsv.scipro.system.AbstractServiceImpl; @@ -11,25 +13,50 @@ public class FinalSeminarOppositionServiceImpl implements FinalSeminarOppositionService { private final FinalSeminarOppositionGrading finalSeminarOppositionGrading; + private final EventBus eventBus; + private final FinalSeminarOppositionRepo finalSeminarOppositionRepository; @Inject public FinalSeminarOppositionServiceImpl( Provider em, - FinalSeminarOppositionGrading finalSeminarOppositionGrading + FinalSeminarOppositionGrading finalSeminarOppositionGrading, + EventBus eventBus, + FinalSeminarOppositionRepo finalSeminarOppositionRepository ) { super(em, FinalSeminarOpposition.class, QFinalSeminarOpposition.finalSeminarOpposition); this.finalSeminarOppositionGrading = finalSeminarOppositionGrading; + this.eventBus = eventBus; + this.finalSeminarOppositionRepository = finalSeminarOppositionRepository; } @Override - public List getCriteriaForOpposition(FinalSeminarOpposition opposition) { + public OppositionCriteria getCriteriaForOpposition(FinalSeminarOpposition opposition) { return finalSeminarOppositionGrading.oppositionCriteria(opposition); } @Override - public void gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback) { - // TODO: - // * save assessment - // * post events + @Transactional + public FinalSeminarOpposition gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback) + throws PointNotValidException { + OppositionCriteria criteriaForOpposition = getCriteriaForOpposition(opposition); + boolean validPoints = criteriaForOpposition + .pointsAvailable() + .stream() + .anyMatch(criterion -> criterion.value() == points); + if (!validPoints) { + throw new PointNotValidException(points, List.of(0, 1)); + } + + opposition.setPoints(points); + opposition.setFeedback(feedback); + FinalSeminarOpposition assessedOpposition = finalSeminarOppositionRepository.save(opposition); + + if (criteriaForOpposition.pointsToPass() > points) { + eventBus.post(new OppositionFailedEvent(assessedOpposition)); + } else { + eventBus.post(new OppositionApprovedEvent(assessedOpposition)); + } + + return assessedOpposition; } } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriteria.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriteria.java new file mode 100644 index 0000000000..96be8aa6a1 --- /dev/null +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriteria.java @@ -0,0 +1,7 @@ +package se.su.dsv.scipro.finalseminar; + +import java.util.List; + +public record OppositionCriteria(int pointsToPass, List pointsAvailable) { + public record Point(int value, String requirement) {} +} diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriterion.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriterion.java deleted file mode 100644 index 9915cf55f1..0000000000 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionCriterion.java +++ /dev/null @@ -1,3 +0,0 @@ -package se.su.dsv.scipro.finalseminar; - -public record OppositionCriterion(int points, String requirement) {} diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/PointNotValidException.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/PointNotValidException.java new file mode 100644 index 0000000000..82e92fc68f --- /dev/null +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/PointNotValidException.java @@ -0,0 +1,27 @@ +package se.su.dsv.scipro.finalseminar; + +import java.util.List; + +public class PointNotValidException extends Exception { + + private final int givenValue; + private final List acceptableValues; + + public PointNotValidException(int givenValue, List acceptableValues) { + this.givenValue = givenValue; + this.acceptableValues = acceptableValues; + } + + public int givenValue() { + return givenValue; + } + + public List acceptableValues() { + return acceptableValues; + } + + @Override + public String toString() { + return "PointNotValidException{" + "givenValue=" + givenValue + ", acceptableValues=" + acceptableValues + '}'; + } +} diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java index 9afe32c8ab..66d47f0887 100644 --- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java @@ -11,7 +11,7 @@ import java.util.*; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionGrading; import se.su.dsv.scipro.finalseminar.OppositionApprovedEvent; -import se.su.dsv.scipro.finalseminar.OppositionCriterion; +import se.su.dsv.scipro.finalseminar.OppositionCriteria; import se.su.dsv.scipro.grading.GradingBasis; import se.su.dsv.scipro.grading.GradingReportTemplateService; import se.su.dsv.scipro.grading.GradingReportTemplateUpdate; @@ -303,14 +303,25 @@ public class GradingReportServiceImpl @Override @Transactional - public List oppositionCriteria(FinalSeminarOpposition opposition) { - return getSupervisorGradingReport(opposition.getProject(), opposition.getUser()) + public OppositionCriteria oppositionCriteria(FinalSeminarOpposition opposition) { + SupervisorGradingReport supervisorGradingReport = getSupervisorGradingReport( + opposition.getProject(), + opposition.getUser() + ); + Optional oppositionGradingCriteria = supervisorGradingReport .getIndividualCriteria() .stream() .filter(individualCriterion -> individualCriterion.getFlag() == AbstractGradingCriterion.Flag.OPPOSITION) + .findAny(); + if (oppositionGradingCriteria.isEmpty()) { + return new OppositionCriteria(0, List.of()); + } + List points = oppositionGradingCriteria + .stream() .map(GradingCriterion::getGradingCriterionPoints) .flatMap(Collection::stream) - .map(gcp -> new OppositionCriterion(gcp.getPoint(), gcp.getDescription(Language.ENGLISH))) + .map(gcp -> new OppositionCriteria.Point(gcp.getPoint(), gcp.getDescription(Language.ENGLISH))) .toList(); + return new OppositionCriteria(oppositionGradingCriteria.get().getPointsRequiredToPass(), points); } } diff --git a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java index 053ccee8d8..fb39050f2d 100644 --- a/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java +++ b/core/src/test/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImplIntegrationTest.java @@ -1,6 +1,11 @@ package se.su.dsv.scipro.finalseminar; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import jakarta.inject.Inject; import java.time.LocalDate; @@ -52,6 +57,56 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio @Test public void opposition_criteria_are_taken_from_the_grading_report_template() { FinalSeminarOpposition opposition = createOpposition(finalSeminar, createUser()); + createSimpleGradingReportTemplateWithPassFail(); + + assertEquals(2, finalSeminarOppositionService.getCriteriaForOpposition(opposition).pointsAvailable().size()); + } + + @Test + public void can_not_grade_outside_criterion() { + FinalSeminarOpposition opposition = createOpposition(finalSeminar, createUser()); + createSimpleGradingReportTemplateWithPassFail(); + + PointNotValidException exception = assertThrows(PointNotValidException.class, () -> + finalSeminarOppositionService.gradeOpponent(opposition, 2, "Feedback") + ); + assertEquals(2, exception.givenValue()); + assertThat(exception.acceptableValues(), hasSize(2)); + assertThat(exception.acceptableValues(), contains(0, 1)); + } + + @Test + public void publishes_failed_event_when_grading_with_failing_criterion() throws Exception { + FinalSeminarOpposition opposition = createOpposition(finalSeminar, createUser()); + createSimpleGradingReportTemplateWithPassFail(); + + finalSeminarOppositionService.gradeOpponent(opposition, 0, "Feedback"); + + assertThat(getPublishedEvents(), hasItem(new OppositionFailedEvent(opposition))); + } + + @Test + public void publishes_approved_event_when_grading_with_passing_criterion() throws Exception { + FinalSeminarOpposition opposition = createOpposition(finalSeminar, createUser()); + createSimpleGradingReportTemplateWithPassFail(); + + finalSeminarOppositionService.gradeOpponent(opposition, 1, "Feedback"); + + assertThat(getPublishedEvents(), hasItem(new OppositionApprovedEvent(opposition))); + } + + @Test + public void stores_assessment() throws Exception { + FinalSeminarOpposition opposition = createOpposition(finalSeminar, createUser()); + createSimpleGradingReportTemplateWithPassFail(); + + FinalSeminarOpposition graded = finalSeminarOppositionService.gradeOpponent(opposition, 1, "Feedback"); + + assertEquals(1, graded.getPoints()); + assertEquals("Feedback", graded.getFeedback()); + } + + private void createSimpleGradingReportTemplateWithPassFail() { GradingReportTemplate gradingReportTemplate = createGradingReportTemplate(); GradingCriterionPointTemplate failingCriterion = new GradingCriterionPointTemplate(); @@ -67,8 +122,6 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio AbstractGradingCriterion.Flag.OPPOSITION ); save(gradingReportTemplate); - - assertEquals(2, finalSeminarOppositionService.getCriteriaForOpposition(opposition).size()); } private void createOppositionReport(FinalSeminarOpposition opposition) { diff --git a/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java b/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java index 04d0f70da7..2d52bd0d36 100644 --- a/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java +++ b/core/src/test/java/se/su/dsv/scipro/test/SpringTest.java @@ -1,11 +1,14 @@ package se.su.dsv.scipro.test; +import com.google.common.eventbus.EventBus; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.EntityTransaction; import jakarta.persistence.Persistence; import java.sql.SQLException; import java.time.Clock; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; import org.flywaydb.core.Flyway; @@ -35,6 +38,8 @@ public abstract class SpringTest { @Container static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + private CapturingEventBus capturingEventBus; + @BeforeEach public final void prepareSpring() throws SQLException { MariaDbDataSource dataSource = new MariaDbDataSource(mariaDBContainer.getJdbcUrl()); @@ -50,8 +55,11 @@ public abstract class SpringTest { transaction.begin(); transaction.setRollbackOnly(); + capturingEventBus = new CapturingEventBus(); + AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); + annotationConfigApplicationContext.registerBean("eventBus", EventBus.class, () -> this.capturingEventBus); annotationConfigApplicationContext.register(TestContext.class); annotationConfigApplicationContext.getBeanFactory().registerSingleton("entityManager", this.entityManager); annotationConfigApplicationContext.refresh(); @@ -75,6 +83,10 @@ public abstract class SpringTest { } } + protected List getPublishedEvents() { + return capturingEventBus.publishedEvents; + } + @Configuration(proxyBeanMethods = false) @Import({ CoreConfig.class, RepositoryConfiguration.class }) public static class TestContext { @@ -106,4 +118,15 @@ public abstract class SpringTest { return currentProfile; } } + + private static class CapturingEventBus extends EventBus { + + private List publishedEvents = new ArrayList<>(); + + @Override + public void post(Object event) { + publishedEvents.add(event); + super.post(event); + } + } } diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java index 5e2799b0b4..f8ed61ccce 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java @@ -228,20 +228,20 @@ public class SeminarOppositionPanel extends Panel { private class FinalSeminarOppositionForm extends Form { - private IModel pointsModel = new StatelessModel<>(); + private IModel pointsModel = new StatelessModel<>(); private IModel feedbackModel = new Model<>(); public FinalSeminarOppositionForm(String id, final IModel finalSeminarOpposition) { super(id, finalSeminarOpposition); - IModel> criteriaModel = LoadableDetachableModel.of(() -> + IModel criteriaModel = LoadableDetachableModel.of(() -> finalSeminarOppositionService.getCriteriaForOpposition(finalSeminarOpposition.getObject()) ); - FormComponent pointsField = new DropDownChoice<>( + FormComponent pointsField = new DropDownChoice<>( POINTS, pointsModel, - criteriaModel, - new LambdaChoiceRenderer<>(OppositionCriterion::points) + criteriaModel.map(OppositionCriteria::pointsAvailable), + new LambdaChoiceRenderer<>(OppositionCriteria.Point::value) ); pointsField.setRequired(true); add(pointsField); @@ -255,14 +255,19 @@ public class SeminarOppositionPanel extends Panel { new AjaxSubmitLink(SUBMIT) { @Override protected void onSubmit(AjaxRequestTarget target) { - finalSeminarOppositionService.gradeOpponent( - finalSeminarOpposition.getObject(), - pointsModel.getObject().points(), - feedbackModel.getObject() - ); - success(getString("feedback.opponent.updated", finalSeminarOpposition)); - target.add(feedbackPanel); - target.add(oppositionContainer); + try { + finalSeminarOppositionService.gradeOpponent( + finalSeminarOpposition.getObject(), + pointsModel.getObject().value(), + feedbackModel.getObject() + ); + success(getString("feedback.opponent.updated", finalSeminarOpposition)); + target.add(feedbackPanel); + target.add(oppositionContainer); + } catch (PointNotValidException e) { + error(getString("point.not.valid")); + target.add(feedbackPanel); + } } @Override diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties index 4976fecc4d..24ac972a57 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties @@ -8,6 +8,7 @@ gradingFeedback.Required = You need to write a motivation points.Required = Points are required opponents.form.points.RangeValidator.range= Points assigned must be between ${minimum} and ${maximum} feedback.opponent.updated= Opponent ${user.fullName} feedback updated. +point.not.valid=You need to assign points from the available selection. feedback.opponent.not.updated= Opponent ${user.fullName} feedback updated. Point and motivation could not be transferred to the students final grading report since the opponents supervisor already filled it in. criteria= As the supervisor on this final seminar you are also required to grade the opponents opposition report.\ <\br><\br>Requirement for 1 point: <\br>That the opposition report provides a short summary of the evaluated thesis, that it deliberates about the scientific basis, originality, \ diff --git a/view/src/test/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanelTest.java b/view/src/test/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanelTest.java index 66e5c2224d..587e4559d2 100644 --- a/view/src/test/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanelTest.java +++ b/view/src/test/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanelTest.java @@ -61,6 +61,15 @@ public class SeminarOppositionPanelTest extends SciProTest { finalSeminar.setProject(project); setLoggedInAs(supervisorUser); + + Mockito.lenient() + .when(finalSeminarOppositionService.getCriteriaForOpposition(opposition)) + .thenReturn( + new OppositionCriteria( + 1, + List.of(new OppositionCriteria.Point(0, ""), new OppositionCriteria.Point(1, "Filled in report")) + ) + ); } @Test -- 2.39.5 From 21d7c034bf6593ad61dbb561eee4a1a8b20a2003 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Mon, 20 Jan 2025 10:47:14 +0100 Subject: [PATCH 08/23] The info box is now based on the criteria rather than hard-coded text --- .../scipro/report/GradingReportServiceImpl.java | 7 ++++++- .../finalseminar/SeminarOppositionPanel.html | 11 +++++++++-- .../finalseminar/SeminarOppositionPanel.java | 17 +++++++++++++++++ .../SeminarOppositionPanel.properties | 7 +------ 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java index 66d47f0887..1d39c7306a 100644 --- a/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/report/GradingReportServiceImpl.java @@ -320,7 +320,12 @@ public class GradingReportServiceImpl .stream() .map(GradingCriterion::getGradingCriterionPoints) .flatMap(Collection::stream) - .map(gcp -> new OppositionCriteria.Point(gcp.getPoint(), gcp.getDescription(Language.ENGLISH))) + .map(gcp -> + new OppositionCriteria.Point( + gcp.getPoint(), + Objects.requireNonNullElse(gcp.getDescription(Language.ENGLISH), "") + ) + ) .toList(); return new OppositionCriteria(oppositionGradingCriteria.get().getPointsRequiredToPass(), points); } diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html index e5e8c5ba16..f0d3c04f34 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.html @@ -19,8 +19,15 @@
-
- +
+
+

+ +

+

+ +

+
diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java index f8ed61ccce..3188f17091 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java @@ -237,6 +237,15 @@ public class SeminarOppositionPanel extends Panel { finalSeminarOppositionService.getCriteriaForOpposition(finalSeminarOpposition.getObject()) ); + add( + new ListView<>("requirements", criteriaModel.map(this::getPointsWithRequirements)) { + @Override + protected void populateItem(ListItem item) { + item.add(new Label("requirement", item.getModel().map(OppositionCriteria.Point::requirement))); + } + } + ); + FormComponent pointsField = new DropDownChoice<>( POINTS, pointsModel, @@ -295,6 +304,14 @@ public class SeminarOppositionPanel extends Panel { ); } + private List getPointsWithRequirements(OppositionCriteria oppositionCriteria) { + return oppositionCriteria + .pointsAvailable() + .stream() + .filter(point -> !point.requirement().isBlank()) + .toList(); + } + @Override protected void onConfigure() { super.onConfigure(); diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties index 24ac972a57..3d2bc5da31 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties @@ -10,12 +10,7 @@ opponents.form.points.RangeValidator.range= Points assigned must be between ${mi feedback.opponent.updated= Opponent ${user.fullName} feedback updated. point.not.valid=You need to assign points from the available selection. feedback.opponent.not.updated= Opponent ${user.fullName} feedback updated. Point and motivation could not be transferred to the students final grading report since the opponents supervisor already filled it in. -criteria= As the supervisor on this final seminar you are also required to grade the opponents opposition report.\ - <\br><\br>Requirement for 1 point: <\br>That the opposition report provides a short summary of the evaluated thesis, that it deliberates about the scientific basis, originality, \ - significance, and formulation of the problem and research question, as well as that it contains clear suggestions for improvements. \ - <\br><\br>For 2 points the following is also required: \ - <\br>That the opposition report thoroughly and in a well-balanced way describes from numerous aspects the strengths and weaknesses of the evaluated thesis and that it \ - offers clear and well- motivated suggestions for improvements. +criteria= As the supervisor on this final seminar you are also required to grade the opponents opposition report. opposition.report= Opposition report: removed= Opponent ${user.fullName} successfully removed opposition.report.removed= Opposition report successfully removed -- 2.39.5 From 6b24e8469473a7e0a4a780d580ffd40faae30268 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Tue, 14 Jan 2025 11:16:38 +0100 Subject: [PATCH 09/23] Request improvements --- .../java/se/su/dsv/scipro/CoreConfig.java | 6 +++-- .../finalseminar/FinalSeminarOpposition.java | 25 +++++++++++++++++++ .../FinalSeminarOppositionService.java | 2 ++ .../FinalSeminarOppositionServiceImpl.java | 19 +++++++++++++- ...eminar_opposition_request_improvements.sql | 3 +++ 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 core/src/main/resources/db/migration/V5__final_seminar_opposition_request_improvements.sql diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java index 537fdb9976..6ecd8b4d2c 100644 --- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java +++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java @@ -435,13 +435,15 @@ public class CoreConfig { Provider em, FinalSeminarOppositionGrading finalSeminarOppositionGrading, EventBus eventBus, - FinalSeminarOppositionRepo finalSeminarOppositionRepository + FinalSeminarOppositionRepo finalSeminarOppositionRepository, + Clock clock ) { return new FinalSeminarOppositionServiceImpl( em, finalSeminarOppositionGrading, eventBus, - finalSeminarOppositionRepository + finalSeminarOppositionRepository, + clock ); } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOpposition.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOpposition.java index 9825e981a8..8a1533fedd 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOpposition.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOpposition.java @@ -8,6 +8,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; +import java.time.Instant; import java.util.Objects; import se.su.dsv.scipro.file.FileReference; import se.su.dsv.scipro.project.Project; @@ -31,6 +32,14 @@ public class FinalSeminarOpposition extends FinalSeminarParticipation { @Column(name = "feedback", length = FEEDBACK_LENGTH) private String feedback; + @Basic + @Column(name = "improvements_requested_at") + private Instant improvementsRequestedAt; + + @Basic + @Column(name = "supervisor_improvements_comment") + private String supervisorCommentForImprovements; + // ---------------------------------------------------------------------------------- // JPA-mappings of foreign keys in this table (final_seminar_opposition) referencing // other tables. @@ -92,6 +101,22 @@ public class FinalSeminarOpposition extends FinalSeminarParticipation { this.oppositionReport = oppositionReport; } + public Instant getImprovementsRequestedAt() { + return improvementsRequestedAt; + } + + public void setImprovementsRequestedAt(Instant improvementsRequestedAt) { + this.improvementsRequestedAt = improvementsRequestedAt; + } + + public String getSupervisorCommentForImprovements() { + return supervisorCommentForImprovements; + } + + public void setSupervisorCommentForImprovements(String supervisorCommentsForImprovements) { + this.supervisorCommentForImprovements = supervisorCommentsForImprovements; + } + // ---------------------------------------------------------------------------------- // Methods Common To All Objects // ---------------------------------------------------------------------------------- diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java index e320c1da2e..fbd5003f7b 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java @@ -10,4 +10,6 @@ public interface FinalSeminarOppositionService extends GenericService em, FinalSeminarOppositionGrading finalSeminarOppositionGrading, EventBus eventBus, - FinalSeminarOppositionRepo finalSeminarOppositionRepository + FinalSeminarOppositionRepo finalSeminarOppositionRepository, + Clock clock ) { super(em, FinalSeminarOpposition.class, QFinalSeminarOpposition.finalSeminarOpposition); this.finalSeminarOppositionGrading = finalSeminarOppositionGrading; this.eventBus = eventBus; this.finalSeminarOppositionRepository = finalSeminarOppositionRepository; + this.clock = clock; } @Override @@ -59,4 +64,16 @@ public class FinalSeminarOppositionServiceImpl return assessedOpposition; } + + @Override + @Transactional + public void requestImprovements(FinalSeminarOpposition opposition, String supervisorComment) { + OppositionReport oppositionReport = opposition.getOppositionReport(); + if (oppositionReport == null) { + return; + } + oppositionReport.setSubmitted(false); + opposition.setImprovementsRequestedAt(clock.instant()); + opposition.setSupervisorCommentForImprovements(supervisorComment); + } } diff --git a/core/src/main/resources/db/migration/V5__final_seminar_opposition_request_improvements.sql b/core/src/main/resources/db/migration/V5__final_seminar_opposition_request_improvements.sql new file mode 100644 index 0000000000..e922687232 --- /dev/null +++ b/core/src/main/resources/db/migration/V5__final_seminar_opposition_request_improvements.sql @@ -0,0 +1,3 @@ +ALTER TABLE `final_seminar_opposition` + ADD COLUMN `improvements_requested_at` DATETIME NULL, + ADD COLUMN `supervisor_improvements_comment` TEXT NULL; -- 2.39.5 From d01dee1ec78cfaca8f4ae90a658df350829ee208 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Mon, 20 Jan 2025 13:19:03 +0100 Subject: [PATCH 10/23] Add a thesis document to the final seminar to help testing --- .../se/su/dsv/scipro/DataInitializer.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java index 80f06e8e64..98ebc08e63 100644 --- a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java +++ b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java @@ -4,12 +4,18 @@ import jakarta.inject.Inject; import jakarta.inject.Provider; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.time.LocalDate; import java.time.LocalTime; import java.time.Month; import java.time.ZonedDateTime; import java.util.*; +import java.util.function.Function; import se.su.dsv.scipro.checklist.ChecklistCategory; +import se.su.dsv.scipro.file.FileReference; +import se.su.dsv.scipro.file.FileService; +import se.su.dsv.scipro.file.FileUpload; import se.su.dsv.scipro.finalseminar.FinalSeminar; import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition; import se.su.dsv.scipro.match.ApplicationPeriod; @@ -42,6 +48,9 @@ public class DataInitializer implements Lifecycle { @Inject private MilestoneActivityTemplateService milestoneActivityTemplateService; + @Inject + private FileService fileService; + @Inject private CurrentProfile profile; @@ -102,11 +111,17 @@ public class DataInitializer implements Lifecycle { } private void createPastFinalSeminar() { + FileReference document = fileService.storeFile( + new SimpleTextFile(sture_student, "document.txt", "Hello World") + ); + FinalSeminar finalSeminar = new FinalSeminar(); finalSeminar.setStartDate(Date.from(ZonedDateTime.now().minusDays(1).toInstant())); finalSeminar.setProject(project1); finalSeminar.setRoom("zoom"); finalSeminar.setPresentationLanguage(Language.ENGLISH); + finalSeminar.setDocument(document); + finalSeminar.setDocumentUploadDate(document.getFileDescription().getDateCreated()); FinalSeminarOpposition opponent = new FinalSeminarOpposition(); opponent.setProject(project2); @@ -1924,4 +1939,42 @@ public class DataInitializer implements Lifecycle { em.get().persist(entity); return entity; } + + private static final class SimpleTextFile implements FileUpload { + + private final User uploader; + private final String fileName; + private final String content; + + private SimpleTextFile(User uploader, String fileName, String content) { + this.uploader = uploader; + this.fileName = fileName; + this.content = content; + } + + @Override + public String getFileName() { + return fileName; + } + + @Override + public String getContentType() { + return "text/plain"; + } + + @Override + public User getUploader() { + return uploader; + } + + @Override + public long getSize() { + return content.length(); + } + + @Override + public T handleData(Function handler) { + return handler.apply(new ByteArrayInputStream(content.getBytes())); + } + } } -- 2.39.5 From 45c21e3fe43f2615e2ad8170c06d371bd7df6fe8 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Mon, 20 Jan 2025 13:30:20 +0100 Subject: [PATCH 11/23] Store the request for improvements --- .../finalseminar/SeminarOppositionPanel.java | 20 +++++++++++++++++-- .../SeminarOppositionPanel.properties | 5 ++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java index 3188f17091..976c834af7 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java @@ -2,6 +2,9 @@ package se.su.dsv.scipro.finalseminar; import com.google.common.eventbus.EventBus; import jakarta.inject.Inject; +import java.time.Duration; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Date; import java.util.List; import java.util.Objects; @@ -357,8 +360,21 @@ public class SeminarOppositionPanel extends Panel { @Override protected void onSubmit() { - System.out.println("Request improvements submitted"); - System.out.println("Feedback to opponent: " + feedbackToOpponentModel.getObject()); + // TODO: return deadline + finalSeminarOppositionService.requestImprovements(getModelObject(), feedbackToOpponentModel.getObject()); + + record ImprovementFeedback(String fullName, ZonedDateTime deadline) {} + ZonedDateTime deadline = getModelObject() + .getFinalSeminar() + .getStartDate() + .toInstant() + .plus(Duration.ofDays(10)) + .atZone(ZoneId.of("Europe/Stockholm")); + success( + getString("feedback.opponent.requested.improvements", () -> + new ImprovementFeedback(getModelObject().getUser().getFullName(), deadline) + ) + ); } } } diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties index 3d2bc5da31..0a041037c6 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.properties @@ -16,4 +16,7 @@ removed= Opponent ${user.fullName} successfully removed opposition.report.removed= Opposition report successfully removed are.you.sure= Are you sure you want to remove this opponent report? no.opponents= There are no opponents registered yet. -noOppositionReportYet= No opposition report has been submitted yet. \ No newline at end of file +noOppositionReportYet= No opposition report has been submitted yet. +feedback.opponent.requested.improvements = You've requested improvements from ${fullName}. \ + They have until ${deadline} to make the changes. If they fail to resubmit by that point they \ + will automatically get a failing grade. -- 2.39.5 From 7bb37407d69ace4388cb9f5d2d43ff8db3494a09 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Mon, 20 Jan 2025 14:21:51 +0100 Subject: [PATCH 12/23] Calculate correct deadline for improvement submission --- .../java/se/su/dsv/scipro/CoreConfig.java | 8 ++++-- .../FinalSeminarOppositionService.java | 6 ++++- .../FinalSeminarOppositionServiceImpl.java | 27 ++++++++++++++++--- .../finalseminar/FinalSeminarSettings.java | 14 ++++++++++ .../se/su/dsv/scipro/misc/DaysService.java | 10 +++++++ ...nar_work_days_to_fix_opposition_report.sql | 2 ++ .../AdminFinalSeminarSettingsPage.html | 7 +++++ .../AdminFinalSeminarSettingsPage.java | 11 ++++++++ .../finalseminar/SeminarOppositionPanel.java | 17 +++++------- 9 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 core/src/main/resources/db/migration/V6__final_seminar_work_days_to_fix_opposition_report.sql diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java index 6ecd8b4d2c..d94c934073 100644 --- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java +++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java @@ -436,14 +436,18 @@ public class CoreConfig { FinalSeminarOppositionGrading finalSeminarOppositionGrading, EventBus eventBus, FinalSeminarOppositionRepo finalSeminarOppositionRepository, - Clock clock + Clock clock, + FinalSeminarSettingsService finalSeminarSettingsService, + DaysService daysService ) { return new FinalSeminarOppositionServiceImpl( em, finalSeminarOppositionGrading, eventBus, finalSeminarOppositionRepository, - clock + clock, + finalSeminarSettingsService, + daysService ); } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java index fbd5003f7b..e3b82f0682 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java @@ -1,5 +1,6 @@ package se.su.dsv.scipro.finalseminar; +import java.time.Instant; import se.su.dsv.scipro.system.GenericService; public interface FinalSeminarOppositionService extends GenericService { @@ -11,5 +12,8 @@ public interface FinalSeminarOppositionService extends GenericService
+
+ +
+ +
+
+
diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/AdminFinalSeminarSettingsPage.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/AdminFinalSeminarSettingsPage.java index 59dad683ab..f04ffe4136 100755 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/AdminFinalSeminarSettingsPage.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/AdminFinalSeminarSettingsPage.java @@ -131,6 +131,17 @@ public class AdminFinalSeminarSettingsPage extends AbstractAdminSystemPage { Integer.class ) ); + add( + new RequiredTextField<>( + "work_days_to_fix_requested_improvements_to_opposition_report", + LambdaModel.of( + model, + FinalSeminarSettings::getWorkDaysToFixRequestedImprovementsToOppositionReport, + FinalSeminarSettings::setWorkDaysToFixRequestedImprovementsToOppositionReport + ), + Integer.class + ) + ); add( new CheckBox( SEMINAR_PDF, diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java index 976c834af7..bb58ce4a9c 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/SeminarOppositionPanel.java @@ -2,7 +2,7 @@ package se.su.dsv.scipro.finalseminar; import com.google.common.eventbus.EventBus; import jakarta.inject.Inject; -import java.time.Duration; +import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Date; @@ -360,19 +360,16 @@ public class SeminarOppositionPanel extends Panel { @Override protected void onSubmit() { - // TODO: return deadline - finalSeminarOppositionService.requestImprovements(getModelObject(), feedbackToOpponentModel.getObject()); + Instant deadline = finalSeminarOppositionService.requestImprovements( + getModelObject(), + feedbackToOpponentModel.getObject() + ); record ImprovementFeedback(String fullName, ZonedDateTime deadline) {} - ZonedDateTime deadline = getModelObject() - .getFinalSeminar() - .getStartDate() - .toInstant() - .plus(Duration.ofDays(10)) - .atZone(ZoneId.of("Europe/Stockholm")); + ZonedDateTime localDeadline = deadline.atZone(ZoneId.systemDefault()); success( getString("feedback.opponent.requested.improvements", () -> - new ImprovementFeedback(getModelObject().getUser().getFullName(), deadline) + new ImprovementFeedback(getModelObject().getUser().getFullName(), localDeadline) ) ); } -- 2.39.5 From ecdd68efbeea1616ae1aad57cb2ef053298be7ac Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Mon, 20 Jan 2025 14:24:24 +0100 Subject: [PATCH 13/23] Fix test --- .../se/su/dsv/scipro/finalseminar/SeminarPanelTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/view/src/test/java/se/su/dsv/scipro/finalseminar/SeminarPanelTest.java b/view/src/test/java/se/su/dsv/scipro/finalseminar/SeminarPanelTest.java index 2edb1a7727..da4f5a63ca 100644 --- a/view/src/test/java/se/su/dsv/scipro/finalseminar/SeminarPanelTest.java +++ b/view/src/test/java/se/su/dsv/scipro/finalseminar/SeminarPanelTest.java @@ -66,6 +66,14 @@ public class SeminarPanelTest extends SciProTest { Mockito.when(plagiarismControl.getStatus(any(FileDescription.class))).thenReturn( new PlagiarismControl.Status.NotSubmitted() ); + Mockito.lenient() + .when(finalSeminarOppositionService.getCriteriaForOpposition(opposition)) + .thenReturn( + new OppositionCriteria( + 1, + List.of(new OppositionCriteria.Point(0, ""), new OppositionCriteria.Point(1, "Filled in report")) + ) + ); } private void addCoSupervisorToProject() { -- 2.39.5 From da91d4074d1a9b763b8dac96bb076dc029949402 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Tue, 21 Jan 2025 11:18:00 +0100 Subject: [PATCH 14/23] Replace direct repository access with a service --- .../se/su/dsv/scipro/finalseminar/OppositionReportPage.java | 4 ++-- view/src/test/java/se/su/dsv/scipro/SciProTest.java | 3 --- .../dsv/scipro/finalseminar/OppositionReportPageTest.java | 6 +++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.java index fc35785ba1..743e8aaee5 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.java @@ -24,7 +24,7 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements public static final String FILL_OUT_REPORT = "fillOutReport"; @Inject - private FinalSeminarOppositionRepo finalSeminarOppositionRepo; + private FinalSeminarOppositionService finalSeminarOppositionService; @Inject private OppositionReportService oppositionReportService; @@ -35,7 +35,7 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements throw new RestartResponseException(ProjectDetailsPage.class, pp); } - final FinalSeminarOpposition opposition = finalSeminarOppositionRepo.findOne(pp.get("oid").toLong()); + final FinalSeminarOpposition opposition = finalSeminarOppositionService.findOne(pp.get("oid").toLong()); if (opposition == null) { throw new RestartResponseException(ProjectDetailsPage.class, pp); diff --git a/view/src/test/java/se/su/dsv/scipro/SciProTest.java b/view/src/test/java/se/su/dsv/scipro/SciProTest.java index 768b84e0df..12c77ff7cc 100755 --- a/view/src/test/java/se/su/dsv/scipro/SciProTest.java +++ b/view/src/test/java/se/su/dsv/scipro/SciProTest.java @@ -321,9 +321,6 @@ public abstract class SciProTest { @Mock protected FinalSeminarUploadController finalSeminarUploadController; - @Mock - protected FinalSeminarOppositionRepo finalSeminarOppositionRepo; - @Mock protected PlagiarismControl plagiarismControl; diff --git a/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java b/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java index 6e9d239fa4..05b2d9a1f5 100644 --- a/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java +++ b/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java @@ -104,14 +104,14 @@ public class OppositionReportPageTest extends SciProTest { public void disable_form_if_opposition_does_not_belong_to_logged_in_user() { mockReport(bachelor); long oppositionId = 4L; - Mockito.when(finalSeminarOppositionRepo.findOne(oppositionId)).thenReturn(finalSeminarOpposition); + Mockito.when(finalSeminarOppositionService.findOne(oppositionId)).thenReturn(finalSeminarOpposition); startPage(oppositionId); tester.assertDisabled(FILL_OUT_REPORT); } @Test public void redirect_if_no_opposition_is_found_from_id() { - Mockito.when(finalSeminarOppositionRepo.findOne(ArgumentMatchers.anyLong())).thenReturn(null); + Mockito.when(finalSeminarOppositionService.findOne(ArgumentMatchers.anyLong())).thenReturn(null); startPage(1L); tester.assertRenderedPage(ProjectDetailsPage.class); } @@ -156,7 +156,7 @@ public class OppositionReportPageTest extends SciProTest { private void startOppositionPage() { long oppositionId = 4L; setLoggedInAs(user); - Mockito.when(finalSeminarOppositionRepo.findOne(oppositionId)).thenReturn(finalSeminarOpposition); + Mockito.when(finalSeminarOppositionService.findOne(oppositionId)).thenReturn(finalSeminarOpposition); startPage(oppositionId); } -- 2.39.5 From fec631f2d07155059544a1714ccc406a05156665 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Thu, 23 Jan 2025 16:06:30 +0100 Subject: [PATCH 15/23] Replace @Entity with DTO --- .../java/se/su/dsv/scipro/CoreConfig.java | 7 +++++-- .../FinalSeminarOppositionService.java | 7 +++---- .../FinalSeminarOppositionServiceImpl.java | 16 +++++++++++++++- .../dsv/scipro/finalseminar/Opposition.java | 6 ++++++ .../finalseminar/OppositionReportPage.java | 19 ++++++------------- .../OppositionReportPageTest.java | 15 +++++++++------ 6 files changed, 44 insertions(+), 26 deletions(-) create mode 100644 core/src/main/java/se/su/dsv/scipro/finalseminar/Opposition.java diff --git a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java index d94c934073..080ff17d1a 100644 --- a/core/src/main/java/se/su/dsv/scipro/CoreConfig.java +++ b/core/src/main/java/se/su/dsv/scipro/CoreConfig.java @@ -154,6 +154,7 @@ import se.su.dsv.scipro.report.GradingReportServiceImpl; 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.OppositionReportService; import se.su.dsv.scipro.report.OppositionReportServiceImpl; import se.su.dsv.scipro.report.ReportServiceImpl; import se.su.dsv.scipro.report.SupervisorGradingReportRepository; @@ -438,7 +439,8 @@ public class CoreConfig { FinalSeminarOppositionRepo finalSeminarOppositionRepository, Clock clock, FinalSeminarSettingsService finalSeminarSettingsService, - DaysService daysService + DaysService daysService, + OppositionReportService oppositionReportService ) { return new FinalSeminarOppositionServiceImpl( em, @@ -447,7 +449,8 @@ public class CoreConfig { finalSeminarOppositionRepository, clock, finalSeminarSettingsService, - daysService + daysService, + oppositionReportService ); } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java index e3b82f0682..e7d7916583 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionService.java @@ -3,10 +3,7 @@ package se.su.dsv.scipro.finalseminar; import java.time.Instant; import se.su.dsv.scipro.system.GenericService; -public interface FinalSeminarOppositionService extends GenericService { - @Override - void delete(Long aLong); - +public interface FinalSeminarOppositionService { OppositionCriteria getCriteriaForOpposition(FinalSeminarOpposition opposition); FinalSeminarOpposition gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback) @@ -16,4 +13,6 @@ public interface FinalSeminarOppositionService extends GenericService opposition = LoadableDetachableModel.of(() -> + finalSeminarOppositionService.getOpposition(pp.get("oid").toLong()) + ); - if (opposition == null) { + if (opposition.getObject() == null) { throw new RestartResponseException(ProjectDetailsPage.class, pp); } - final IModel report = getOppositionReport(opposition); + final IModel report = opposition.map(Opposition::report); add( new ViewAttachmentPanel( @@ -71,18 +73,9 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements @Override protected void onConfigure() { super.onConfigure(); - setEnabled(opposition.getUser().equals(SciProSession.get().getUser())); + setEnabled(opposition.getObject().user().equals(SciProSession.get().getUser())); } } ); } - - private IModel getOppositionReport(final FinalSeminarOpposition opposition) { - return new LoadableDetachableModel<>() { - @Override - protected OppositionReport load() { - return oppositionReportService.findOrCreateReport(opposition); - } - }; - } } diff --git a/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java b/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java index 05b2d9a1f5..f3efe7c6ca 100644 --- a/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java +++ b/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java @@ -76,10 +76,9 @@ public class OppositionReportPageTest extends SciProTest { Mockito.when(finalSeminarService.findByProject(opponentsProject)).thenReturn(finalSeminar); } - private void mockReport(ProjectType bachelor) { + private OppositionReport mockReport(ProjectType bachelor) { GradingReportTemplate reportTemplate = createTemplate(bachelor); - OppositionReport oppositionReport = reportTemplate.createOppositionReport(finalSeminarOpposition); - Mockito.when(oppositionReportService.findOrCreateReport(finalSeminarOpposition)).thenReturn(oppositionReport); + return reportTemplate.createOppositionReport(finalSeminarOpposition); } @Test @@ -104,14 +103,16 @@ public class OppositionReportPageTest extends SciProTest { public void disable_form_if_opposition_does_not_belong_to_logged_in_user() { mockReport(bachelor); long oppositionId = 4L; - Mockito.when(finalSeminarOppositionService.findOne(oppositionId)).thenReturn(finalSeminarOpposition); + Mockito.when(finalSeminarOppositionService.getOpposition(oppositionId)).thenReturn( + new Opposition(user, mockReport(bachelor)) + ); startPage(oppositionId); tester.assertDisabled(FILL_OUT_REPORT); } @Test public void redirect_if_no_opposition_is_found_from_id() { - Mockito.when(finalSeminarOppositionService.findOne(ArgumentMatchers.anyLong())).thenReturn(null); + Mockito.when(finalSeminarOppositionService.getOpposition(ArgumentMatchers.anyLong())).thenReturn(null); startPage(1L); tester.assertRenderedPage(ProjectDetailsPage.class); } @@ -156,7 +157,9 @@ public class OppositionReportPageTest extends SciProTest { private void startOppositionPage() { long oppositionId = 4L; setLoggedInAs(user); - Mockito.when(finalSeminarOppositionService.findOne(oppositionId)).thenReturn(finalSeminarOpposition); + Mockito.when(finalSeminarOppositionService.getOpposition(oppositionId)).thenReturn( + new Opposition(user, mockReport(bachelor)) + ); startPage(oppositionId); } -- 2.39.5 From 54d5ef8752005de299b0faa44ea6f47750d29a2a Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Thu, 23 Jan 2025 21:17:36 +0100 Subject: [PATCH 16/23] Show requests for improvement when filling in the opposition report --- .../FinalSeminarOppositionServiceImpl.java | 19 ++++++++++- .../dsv/scipro/finalseminar/Opposition.java | 6 +++- .../finalseminar/OppositionReportPage.html | 11 ++++++ .../finalseminar/OppositionReportPage.java | 34 +++++++++++++++++++ .../OppositionReportPageTest.java | 4 +-- 5 files changed, 70 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java index a39d78eeda..ac52aba45e 100755 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/FinalSeminarOppositionServiceImpl.java @@ -8,6 +8,7 @@ import jakarta.transaction.Transactional; import java.time.Clock; import java.time.Instant; import java.util.List; +import java.util.Optional; import se.su.dsv.scipro.misc.DaysService; import se.su.dsv.scipro.report.OppositionReport; import se.su.dsv.scipro.report.OppositionReportService; @@ -107,6 +108,22 @@ public class FinalSeminarOppositionServiceImpl return null; } OppositionReport report = oppositionReportService.findOrCreateReport(finalSeminarOpposition); - return new Opposition(finalSeminarOpposition.getUser(), report); + Optional improvements = getImprovementsNeeded(finalSeminarOpposition); + return new Opposition(finalSeminarOpposition.getUser(), report, improvements); + } + + private Optional getImprovementsNeeded( + FinalSeminarOpposition finalSeminarOpposition + ) { + if (finalSeminarOpposition.getSupervisorCommentForImprovements() != null) { + return Optional.of( + new Opposition.ImprovementsNeeded( + finalSeminarOpposition.getSupervisorCommentForImprovements(), + finalSeminarOpposition.getImprovementsRequestedAt() + ) + ); + } else { + return Optional.empty(); + } } } diff --git a/core/src/main/java/se/su/dsv/scipro/finalseminar/Opposition.java b/core/src/main/java/se/su/dsv/scipro/finalseminar/Opposition.java index b661be2a32..5ff6a71e5b 100644 --- a/core/src/main/java/se/su/dsv/scipro/finalseminar/Opposition.java +++ b/core/src/main/java/se/su/dsv/scipro/finalseminar/Opposition.java @@ -1,6 +1,10 @@ package se.su.dsv.scipro.finalseminar; +import java.time.Instant; +import java.util.Optional; import se.su.dsv.scipro.report.OppositionReport; import se.su.dsv.scipro.system.User; -public record Opposition(User user, OppositionReport report) {} +public record Opposition(User user, OppositionReport report, Optional improvementsNeeded) { + record ImprovementsNeeded(String comment, Instant deadline) {} +} diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.html b/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.html index f4df0cf012..f74a0846d8 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.html +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.html @@ -20,6 +20,17 @@ Final seminar file:
+ +
+

+ The supervisor has requested improvements to your opposition report. + You have until + to make the requested changes. See below for the comments from the supervisor. +

+

+
+
+
Thesis summary diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.java b/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.java index 8c4ffcad8b..ca8f6f547d 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.java +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.java @@ -1,8 +1,11 @@ package se.su.dsv.scipro.finalseminar; import jakarta.inject.Inject; +import java.time.ZoneId; +import java.util.Optional; import org.apache.wicket.RestartResponseException; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.TextArea; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LambdaModel; @@ -52,6 +55,33 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements ) ); + IModel improvements = opposition + .map(Opposition::improvementsNeeded) + .map(OppositionReportPage::orNull); + add( + new Label("improvements_requested_comment", improvements.map(Opposition.ImprovementsNeeded::comment)) { + @Override + protected void onConfigure() { + super.onConfigure(); + setVisible(!getDefaultModelObjectAsString().isBlank()); + } + } + ); + add( + new Label( + "improvements_requested_deadline", + improvements + .map(Opposition.ImprovementsNeeded::deadline) + .map(deadline -> deadline.atZone(ZoneId.systemDefault())) + ) { + @Override + protected void onConfigure() { + super.onConfigure(); + setVisible(getDefaultModelObject() != null); + } + } + ); + add( new FillOutReportPanel<>(FILL_OUT_REPORT, report) { { @@ -78,4 +108,8 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements } ); } + + private static A orNull(Optional optional) { + return optional.orElse(null); + } } diff --git a/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java b/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java index f3efe7c6ca..6edbd445f0 100644 --- a/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java +++ b/view/src/test/java/se/su/dsv/scipro/finalseminar/OppositionReportPageTest.java @@ -104,7 +104,7 @@ public class OppositionReportPageTest extends SciProTest { mockReport(bachelor); long oppositionId = 4L; Mockito.when(finalSeminarOppositionService.getOpposition(oppositionId)).thenReturn( - new Opposition(user, mockReport(bachelor)) + new Opposition(user, mockReport(bachelor), Optional.empty()) ); startPage(oppositionId); tester.assertDisabled(FILL_OUT_REPORT); @@ -158,7 +158,7 @@ public class OppositionReportPageTest extends SciProTest { long oppositionId = 4L; setLoggedInAs(user); Mockito.when(finalSeminarOppositionService.getOpposition(oppositionId)).thenReturn( - new Opposition(user, mockReport(bachelor)) + new Opposition(user, mockReport(bachelor), Optional.empty()) ); startPage(oppositionId); } -- 2.39.5 From 9db30b3d2806270b9c6d1dce6aa7103ef59579cc Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Fri, 24 Jan 2025 15:16:02 +0100 Subject: [PATCH 17/23] Translated some swedish text into english. The entire system is in english so have two paragraphs in swedish made no sense. --- .../su/dsv/scipro/finalseminar/OppositionReportPage.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.html b/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.html index f74a0846d8..62050c9116 100644 --- a/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.html +++ b/view/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportPage.html @@ -9,9 +9,9 @@
- Använd bedömningskriterierna i denna rapport och skriv dina synpunkter som opponent i fritextfälten - under varje kriterium. Du gör dock ingen poängbedömning men - är fri att skriva så mycket som du önskar på varje bedömningskriterium. + Use the assessment criteria in this report and write your views as an opponent in the text + fields under each criterion. However, you do not make a point assessment but are free to + write as much as you wish on each criterion.
@@ -35,7 +35,7 @@ Thesis summary

- Ge en kort sammanfattning av det utvärderade arbetet. + Give a short summary of the evaluated work.