Allow supervisors to request improvements from final seminar opponents #78
@ -31,6 +31,7 @@ import se.su.dsv.scipro.finalseminar.AuthorRepository;
|
|||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationRepository;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationRepository;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationServiceImpl;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationServiceImpl;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarCreationSubscribers;
|
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.FinalSeminarOppositionRepo;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionServiceImpl;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionServiceImpl;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarRepository;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarRepository;
|
||||||
@ -153,8 +154,8 @@ import se.su.dsv.scipro.report.GradingReportServiceImpl;
|
|||||||
import se.su.dsv.scipro.report.GradingReportTemplateRepo;
|
import se.su.dsv.scipro.report.GradingReportTemplateRepo;
|
||||||
import se.su.dsv.scipro.report.GradingReportTemplateRepoImpl;
|
import se.su.dsv.scipro.report.GradingReportTemplateRepoImpl;
|
||||||
import se.su.dsv.scipro.report.OppositionReportRepo;
|
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.OppositionReportServiceImpl;
|
||||||
import se.su.dsv.scipro.report.ReportServiceImpl;
|
|
||||||
import se.su.dsv.scipro.report.SupervisorGradingReportRepository;
|
import se.su.dsv.scipro.report.SupervisorGradingReportRepository;
|
||||||
import se.su.dsv.scipro.reviewing.DecisionRepository;
|
import se.su.dsv.scipro.reviewing.DecisionRepository;
|
||||||
import se.su.dsv.scipro.reviewing.FinalSeminarApprovalService;
|
import se.su.dsv.scipro.reviewing.FinalSeminarApprovalService;
|
||||||
@ -430,8 +431,26 @@ public class CoreConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public FinalSeminarOppositionServiceImpl finalSeminarOppositionService(Provider<EntityManager> em) {
|
public FinalSeminarOppositionServiceImpl finalSeminarOppositionService(
|
||||||
return new FinalSeminarOppositionServiceImpl(em);
|
Provider<EntityManager> em,
|
||||||
|
FinalSeminarOppositionGrading finalSeminarOppositionGrading,
|
||||||
|
EventBus eventBus,
|
||||||
|
FinalSeminarOppositionRepo finalSeminarOppositionRepository,
|
||||||
|
Clock clock,
|
||||||
|
FinalSeminarSettingsService finalSeminarSettingsService,
|
||||||
|
DaysService daysService,
|
||||||
|
OppositionReportService oppositionReportService
|
||||||
|
) {
|
||||||
|
return new FinalSeminarOppositionServiceImpl(
|
||||||
|
em,
|
||||||
|
finalSeminarOppositionGrading,
|
||||||
|
eventBus,
|
||||||
|
finalSeminarOppositionRepository,
|
||||||
|
clock,
|
||||||
|
finalSeminarSettingsService,
|
||||||
|
daysService,
|
||||||
|
oppositionReportService
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -669,13 +688,15 @@ public class CoreConfig {
|
|||||||
OppositionReportRepo oppositionReportRepository,
|
OppositionReportRepo oppositionReportRepository,
|
||||||
GradingReportTemplateRepo gradingReportTemplateRepository,
|
GradingReportTemplateRepo gradingReportTemplateRepository,
|
||||||
FileService fileService,
|
FileService fileService,
|
||||||
FinalSeminarOppositionRepo finalSeminarOppositionRepository
|
FinalSeminarOppositionRepo finalSeminarOppositionRepository,
|
||||||
|
EventBus eventBus
|
||||||
) {
|
) {
|
||||||
return new OppositionReportServiceImpl(
|
return new OppositionReportServiceImpl(
|
||||||
oppositionReportRepository,
|
oppositionReportRepository,
|
||||||
gradingReportTemplateRepository,
|
gradingReportTemplateRepository,
|
||||||
fileService,
|
fileService,
|
||||||
finalSeminarOppositionRepository
|
finalSeminarOppositionRepository,
|
||||||
|
eventBus
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -855,11 +876,6 @@ public class CoreConfig {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
public ReportServiceImpl reportService(Provider<EntityManager> em, FileService fileService) {
|
|
||||||
return new ReportServiceImpl(em, fileService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ResearchAreaServiceImpl researchAreaService(Provider<EntityManager> em) {
|
public ResearchAreaServiceImpl researchAreaService(Provider<EntityManager> em) {
|
||||||
return new ResearchAreaServiceImpl(em);
|
return new ResearchAreaServiceImpl(em);
|
||||||
|
@ -4,16 +4,29 @@ import jakarta.inject.Inject;
|
|||||||
import jakarta.inject.Provider;
|
import jakarta.inject.Provider;
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.time.Month;
|
import java.time.Month;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
import se.su.dsv.scipro.checklist.ChecklistCategory;
|
import se.su.dsv.scipro.checklist.ChecklistCategory;
|
||||||
|
import se.su.dsv.scipro.data.dataobjects.Member;
|
||||||
|
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;
|
import se.su.dsv.scipro.match.ApplicationPeriod;
|
||||||
import se.su.dsv.scipro.match.Keyword;
|
import se.su.dsv.scipro.match.Keyword;
|
||||||
import se.su.dsv.scipro.milestones.dataobjects.MilestoneActivityTemplate;
|
import se.su.dsv.scipro.milestones.dataobjects.MilestoneActivityTemplate;
|
||||||
import se.su.dsv.scipro.milestones.dataobjects.MilestonePhaseTemplate;
|
import se.su.dsv.scipro.milestones.dataobjects.MilestonePhaseTemplate;
|
||||||
import se.su.dsv.scipro.milestones.service.MilestoneActivityTemplateService;
|
import se.su.dsv.scipro.milestones.service.MilestoneActivityTemplateService;
|
||||||
|
import se.su.dsv.scipro.notifications.dataobject.Notification;
|
||||||
|
import se.su.dsv.scipro.notifications.dataobject.SeminarEvent;
|
||||||
|
import se.su.dsv.scipro.notifications.settings.service.ReceiverConfigurationService;
|
||||||
import se.su.dsv.scipro.profiles.CurrentProfile;
|
import se.su.dsv.scipro.profiles.CurrentProfile;
|
||||||
import se.su.dsv.scipro.profiles.Profiles;
|
import se.su.dsv.scipro.profiles.Profiles;
|
||||||
import se.su.dsv.scipro.project.Project;
|
import se.su.dsv.scipro.project.Project;
|
||||||
@ -39,12 +52,18 @@ public class DataInitializer implements Lifecycle {
|
|||||||
@Inject
|
@Inject
|
||||||
private MilestoneActivityTemplateService milestoneActivityTemplateService;
|
private MilestoneActivityTemplateService milestoneActivityTemplateService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private FileService fileService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private CurrentProfile profile;
|
private CurrentProfile profile;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private Provider<EntityManager> em;
|
private Provider<EntityManager> em;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ReceiverConfigurationService receiverConfigurationService;
|
||||||
|
|
||||||
private static final String MAIL = "@example.com";
|
private static final String MAIL = "@example.com";
|
||||||
|
|
||||||
private static final String ADMIN = "admin";
|
private static final String ADMIN = "admin";
|
||||||
@ -75,6 +94,8 @@ public class DataInitializer implements Lifecycle {
|
|||||||
private ResearchArea researchArea2;
|
private ResearchArea researchArea2;
|
||||||
private ProjectType masterClass;
|
private ProjectType masterClass;
|
||||||
private ProjectType magisterClass;
|
private ProjectType magisterClass;
|
||||||
|
private Project project1;
|
||||||
|
private Project project2;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@ -89,12 +110,45 @@ public class DataInitializer implements Lifecycle {
|
|||||||
createMilestonesIfNotDone();
|
createMilestonesIfNotDone();
|
||||||
createUsers();
|
createUsers();
|
||||||
createProjects();
|
createProjects();
|
||||||
|
createPastFinalSeminar();
|
||||||
|
setUpNotifications();
|
||||||
}
|
}
|
||||||
if (profile.getCurrentProfile() == Profiles.DEV && noAdminUser()) {
|
if (profile.getCurrentProfile() == Profiles.DEV && noAdminUser()) {
|
||||||
createAdmin();
|
createAdmin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setUpNotifications() {
|
||||||
|
receiverConfigurationService.setReceiving(
|
||||||
|
Notification.Type.FINAL_SEMINAR,
|
||||||
|
SeminarEvent.Event.OPPOSITION_REPORT_SUBMITTED,
|
||||||
|
Member.Type.SUPERVISOR,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
opponent.setFinalSeminar(finalSeminar);
|
||||||
|
opponent.setUser(sid_student);
|
||||||
|
finalSeminar.addOpposition(opponent);
|
||||||
|
|
||||||
|
save(finalSeminar);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {}
|
public void stop() {}
|
||||||
|
|
||||||
@ -145,11 +199,11 @@ public class DataInitializer implements Lifecycle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createProjects() {
|
private void createProjects() {
|
||||||
createProject(PROJECT_1, eric_employee, sture_student, stina_student, eve_employee);
|
project1 = createProject(PROJECT_1, eric_employee, sture_student, stina_student, eve_employee);
|
||||||
createProject(PROJECT_2, eve_employee, sid_student, simon_student, eric_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()
|
Project project = Project.builder()
|
||||||
.title(title)
|
.title(title)
|
||||||
.projectType(bachelorClass)
|
.projectType(bachelorClass)
|
||||||
@ -159,7 +213,7 @@ public class DataInitializer implements Lifecycle {
|
|||||||
project.addProjectParticipant(student2);
|
project.addProjectParticipant(student2);
|
||||||
project.addProjectParticipant(student1);
|
project.addProjectParticipant(student1);
|
||||||
project.addReviewer(reviewer);
|
project.addReviewer(reviewer);
|
||||||
save(project);
|
return save(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createUsers() {
|
private void createUsers() {
|
||||||
@ -1907,4 +1961,42 @@ public class DataInitializer implements Lifecycle {
|
|||||||
em.get().persist(entity);
|
em.get().persist(entity);
|
||||||
return 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> T handleData(Function<InputStream, T> handler) {
|
||||||
|
return handler.apply(new ByteArrayInputStream(content.getBytes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package se.su.dsv.scipro.finalseminar;
|
package se.su.dsv.scipro.finalseminar;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import se.su.dsv.scipro.project.Project;
|
import se.su.dsv.scipro.project.Project;
|
||||||
import se.su.dsv.scipro.system.User;
|
import se.su.dsv.scipro.system.User;
|
||||||
|
|
||||||
@ -26,4 +27,17 @@ class AbstractOppositionEvent {
|
|||||||
public FinalSeminarOpposition getOpposition() {
|
public FinalSeminarOpposition getOpposition() {
|
||||||
return opposition;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package se.su.dsv.scipro.finalseminar;
|
||||||
|
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.inject.Provider;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import se.su.dsv.scipro.workerthreads.AbstractWorker;
|
||||||
|
import se.su.dsv.scipro.workerthreads.Scheduler;
|
||||||
|
|
||||||
|
public class ExpireUnfulfilledOppositionImprovementsWorker extends AbstractWorker {
|
||||||
|
|
||||||
|
private final FinalSeminarOppositionServiceImpl oppositionService;
|
||||||
|
|
||||||
|
public ExpireUnfulfilledOppositionImprovementsWorker(FinalSeminarOppositionServiceImpl oppositionService) {
|
||||||
|
this.oppositionService = oppositionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doWork() {
|
||||||
|
oppositionService.expireUnfulfilledOppositionImprovements();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Schedule {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public Schedule(Scheduler scheduler, Provider<ExpireUnfulfilledOppositionImprovementsWorker> worker) {
|
||||||
|
scheduler
|
||||||
|
.schedule("Fail opponents that have not submitted improvements")
|
||||||
|
.runBy(worker)
|
||||||
|
.every(1, TimeUnit.HOURS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import jakarta.persistence.JoinColumn;
|
|||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.OneToOne;
|
import jakarta.persistence.OneToOne;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import se.su.dsv.scipro.file.FileReference;
|
import se.su.dsv.scipro.file.FileReference;
|
||||||
import se.su.dsv.scipro.project.Project;
|
import se.su.dsv.scipro.project.Project;
|
||||||
@ -31,6 +32,14 @@ public class FinalSeminarOpposition extends FinalSeminarParticipation {
|
|||||||
@Column(name = "feedback", length = FEEDBACK_LENGTH)
|
@Column(name = "feedback", length = FEEDBACK_LENGTH)
|
||||||
private String feedback;
|
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
|
// JPA-mappings of foreign keys in this table (final_seminar_opposition) referencing
|
||||||
// other tables.
|
// other tables.
|
||||||
@ -92,6 +101,22 @@ public class FinalSeminarOpposition extends FinalSeminarParticipation {
|
|||||||
this.oppositionReport = oppositionReport;
|
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
|
// Methods Common To All Objects
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package se.su.dsv.scipro.finalseminar;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface FinalSeminarOppositionGrading {
|
||||||
|
OppositionCriteria oppositionCriteria(FinalSeminarOpposition opposition);
|
||||||
|
}
|
@ -11,4 +11,6 @@ import se.su.dsv.scipro.system.User;
|
|||||||
public interface FinalSeminarOppositionRepo
|
public interface FinalSeminarOppositionRepo
|
||||||
extends JpaRepository<FinalSeminarOpposition, Long>, QueryDslPredicateExecutor<FinalSeminarOpposition> {
|
extends JpaRepository<FinalSeminarOpposition, Long>, QueryDslPredicateExecutor<FinalSeminarOpposition> {
|
||||||
List<FinalSeminarOpposition> findByOpposingUserAndType(User user, ProjectType projectType);
|
List<FinalSeminarOpposition> findByOpposingUserAndType(User user, ProjectType projectType);
|
||||||
|
|
||||||
|
Collection<FinalSeminarOpposition> findUnfulfilledOppositionImprovements();
|
||||||
}
|
}
|
||||||
|
@ -24,4 +24,13 @@ public class FinalSeminarOppositionRepoImpl
|
|||||||
.where(QFinalSeminarOpposition.finalSeminarOpposition.project.projectType.eq(projectType))
|
.where(QFinalSeminarOpposition.finalSeminarOpposition.project.projectType.eq(projectType))
|
||||||
.fetch();
|
.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<FinalSeminarOpposition> findUnfulfilledOppositionImprovements() {
|
||||||
|
return createQuery()
|
||||||
|
.innerJoin(QFinalSeminarOpposition.finalSeminarOpposition.oppositionReport)
|
||||||
|
.where(QFinalSeminarOpposition.finalSeminarOpposition.improvementsRequestedAt.isNotNull())
|
||||||
|
.where(QFinalSeminarOpposition.finalSeminarOpposition.oppositionReport.submitted.isFalse())
|
||||||
|
.fetch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
package se.su.dsv.scipro.finalseminar;
|
package se.su.dsv.scipro.finalseminar;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import se.su.dsv.scipro.system.GenericService;
|
import se.su.dsv.scipro.system.GenericService;
|
||||||
|
|
||||||
public interface FinalSeminarOppositionService extends GenericService<FinalSeminarOpposition, Long> {
|
public interface FinalSeminarOppositionService {
|
||||||
@Override
|
OppositionCriteria getCriteriaForOpposition(FinalSeminarOpposition opposition);
|
||||||
void delete(Long aLong);
|
|
||||||
|
FinalSeminarOpposition gradeOpponent(FinalSeminarOpposition opposition, int points, String feedback)
|
||||||
|
throws PointNotValidException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the deadline by which the improvements must have been submitted
|
||||||
|
*/
|
||||||
|
Instant requestImprovements(FinalSeminarOpposition opposition, String supervisorComment);
|
||||||
|
|
||||||
|
Opposition getOpposition(long id);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,177 @@
|
|||||||
package se.su.dsv.scipro.finalseminar;
|
package se.su.dsv.scipro.finalseminar;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.EventBus;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Provider;
|
import jakarta.inject.Provider;
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
|
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;
|
||||||
import se.su.dsv.scipro.system.AbstractServiceImpl;
|
import se.su.dsv.scipro.system.AbstractServiceImpl;
|
||||||
|
|
||||||
public class FinalSeminarOppositionServiceImpl
|
public class FinalSeminarOppositionServiceImpl
|
||||||
extends AbstractServiceImpl<FinalSeminarOpposition, Long>
|
extends AbstractServiceImpl<FinalSeminarOpposition, Long>
|
||||||
implements FinalSeminarOppositionService {
|
implements FinalSeminarOppositionService {
|
||||||
|
|
||||||
|
private final FinalSeminarOppositionGrading finalSeminarOppositionGrading;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final FinalSeminarOppositionRepo finalSeminarOppositionRepository;
|
||||||
|
private final Clock clock;
|
||||||
|
private final FinalSeminarSettingsService finalSeminarSettingsService;
|
||||||
|
private final DaysService daysService;
|
||||||
|
private final OppositionReportService oppositionReportService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FinalSeminarOppositionServiceImpl(Provider<EntityManager> em) {
|
public FinalSeminarOppositionServiceImpl(
|
||||||
|
Provider<EntityManager> em,
|
||||||
|
FinalSeminarOppositionGrading finalSeminarOppositionGrading,
|
||||||
|
EventBus eventBus,
|
||||||
|
FinalSeminarOppositionRepo finalSeminarOppositionRepository,
|
||||||
|
Clock clock,
|
||||||
|
FinalSeminarSettingsService finalSeminarSettingsService,
|
||||||
|
DaysService daysService,
|
||||||
|
OppositionReportService oppositionReportService
|
||||||
|
) {
|
||||||
super(em, FinalSeminarOpposition.class, QFinalSeminarOpposition.finalSeminarOpposition);
|
super(em, FinalSeminarOpposition.class, QFinalSeminarOpposition.finalSeminarOpposition);
|
||||||
|
this.finalSeminarOppositionGrading = finalSeminarOppositionGrading;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.finalSeminarOppositionRepository = finalSeminarOppositionRepository;
|
||||||
|
this.clock = clock;
|
||||||
|
this.finalSeminarSettingsService = finalSeminarSettingsService;
|
||||||
|
this.daysService = daysService;
|
||||||
|
this.oppositionReportService = oppositionReportService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OppositionCriteria getCriteriaForOpposition(FinalSeminarOpposition opposition) {
|
||||||
|
return finalSeminarOppositionGrading.oppositionCriteria(opposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@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));
|
||||||
|
}
|
||||||
|
|
||||||
|
FinalSeminarGrade notApproved = criteriaForOpposition.pointsToPass() > points
|
||||||
|
? FinalSeminarGrade.NOT_APPROVED
|
||||||
|
: FinalSeminarGrade.APPROVED;
|
||||||
|
return internalGradeOpponent(opposition, points, feedback, notApproved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FinalSeminarOpposition internalGradeOpponent(
|
||||||
|
FinalSeminarOpposition opposition,
|
||||||
|
int points,
|
||||||
|
String feedback,
|
||||||
|
FinalSeminarGrade grade
|
||||||
|
) {
|
||||||
|
opposition.setGrade(grade);
|
||||||
|
opposition.setPoints(points);
|
||||||
|
opposition.setFeedback(feedback);
|
||||||
|
FinalSeminarOpposition assessedOpposition = finalSeminarOppositionRepository.save(opposition);
|
||||||
|
|
||||||
|
if (grade == FinalSeminarGrade.NOT_APPROVED) {
|
||||||
|
eventBus.post(new OppositionFailedEvent(assessedOpposition));
|
||||||
|
} else {
|
||||||
|
eventBus.post(new OppositionApprovedEvent(assessedOpposition));
|
||||||
|
}
|
||||||
|
|
||||||
|
return assessedOpposition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Instant requestImprovements(FinalSeminarOpposition opposition, String supervisorComment) {
|
||||||
|
OppositionReport oppositionReport = opposition.getOppositionReport();
|
||||||
|
if (oppositionReport == null) {
|
||||||
|
throw new IllegalStateException("There is no opposition report submitted");
|
||||||
|
}
|
||||||
|
|
||||||
|
FinalSeminarSettings finalSeminarSettings = finalSeminarSettingsService.getInstance();
|
||||||
|
|
||||||
|
Instant now = clock.instant();
|
||||||
|
Instant deadline = daysService.workDaysAfter(
|
||||||
|
now,
|
||||||
|
finalSeminarSettings.getWorkDaysToFixRequestedImprovementsToOppositionReport()
|
||||||
|
);
|
||||||
|
|
||||||
|
oppositionReport.setSubmitted(false);
|
||||||
|
opposition.setImprovementsRequestedAt(now);
|
||||||
|
opposition.setSupervisorCommentForImprovements(supervisorComment);
|
||||||
|
|
||||||
|
eventBus.post(new OppositionReportImprovementsRequestedEvent(opposition, supervisorComment, deadline));
|
||||||
|
|
||||||
|
return deadline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Opposition getOpposition(long id) {
|
||||||
|
FinalSeminarOpposition finalSeminarOpposition = finalSeminarOppositionRepository.findOne(id);
|
||||||
|
if (finalSeminarOpposition == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
OppositionReport report = oppositionReportService.findOrCreateReport(finalSeminarOpposition);
|
||||||
|
Optional<Opposition.ImprovementsNeeded> improvements = getImprovementsNeeded(finalSeminarOpposition);
|
||||||
|
return new Opposition(finalSeminarOpposition.getUser(), report, improvements);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Opposition.ImprovementsNeeded> getImprovementsNeeded(
|
||||||
|
FinalSeminarOpposition finalSeminarOpposition
|
||||||
|
) {
|
||||||
|
if (finalSeminarOpposition.getSupervisorCommentForImprovements() != null) {
|
||||||
|
return Optional.of(
|
||||||
|
new Opposition.ImprovementsNeeded(
|
||||||
|
finalSeminarOpposition.getSupervisorCommentForImprovements(),
|
||||||
|
finalSeminarOpposition.getImprovementsRequestedAt()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void expireUnfulfilledOppositionImprovements() {
|
||||||
|
Collection<FinalSeminarOpposition> unfulfilledOppositions =
|
||||||
|
finalSeminarOppositionRepository.findUnfulfilledOppositionImprovements();
|
||||||
|
|
||||||
|
Instant now = clock.instant();
|
||||||
|
int workDaysToFixRequestedImprovementsToOppositionReport = finalSeminarSettingsService
|
||||||
|
.getInstance()
|
||||||
|
.getWorkDaysToFixRequestedImprovementsToOppositionReport();
|
||||||
|
for (FinalSeminarOpposition unfulfilledOpposition : unfulfilledOppositions) {
|
||||||
|
Instant deadline = daysService.workDaysAfter(
|
||||||
|
unfulfilledOpposition.getImprovementsRequestedAt(),
|
||||||
|
workDaysToFixRequestedImprovementsToOppositionReport
|
||||||
|
);
|
||||||
|
if (now.isAfter(deadline)) {
|
||||||
|
internalGradeOpponent(
|
||||||
|
unfulfilledOpposition,
|
||||||
|
0,
|
||||||
|
unfulfilledOpposition.getSupervisorCommentForImprovements(),
|
||||||
|
FinalSeminarGrade.NOT_APPROVED
|
||||||
|
);
|
||||||
|
|
||||||
|
OppositionReport oppositionReport = unfulfilledOpposition.getOppositionReport();
|
||||||
|
if (oppositionReport != null) {
|
||||||
|
// Lock the report so it's not possible to submit it again
|
||||||
|
oppositionReport.setSubmitted(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
finalSeminarOppositionRepository.save(unfulfilledOpposition);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,10 @@ package se.su.dsv.scipro.finalseminar;
|
|||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
import com.querydsl.core.BooleanBuilder;
|
import com.querydsl.core.BooleanBuilder;
|
||||||
|
import com.querydsl.core.types.SubQueryExpressionImpl;
|
||||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||||
|
import com.querydsl.core.types.dsl.Expressions;
|
||||||
|
import com.querydsl.jpa.JPAExpressions;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Provider;
|
import jakarta.inject.Provider;
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
@ -542,19 +545,22 @@ public class FinalSeminarServiceImpl extends AbstractServiceImpl<FinalSeminar, L
|
|||||||
|
|
||||||
private BooleanExpression unfinishedSeminars(Date after, Date before) {
|
private BooleanExpression unfinishedSeminars(Date after, Date before) {
|
||||||
QFinalSeminar seminar = QFinalSeminar.finalSeminar;
|
QFinalSeminar seminar = QFinalSeminar.finalSeminar;
|
||||||
if (after == null && before == null) {
|
QFinalSeminarOpposition opposition = QFinalSeminarOpposition.finalSeminarOpposition;
|
||||||
return seminar.oppositions
|
BooleanExpression ungradedParticipant = Expressions.anyOf(
|
||||||
|
seminar.oppositions
|
||||||
.any()
|
.any()
|
||||||
.grade.isNull()
|
.id.in(
|
||||||
.or(seminar.activeParticipations.any().grade.isNull().or(seminar.respondents.any().grade.isNull()));
|
JPAExpressions.select(opposition.id)
|
||||||
|
.from(opposition)
|
||||||
|
.where(opposition.grade.isNull().and(opposition.improvementsRequestedAt.isNull()))
|
||||||
|
),
|
||||||
|
seminar.activeParticipations.any().grade.isNull(),
|
||||||
|
seminar.respondents.any().grade.isNull()
|
||||||
|
);
|
||||||
|
if (after == null && before == null) {
|
||||||
|
return ungradedParticipant;
|
||||||
} else {
|
} else {
|
||||||
return seminar.startDate
|
return seminar.startDate.between(after, before).and(ungradedParticipant);
|
||||||
.between(after, before)
|
|
||||||
.andAnyOf(
|
|
||||||
seminar.oppositions.any().grade.isNull(),
|
|
||||||
seminar.activeParticipations.any().grade.isNull(),
|
|
||||||
seminar.respondents.any().grade.isNull()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,9 @@ public class FinalSeminarSettings extends DomainObject {
|
|||||||
@Column(name = "days_ahead_to_upload_thesis", nullable = false)
|
@Column(name = "days_ahead_to_upload_thesis", nullable = false)
|
||||||
private int daysAheadToUploadThesis = DEFAULT_DAYS_AHEAD_TO_UPLOAD_THESIS;
|
private int daysAheadToUploadThesis = DEFAULT_DAYS_AHEAD_TO_UPLOAD_THESIS;
|
||||||
|
|
||||||
|
@Column(name = "work_days_to_fix_requested_improvements_to_opposition_report", nullable = false)
|
||||||
|
private int workDaysToFixRequestedImprovementsToOppositionReport = 10;
|
||||||
|
|
||||||
@Column(name = "thesis_must_be_pdf", nullable = false)
|
@Column(name = "thesis_must_be_pdf", nullable = false)
|
||||||
private boolean thesisMustBePDF = false;
|
private boolean thesisMustBePDF = false;
|
||||||
|
|
||||||
@ -113,6 +116,17 @@ public class FinalSeminarSettings extends DomainObject {
|
|||||||
this.oppositionPriorityDays = oppositionPriorityDays;
|
this.oppositionPriorityDays = oppositionPriorityDays;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getWorkDaysToFixRequestedImprovementsToOppositionReport() {
|
||||||
|
return workDaysToFixRequestedImprovementsToOppositionReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWorkDaysToFixRequestedImprovementsToOppositionReport(
|
||||||
|
int workDaysToFixRequestedImprovementsToOppositionReport
|
||||||
|
) {
|
||||||
|
this.workDaysToFixRequestedImprovementsToOppositionReport =
|
||||||
|
workDaysToFixRequestedImprovementsToOppositionReport;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return (
|
return (
|
||||||
|
@ -0,0 +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, Optional<ImprovementsNeeded> improvementsNeeded) {
|
||||||
|
record ImprovementsNeeded(String comment, Instant deadline) {}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package se.su.dsv.scipro.finalseminar;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record OppositionCriteria(int pointsToPass, List<Point> pointsAvailable) {
|
||||||
|
public record Point(int value, String requirement) {}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package se.su.dsv.scipro.finalseminar;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public record OppositionReportImprovementsRequestedEvent(
|
||||||
|
FinalSeminarOpposition opposition,
|
||||||
|
String supervisorComment,
|
||||||
|
Instant deadline
|
||||||
|
) {}
|
@ -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<Integer> acceptableValues;
|
||||||
|
|
||||||
|
public PointNotValidException(int givenValue, List<Integer> acceptableValues) {
|
||||||
|
this.givenValue = givenValue;
|
||||||
|
this.acceptableValues = acceptableValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int givenValue() {
|
||||||
|
return givenValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> acceptableValues() {
|
||||||
|
return acceptableValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PointNotValidException{" + "givenValue=" + givenValue + ", acceptableValues=" + acceptableValues + '}';
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
package se.su.dsv.scipro.misc;
|
package se.su.dsv.scipro.misc;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public interface DaysService {
|
public interface DaysService {
|
||||||
@ -9,4 +12,11 @@ public interface DaysService {
|
|||||||
int workDaysBetween(Date startDate, Date endDate);
|
int workDaysBetween(Date startDate, Date endDate);
|
||||||
LocalDate workDaysAhead(LocalDate date, int days);
|
LocalDate workDaysAhead(LocalDate date, int days);
|
||||||
LocalDate workDaysAfter(LocalDate date, int days);
|
LocalDate workDaysAfter(LocalDate date, int days);
|
||||||
|
|
||||||
|
default Instant workDaysAfter(Instant instant, int days) {
|
||||||
|
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
|
||||||
|
LocalDate localDate = zonedDateTime.toLocalDate();
|
||||||
|
LocalDate newDate = workDaysAfter(localDate, days);
|
||||||
|
return newDate.atTime(zonedDateTime.toLocalTime()).atZone(ZoneId.systemDefault()).toInstant();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import se.su.dsv.scipro.finalseminar.FinalSeminarDeletedEvent;
|
|||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarThesisDeletedEvent;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarThesisDeletedEvent;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarThesisUploadedEvent;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarThesisUploadedEvent;
|
||||||
import se.su.dsv.scipro.finalseminar.OppositionFailedEvent;
|
import se.su.dsv.scipro.finalseminar.OppositionFailedEvent;
|
||||||
|
import se.su.dsv.scipro.finalseminar.OppositionReportImprovementsRequestedEvent;
|
||||||
import se.su.dsv.scipro.finalseminar.ParticipationFailedEvent;
|
import se.su.dsv.scipro.finalseminar.ParticipationFailedEvent;
|
||||||
import se.su.dsv.scipro.notifications.dataobject.NotificationSource;
|
import se.su.dsv.scipro.notifications.dataobject.NotificationSource;
|
||||||
import se.su.dsv.scipro.notifications.dataobject.PeerEvent;
|
import se.su.dsv.scipro.notifications.dataobject.PeerEvent;
|
||||||
@ -23,6 +24,7 @@ import se.su.dsv.scipro.project.ProjectActivatedEvent;
|
|||||||
import se.su.dsv.scipro.project.ProjectCompletedEvent;
|
import se.su.dsv.scipro.project.ProjectCompletedEvent;
|
||||||
import se.su.dsv.scipro.project.ProjectDeactivatedEvent;
|
import se.su.dsv.scipro.project.ProjectDeactivatedEvent;
|
||||||
import se.su.dsv.scipro.project.ReviewerAssignedEvent;
|
import se.su.dsv.scipro.project.ReviewerAssignedEvent;
|
||||||
|
import se.su.dsv.scipro.report.OppositionReportSubmittedEvent;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class Notifications {
|
public class Notifications {
|
||||||
@ -168,6 +170,31 @@ public class Notifications {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void oppositionReportImprovementsRequested(OppositionReportImprovementsRequestedEvent event) {
|
||||||
|
Member recipient = new Member(event.opposition().getUser(), Member.Type.OPPONENT);
|
||||||
|
Set<Member> recipients = Set.of(recipient);
|
||||||
|
NotificationSource source = new NotificationSource();
|
||||||
|
source.setMessage(event.supervisorComment());
|
||||||
|
notificationController.notifyCustomSeminar(
|
||||||
|
event.opposition().getFinalSeminar(),
|
||||||
|
SeminarEvent.Event.OPPOSITION_REPORT_IMPROVEMENTS_REQUESTED,
|
||||||
|
source,
|
||||||
|
recipients
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void oppositionReportSubmitted(OppositionReportSubmittedEvent event) {
|
||||||
|
NotificationSource source = new NotificationSource();
|
||||||
|
source.setMessage(event.report().getAuthorName());
|
||||||
|
notificationController.notifySeminar(
|
||||||
|
event.finalSeminar(),
|
||||||
|
SeminarEvent.Event.OPPOSITION_REPORT_SUBMITTED,
|
||||||
|
source
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void reviewersChanged(ReviewerAssignedEvent event) {
|
public void reviewersChanged(ReviewerAssignedEvent event) {
|
||||||
notificationController.notifyProject(
|
notificationController.notifyProject(
|
||||||
|
@ -26,6 +26,8 @@ public class SeminarEvent extends NotificationEvent {
|
|||||||
THESIS_DELETED,
|
THESIS_DELETED,
|
||||||
THESIS_UPLOAD_REMIND,
|
THESIS_UPLOAD_REMIND,
|
||||||
CANCELLED,
|
CANCELLED,
|
||||||
|
OPPOSITION_REPORT_SUBMITTED,
|
||||||
|
OPPOSITION_REPORT_IMPROVEMENTS_REQUESTED,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Basic
|
@Basic
|
||||||
|
@ -140,6 +140,12 @@ FINAL_SEMINAR.THESIS_UPLOAD_REMIND.body = No final seminar thesis has been uploa
|
|||||||
If no final thesis has been uploaded by {0}, the final seminar will be automatically cancelled.
|
If no final thesis has been uploaded by {0}, the final seminar will be automatically cancelled.
|
||||||
FINAL_SEMINAR.CANCELLED.title = Final seminar for project {1} was cancelled
|
FINAL_SEMINAR.CANCELLED.title = Final seminar for project {1} was cancelled
|
||||||
FINAL_SEMINAR.CANCELLED.body = The final seminar for project {0} was cancelled, supervisor must select a new date for the final seminar.
|
FINAL_SEMINAR.CANCELLED.body = The final seminar for project {0} was cancelled, supervisor must select a new date for the final seminar.
|
||||||
|
FINAL_SEMINAR.OPPOSITION_REPORT_SUBMITTED.title=Opposition report submitted by {1} for the seminar on project {0}
|
||||||
|
FINAL_SEMINAR.OPPOSITION_REPORT_SUBMITTED.body=The opposition report from {0} has been submitted.
|
||||||
|
FINAL_SEMINAR.OPPOSITION_REPORT_IMPROVEMENTS_REQUESTED.title = Opposition report improvements requested
|
||||||
|
FINAL_SEMINAR.OPPOSITION_REPORT_IMPROVEMENTS_REQUESTED.body = The supervisor has deemed that the opposition report submitted \
|
||||||
|
does not meet the minimum requirements and has requested improvements. Please log into SciPro and submit a new \
|
||||||
|
opposition report. Their comments can be seen below:\n\n{0}
|
||||||
FINAL_SEMINAR.compilationSuffix = , project: {0}
|
FINAL_SEMINAR.compilationSuffix = , project: {0}
|
||||||
|
|
||||||
PEER.REVIEW_COMPLETED.title = Peer review completed
|
PEER.REVIEW_COMPLETED.title = Peer review completed
|
||||||
|
@ -2,7 +2,6 @@ package se.su.dsv.scipro.report;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
|
||||||
import se.su.dsv.scipro.grading.GradingBasis;
|
import se.su.dsv.scipro.grading.GradingBasis;
|
||||||
import se.su.dsv.scipro.project.Project;
|
import se.su.dsv.scipro.project.Project;
|
||||||
import se.su.dsv.scipro.system.User;
|
import se.su.dsv.scipro.system.User;
|
||||||
@ -19,8 +18,6 @@ public interface GradingReportService {
|
|||||||
SupervisorGradingReport supervisorGradingReport
|
SupervisorGradingReport supervisorGradingReport
|
||||||
);
|
);
|
||||||
|
|
||||||
boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition);
|
|
||||||
|
|
||||||
GradingBasis getGradingBasis(Project project);
|
GradingBasis getGradingBasis(Project project);
|
||||||
|
|
||||||
GradingBasis updateGradingBasis(Project project, GradingBasis gradingBasis);
|
GradingBasis updateGradingBasis(Project project, GradingBasis gradingBasis);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package se.su.dsv.scipro.report;
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
@ -8,6 +9,9 @@ import java.time.Instant;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
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.OppositionCriteria;
|
||||||
import se.su.dsv.scipro.grading.GradingBasis;
|
import se.su.dsv.scipro.grading.GradingBasis;
|
||||||
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
||||||
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
||||||
@ -20,7 +24,8 @@ import se.su.dsv.scipro.system.ProjectTypeService;
|
|||||||
import se.su.dsv.scipro.system.User;
|
import se.su.dsv.scipro.system.User;
|
||||||
import se.su.dsv.scipro.util.Either;
|
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 EventBus eventBus;
|
||||||
private final ThesisSubmissionHistoryService thesisSubmissionHistoryService;
|
private final ThesisSubmissionHistoryService thesisSubmissionHistoryService;
|
||||||
@ -44,11 +49,11 @@ public class GradingReportServiceImpl implements GradingReportTemplateService, G
|
|||||||
this.supervisorGradingReportRepository = supervisorGradingReportRepository;
|
this.supervisorGradingReportRepository = supervisorGradingReportRepository;
|
||||||
this.gradingReportTemplateRepo = gradingReportTemplateRepo;
|
this.gradingReportTemplateRepo = gradingReportTemplateRepo;
|
||||||
this.projectTypeService = projectTypeService;
|
this.projectTypeService = projectTypeService;
|
||||||
|
|
||||||
|
eventBus.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition) {
|
||||||
@Transactional
|
|
||||||
public boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition) {
|
|
||||||
for (GradingCriterion gradingCriterion : report.getIndividualCriteria()) {
|
for (GradingCriterion gradingCriterion : report.getIndividualCriteria()) {
|
||||||
boolean isOppositionCriterion = gradingCriterion.getFlag() == GradingCriterion.Flag.OPPOSITION;
|
boolean isOppositionCriterion = gradingCriterion.getFlag() == GradingCriterion.Flag.OPPOSITION;
|
||||||
boolean betterGrade =
|
boolean betterGrade =
|
||||||
@ -289,4 +294,39 @@ public class GradingReportServiceImpl implements GradingReportTemplateService, G
|
|||||||
|
|
||||||
return gradingReportTemplateRepo.createTemplate(projectType, update);
|
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 OppositionCriteria oppositionCriteria(FinalSeminarOpposition opposition) {
|
||||||
|
SupervisorGradingReport supervisorGradingReport = getSupervisorGradingReport(
|
||||||
|
opposition.getProject(),
|
||||||
|
opposition.getUser()
|
||||||
|
);
|
||||||
|
Optional<GradingCriterion> oppositionGradingCriteria = supervisorGradingReport
|
||||||
|
.getIndividualCriteria()
|
||||||
|
.stream()
|
||||||
|
.filter(individualCriterion -> individualCriterion.getFlag() == AbstractGradingCriterion.Flag.OPPOSITION)
|
||||||
|
.findAny();
|
||||||
|
if (oppositionGradingCriteria.isEmpty()) {
|
||||||
|
return new OppositionCriteria(0, List.of());
|
||||||
|
}
|
||||||
|
List<OppositionCriteria.Point> points = oppositionGradingCriteria
|
||||||
|
.stream()
|
||||||
|
.map(GradingCriterion::getGradingCriterionPoints)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.map(gcp ->
|
||||||
|
new OppositionCriteria.Point(
|
||||||
|
gcp.getPoint(),
|
||||||
|
Objects.requireNonNullElse(gcp.getDescription(Language.ENGLISH), "")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
return new OppositionCriteria(oppositionGradingCriteria.get().getPointsRequiredToPass(), points);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package se.su.dsv.scipro.report;
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import se.su.dsv.scipro.file.FileUpload;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||||
|
|
||||||
public interface OppositionReportService {
|
public interface OppositionReportService {
|
||||||
@ -7,4 +9,10 @@ public interface OppositionReportService {
|
|||||||
void save(OppositionReport oppositionReport);
|
void save(OppositionReport oppositionReport);
|
||||||
void deleteOppositionReport(FinalSeminarOpposition finalSeminarOpposition);
|
void deleteOppositionReport(FinalSeminarOpposition finalSeminarOpposition);
|
||||||
void deleteOpponentReport(FinalSeminarOpposition modelObject);
|
void deleteOpponentReport(FinalSeminarOpposition modelObject);
|
||||||
|
|
||||||
|
AttachmentReport submit(OppositionReport report);
|
||||||
|
|
||||||
|
void save(OppositionReport report, Optional<FileUpload> fileUpload);
|
||||||
|
|
||||||
|
void deleteAttachment(OppositionReport report);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package se.su.dsv.scipro.report;
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.EventBus;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Named;
|
import jakarta.inject.Named;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import java.util.Optional;
|
||||||
import se.su.dsv.scipro.file.FileReference;
|
import se.su.dsv.scipro.file.FileReference;
|
||||||
import se.su.dsv.scipro.file.FileService;
|
import se.su.dsv.scipro.file.FileService;
|
||||||
|
import se.su.dsv.scipro.file.FileUpload;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionRepo;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionRepo;
|
||||||
|
|
||||||
@ -15,18 +18,21 @@ public class OppositionReportServiceImpl implements OppositionReportService {
|
|||||||
private GradingReportTemplateRepo gradingReportTemplateRepo;
|
private GradingReportTemplateRepo gradingReportTemplateRepo;
|
||||||
private FileService fileService;
|
private FileService fileService;
|
||||||
private FinalSeminarOppositionRepo finalSeminarOppositionRepo;
|
private FinalSeminarOppositionRepo finalSeminarOppositionRepo;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public OppositionReportServiceImpl(
|
public OppositionReportServiceImpl(
|
||||||
OppositionReportRepo oppositionReportRepo,
|
OppositionReportRepo oppositionReportRepo,
|
||||||
GradingReportTemplateRepo gradingReportTemplateRepo,
|
GradingReportTemplateRepo gradingReportTemplateRepo,
|
||||||
FileService fileService,
|
FileService fileService,
|
||||||
FinalSeminarOppositionRepo finalSeminarOppositionRepo
|
FinalSeminarOppositionRepo finalSeminarOppositionRepo,
|
||||||
|
EventBus eventBus
|
||||||
) {
|
) {
|
||||||
this.oppositionReportRepo = oppositionReportRepo;
|
this.oppositionReportRepo = oppositionReportRepo;
|
||||||
this.gradingReportTemplateRepo = gradingReportTemplateRepo;
|
this.gradingReportTemplateRepo = gradingReportTemplateRepo;
|
||||||
this.fileService = fileService;
|
this.fileService = fileService;
|
||||||
this.finalSeminarOppositionRepo = finalSeminarOppositionRepo;
|
this.finalSeminarOppositionRepo = finalSeminarOppositionRepo;
|
||||||
|
this.eventBus = eventBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -74,4 +80,36 @@ public class OppositionReportServiceImpl implements OppositionReportService {
|
|||||||
finalSeminarOppositionRepo.save(finalSeminarOpposition);
|
finalSeminarOppositionRepo.save(finalSeminarOpposition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public OppositionReport submit(OppositionReport report) {
|
||||||
|
report.submit();
|
||||||
|
OppositionReport submitted = oppositionReportRepo.save(report);
|
||||||
|
eventBus.post(new OppositionReportSubmittedEvent(submitted));
|
||||||
|
return submitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void save(OppositionReport report, Optional<FileUpload> fileUpload) {
|
||||||
|
storeReportFile(report, fileUpload);
|
||||||
|
save(report);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void deleteAttachment(OppositionReport report) {
|
||||||
|
FileReference attachment = report.getAttachment();
|
||||||
|
report.setAttachment(null);
|
||||||
|
fileService.delete(attachment);
|
||||||
|
oppositionReportRepo.save(report);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeReportFile(OppositionReport report, Optional<FileUpload> fileUpload) {
|
||||||
|
if (fileUpload.isPresent()) {
|
||||||
|
final FileReference reference = fileService.storeFile(fileUpload.get());
|
||||||
|
report.setAttachment(reference);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package se.su.dsv.scipro.report;
|
||||||
|
|
||||||
|
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
||||||
|
|
||||||
|
public record OppositionReportSubmittedEvent(OppositionReport report) {
|
||||||
|
public FinalSeminar finalSeminar() {
|
||||||
|
return report().getFinalSeminarOpposition().getFinalSeminar();
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package se.su.dsv.scipro.report;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import se.su.dsv.scipro.file.FileUpload;
|
|
||||||
import se.su.dsv.scipro.system.GenericService;
|
|
||||||
|
|
||||||
public interface ReportService extends GenericService<Report, Long> {
|
|
||||||
AttachmentReport submit(AttachmentReport report);
|
|
||||||
|
|
||||||
void save(AttachmentReport report, Optional<FileUpload> fileUpload);
|
|
||||||
|
|
||||||
void deleteAttachment(AttachmentReport report);
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package se.su.dsv.scipro.report;
|
|
||||||
|
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.inject.Provider;
|
|
||||||
import jakarta.persistence.EntityManager;
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
import java.util.Optional;
|
|
||||||
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.system.AbstractServiceImpl;
|
|
||||||
|
|
||||||
public class ReportServiceImpl extends AbstractServiceImpl<Report, Long> implements ReportService {
|
|
||||||
|
|
||||||
private final FileService fileDescriptionService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ReportServiceImpl(Provider<EntityManager> em, final FileService fileDescriptionService) {
|
|
||||||
super(em, Report.class, QReport.report);
|
|
||||||
this.fileDescriptionService = fileDescriptionService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public AttachmentReport submit(AttachmentReport report) {
|
|
||||||
report.submit();
|
|
||||||
return save(report);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void save(AttachmentReport report, Optional<FileUpload> fileUpload) {
|
|
||||||
storeReportFile(report, fileUpload);
|
|
||||||
save(report);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void deleteAttachment(AttachmentReport report) {
|
|
||||||
FileReference attachment = report.getAttachment();
|
|
||||||
report.setAttachment(null);
|
|
||||||
fileDescriptionService.delete(attachment);
|
|
||||||
save(report);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeReportFile(AttachmentReport report, Optional<FileUpload> fileUpload) {
|
|
||||||
if (fileUpload.isPresent()) {
|
|
||||||
final FileReference reference = fileDescriptionService.storeFile(fileUpload.get());
|
|
||||||
report.setAttachment(reference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE `final_seminar_opposition`
|
||||||
|
ADD COLUMN `improvements_requested_at` DATETIME NULL,
|
||||||
|
ADD COLUMN `supervisor_improvements_comment` TEXT NULL;
|
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `final_seminar_settings`
|
||||||
|
ADD COLUMN `work_days_to_fix_requested_improvements_to_opposition_report` INT(11) NOT NULL DEFAULT 10;
|
@ -1,14 +1,22 @@
|
|||||||
package se.su.dsv.scipro.finalseminar;
|
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.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.Month;
|
import java.time.Month;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import se.su.dsv.scipro.project.Project;
|
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.GradingReportTemplate;
|
||||||
import se.su.dsv.scipro.report.OppositionReport;
|
import se.su.dsv.scipro.report.OppositionReport;
|
||||||
import se.su.dsv.scipro.system.DegreeType;
|
import se.su.dsv.scipro.system.DegreeType;
|
||||||
@ -46,6 +54,76 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio
|
|||||||
assertEquals(0, finalSeminarOppositionService.count());
|
assertEquals(0, finalSeminarOppositionService.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
private void createOppositionReport(FinalSeminarOpposition opposition) {
|
private void createOppositionReport(FinalSeminarOpposition opposition) {
|
||||||
OppositionReport report = new OppositionReport(createGradingReportTemplate(), opposition);
|
OppositionReport report = new OppositionReport(createGradingReportTemplate(), opposition);
|
||||||
opposition.setOppositionReport(report);
|
opposition.setOppositionReport(report);
|
||||||
@ -93,7 +171,7 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio
|
|||||||
FinalSeminarOpposition opposition = new FinalSeminarOpposition();
|
FinalSeminarOpposition opposition = new FinalSeminarOpposition();
|
||||||
opposition.setFinalSeminar(finalSeminar);
|
opposition.setFinalSeminar(finalSeminar);
|
||||||
opposition.setUser(student);
|
opposition.setUser(student);
|
||||||
opposition.setProject(createProject(createProjectType()));
|
opposition.setProject(createProject(projectType));
|
||||||
return save(opposition);
|
return save(opposition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import static se.su.dsv.scipro.test.Matchers.isRight;
|
|||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.Month;
|
import java.time.Month;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
@ -30,6 +31,9 @@ public class FinalSeminarServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
@Inject
|
@Inject
|
||||||
private FinalSeminarService finalSeminarService;
|
private FinalSeminarService finalSeminarService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private FinalSeminarOppositionService finalSeminarOppositionService;
|
||||||
|
|
||||||
private ProjectType projectType;
|
private ProjectType projectType;
|
||||||
private FinalSeminar futureFinalSeminar;
|
private FinalSeminar futureFinalSeminar;
|
||||||
private User user;
|
private User user;
|
||||||
@ -309,6 +313,43 @@ public class FinalSeminarServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
assertThat(finalSeminarService.canOppose(user, finalSeminar, otherProject), isRight(anything()));
|
assertThat(finalSeminarService.canOppose(user, finalSeminar, otherProject), isRight(anything()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seminar_is_not_unfinished_if_opponent_has_improvements_requested() {
|
||||||
|
FinalSeminar seminar = createFinalSeminar(createProject(), -6);
|
||||||
|
FinalSeminarOpposition finalSeminarOpposition = addOpposition(seminar, null);
|
||||||
|
addOppositionReport(finalSeminarOpposition);
|
||||||
|
|
||||||
|
Date after = Date.from(seminar.getStartDate().toInstant().minus(Duration.ofDays(1)));
|
||||||
|
Date before = Date.from(seminar.getStartDate().toInstant().plus(Duration.ofDays(1)));
|
||||||
|
|
||||||
|
List<FinalSeminar> unfinishedSeminars = finalSeminarService.findUnfinishedSeminars(
|
||||||
|
after,
|
||||||
|
before,
|
||||||
|
new PageRequest(0, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(unfinishedSeminars, hasItem(seminar));
|
||||||
|
|
||||||
|
finalSeminarOppositionService.requestImprovements(finalSeminarOpposition, "improvements");
|
||||||
|
|
||||||
|
List<FinalSeminar> afterImprovements = finalSeminarService.findUnfinishedSeminars(
|
||||||
|
after,
|
||||||
|
before,
|
||||||
|
new PageRequest(0, 5)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(afterImprovements, not(hasItem(seminar)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addOppositionReport(FinalSeminarOpposition finalSeminarOpposition) {
|
||||||
|
GradingReportTemplate gradingReportTemplate = new GradingReportTemplate(
|
||||||
|
finalSeminarOpposition.getProjectType(),
|
||||||
|
LocalDate.now()
|
||||||
|
);
|
||||||
|
OppositionReport oppositionReport = new OppositionReport(gradingReportTemplate, finalSeminarOpposition);
|
||||||
|
finalSeminarOpposition.setOppositionReport(oppositionReport);
|
||||||
|
}
|
||||||
|
|
||||||
private FinalSeminar createFutureFinalSeminarSomeDaysAgo(final int daysAgo) {
|
private FinalSeminar createFutureFinalSeminarSomeDaysAgo(final int daysAgo) {
|
||||||
FinalSeminar finalSeminar = initFinalSeminar(createProject(), 5);
|
FinalSeminar finalSeminar = initFinalSeminar(createProject(), 5);
|
||||||
final Date dateCreated = Date.from(ZonedDateTime.now().minusDays(daysAgo).toInstant());
|
final Date dateCreated = Date.from(ZonedDateTime.now().minusDays(daysAgo).toInstant());
|
||||||
@ -340,10 +381,10 @@ public class FinalSeminarServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
save(seminar);
|
save(seminar);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addOpposition(FinalSeminar seminar, FinalSeminarGrade grade) {
|
private FinalSeminarOpposition addOpposition(FinalSeminar seminar, FinalSeminarGrade grade) {
|
||||||
FinalSeminarOpposition opposition = createOpposition(seminar);
|
FinalSeminarOpposition opposition = createOpposition(seminar);
|
||||||
opposition.setGrade(grade);
|
opposition.setGrade(grade);
|
||||||
save(opposition);
|
return save(opposition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OppositionReport createOppositionReport(FinalSeminarOpposition opposition) {
|
private OppositionReport createOppositionReport(FinalSeminarOpposition opposition) {
|
||||||
|
@ -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.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.EventBus;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.Month;
|
import java.time.Month;
|
||||||
@ -13,6 +14,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
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.project.Project;
|
||||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||||
import se.su.dsv.scipro.system.DegreeType;
|
import se.su.dsv.scipro.system.DegreeType;
|
||||||
@ -31,6 +33,9 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
@Inject
|
@Inject
|
||||||
private GradingReportServiceImpl gradingReportService;
|
private GradingReportServiceImpl gradingReportService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private EventBus eventBus;
|
||||||
|
|
||||||
private ProjectType projectType;
|
private ProjectType projectType;
|
||||||
private GradingReportTemplate gradingReportTemplate;
|
private GradingReportTemplate gradingReportTemplate;
|
||||||
private Project project;
|
private Project project;
|
||||||
@ -45,7 +50,6 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
project = createProject(projectType, 30);
|
project = createProject(projectType, 30);
|
||||||
gradingReportTemplate = createProjectGradingCriterion(gradingReportTemplate, 2);
|
gradingReportTemplate = createProjectGradingCriterion(gradingReportTemplate, 2);
|
||||||
gradingReportTemplate = createIndividualGradingCriterion(gradingReportTemplate, 2);
|
gradingReportTemplate = createIndividualGradingCriterion(gradingReportTemplate, 2);
|
||||||
gradingReport = createGradingReport(project, student);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -68,6 +72,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void submit_supervisor_grading_report_flags_report_as_submitted() {
|
public void submit_supervisor_grading_report_flags_report_as_submitted() {
|
||||||
|
gradingReport = createGradingReport(project, student);
|
||||||
assessAllCriteria(gradingReport);
|
assessAllCriteria(gradingReport);
|
||||||
Either<List<SubmissionError>, SupervisorGradingReport> result = gradingReportService.submitReport(
|
Either<List<SubmissionError>, SupervisorGradingReport> result = gradingReportService.submitReport(
|
||||||
gradingReport
|
gradingReport
|
||||||
@ -77,6 +82,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void submitting_supervisor_report_throws_exception_if_report_is_not_finished() {
|
public void submitting_supervisor_report_throws_exception_if_report_is_not_finished() {
|
||||||
|
gradingReport = createGradingReport(project, student);
|
||||||
Either<List<SubmissionError>, SupervisorGradingReport> result = gradingReportService.submitReport(
|
Either<List<SubmissionError>, SupervisorGradingReport> result = gradingReportService.submitReport(
|
||||||
gradingReport
|
gradingReport
|
||||||
);
|
);
|
||||||
@ -86,38 +92,35 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
public void update_opposition_criterion() {
|
public void update_opposition_criterion() {
|
||||||
addOppositionCriterion();
|
addOppositionCriterion();
|
||||||
boolean updated = updateOppositionCriterion();
|
updateOppositionCriterion();
|
||||||
|
|
||||||
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
||||||
assert oppositionCriterion != null;
|
assert oppositionCriterion != null;
|
||||||
assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
|
assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
|
||||||
assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
|
assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
|
||||||
assertTrue(updated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void update_opposition_if_title_matches_english_title() {
|
public void update_opposition_if_title_matches_english_title() {
|
||||||
addOppositionCriterion();
|
addOppositionCriterion();
|
||||||
boolean updated = updateOppositionCriterion();
|
updateOppositionCriterion();
|
||||||
|
|
||||||
GradingCriterion oppositionCriterion = findEnglishOppositionCriterion("Ö1 Opposition report");
|
GradingCriterion oppositionCriterion = findEnglishOppositionCriterion("Ö1 Opposition report");
|
||||||
assert oppositionCriterion != null;
|
assert oppositionCriterion != null;
|
||||||
assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
|
assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
|
||||||
assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
|
assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
|
||||||
assertTrue(updated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updating_opposition_criterion_does_nothing_if_criterion_already_has_values() {
|
public void updating_opposition_criterion_does_nothing_if_criterion_already_has_values() {
|
||||||
addOppositionCriterion();
|
addOppositionCriterion();
|
||||||
assessAllCriteria(gradingReport);
|
assessAllCriteria(gradingReport);
|
||||||
boolean updated = updateOppositionCriterion();
|
updateOppositionCriterion();
|
||||||
|
|
||||||
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
||||||
assert oppositionCriterion != null;
|
assert oppositionCriterion != null;
|
||||||
assertEquals(FEEDBACK, oppositionCriterion.getFeedback());
|
assertEquals(FEEDBACK, oppositionCriterion.getFeedback());
|
||||||
assertEquals((Integer) oppositionCriterion.getMaxPoints(), oppositionCriterion.getPoints());
|
assertEquals((Integer) oppositionCriterion.getMaxPoints(), oppositionCriterion.getPoints());
|
||||||
assertFalse(updated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -151,9 +154,9 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
gradingReport = createGradingReport(project, student);
|
gradingReport = createGradingReport(project, student);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean updateOppositionCriterion() {
|
private void updateOppositionCriterion() {
|
||||||
FinalSeminarOpposition opposition = createFinalSeminarOpposition();
|
FinalSeminarOpposition opposition = createFinalSeminarOpposition();
|
||||||
return gradingReportService.updateOppositionCriteria(gradingReport, opposition);
|
eventBus.post(new OppositionApprovedEvent(opposition));
|
||||||
}
|
}
|
||||||
|
|
||||||
private GradingCriterion findOppositionCriterion() {
|
private GradingCriterion findOppositionCriterion() {
|
||||||
@ -176,8 +179,8 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
|||||||
|
|
||||||
private FinalSeminarOpposition createFinalSeminarOpposition() {
|
private FinalSeminarOpposition createFinalSeminarOpposition() {
|
||||||
FinalSeminarOpposition finalSeminarOpposition = new FinalSeminarOpposition();
|
FinalSeminarOpposition finalSeminarOpposition = new FinalSeminarOpposition();
|
||||||
finalSeminarOpposition.setProject(createProject(projectType, 30));
|
finalSeminarOpposition.setProject(project);
|
||||||
finalSeminarOpposition.setUser(createStudent());
|
finalSeminarOpposition.setUser(student);
|
||||||
finalSeminarOpposition.setFinalSeminar(createFinalSeminar());
|
finalSeminarOpposition.setFinalSeminar(createFinalSeminar());
|
||||||
|
|
||||||
finalSeminarOpposition.setFeedback(FEEDBACK_ON_OPPOSITION);
|
finalSeminarOpposition.setFeedback(FEEDBACK_ON_OPPOSITION);
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package se.su.dsv.scipro.test;
|
package se.su.dsv.scipro.test;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.EventBus;
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.EntityManagerFactory;
|
import jakarta.persistence.EntityManagerFactory;
|
||||||
import jakarta.persistence.EntityTransaction;
|
import jakarta.persistence.EntityTransaction;
|
||||||
import jakarta.persistence.Persistence;
|
import jakarta.persistence.Persistence;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.flywaydb.core.Flyway;
|
import org.flywaydb.core.Flyway;
|
||||||
@ -35,6 +38,8 @@ public abstract class SpringTest {
|
|||||||
@Container
|
@Container
|
||||||
static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>("mariadb:10.11");
|
static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>("mariadb:10.11");
|
||||||
|
|
||||||
|
private CapturingEventBus capturingEventBus;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public final void prepareSpring() throws SQLException {
|
public final void prepareSpring() throws SQLException {
|
||||||
MariaDbDataSource dataSource = new MariaDbDataSource(mariaDBContainer.getJdbcUrl());
|
MariaDbDataSource dataSource = new MariaDbDataSource(mariaDBContainer.getJdbcUrl());
|
||||||
@ -50,8 +55,11 @@ public abstract class SpringTest {
|
|||||||
transaction.begin();
|
transaction.begin();
|
||||||
transaction.setRollbackOnly();
|
transaction.setRollbackOnly();
|
||||||
|
|
||||||
|
capturingEventBus = new CapturingEventBus();
|
||||||
|
|
||||||
AnnotationConfigApplicationContext annotationConfigApplicationContext =
|
AnnotationConfigApplicationContext annotationConfigApplicationContext =
|
||||||
new AnnotationConfigApplicationContext();
|
new AnnotationConfigApplicationContext();
|
||||||
|
annotationConfigApplicationContext.registerBean("eventBus", EventBus.class, () -> this.capturingEventBus);
|
||||||
annotationConfigApplicationContext.register(TestContext.class);
|
annotationConfigApplicationContext.register(TestContext.class);
|
||||||
annotationConfigApplicationContext.getBeanFactory().registerSingleton("entityManager", this.entityManager);
|
annotationConfigApplicationContext.getBeanFactory().registerSingleton("entityManager", this.entityManager);
|
||||||
annotationConfigApplicationContext.refresh();
|
annotationConfigApplicationContext.refresh();
|
||||||
@ -75,6 +83,10 @@ public abstract class SpringTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected List<Object> getPublishedEvents() {
|
||||||
|
return capturingEventBus.publishedEvents;
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@Import({ CoreConfig.class, RepositoryConfiguration.class })
|
@Import({ CoreConfig.class, RepositoryConfiguration.class })
|
||||||
public static class TestContext {
|
public static class TestContext {
|
||||||
@ -106,4 +118,15 @@ public abstract class SpringTest {
|
|||||||
return currentProfile;
|
return currentProfile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class CapturingEventBus extends EventBus {
|
||||||
|
|
||||||
|
private List<Object> publishedEvents = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void post(Object event) {
|
||||||
|
publishedEvents.add(event);
|
||||||
|
super.post(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="col-lg-4">How many work days opponents have to resubmit their report</label>
|
||||||
|
<div class="col-lg-1">
|
||||||
|
<input class="form-control" type="text" wicket:id="work_days_to_fix_requested_improvements_to_opposition_report" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="col-lg-offset-4 col-lg-4">
|
<div class="col-lg-offset-4 col-lg-4">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
|
@ -131,6 +131,17 @@ public class AdminFinalSeminarSettingsPage extends AbstractAdminSystemPage {
|
|||||||
Integer.class
|
Integer.class
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
add(
|
||||||
|
new RequiredTextField<>(
|
||||||
|
"work_days_to_fix_requested_improvements_to_opposition_report",
|
||||||
|
LambdaModel.of(
|
||||||
|
model,
|
||||||
|
FinalSeminarSettings::getWorkDaysToFixRequestedImprovementsToOppositionReport,
|
||||||
|
FinalSeminarSettings::setWorkDaysToFixRequestedImprovementsToOppositionReport
|
||||||
|
),
|
||||||
|
Integer.class
|
||||||
|
)
|
||||||
|
);
|
||||||
add(
|
add(
|
||||||
new CheckBox(
|
new CheckBox(
|
||||||
SEMINAR_PDF,
|
SEMINAR_PDF,
|
||||||
|
@ -6,29 +6,36 @@
|
|||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<h4>Opposition report</h4>
|
<h4>Opposition report</h4>
|
||||||
|
|
||||||
<div class="row mb-4">
|
<div class="help-box mb-3">
|
||||||
<div class="col-lg-8">
|
Use the assessment criteria in this report and write your views as an opponent in the text
|
||||||
<div class="help-box">
|
fields under each criterion. However, you do not make a point assessment but are free to
|
||||||
Använd bedömningskriterierna i denna rapport och skriv dina synpunkter som opponent i fritextfälten
|
write as much as you wish on each criterion.
|
||||||
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.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<strong>Final seminar file:</strong> <span wicket:id="thesisFile"></span>
|
<strong>Final seminar file:</strong> <span wicket:id="thesisFile"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div wicket:id="fillOutReport">
|
<wicket:enclosure child="improvements_requested_comment">
|
||||||
<strong>Thesis summary</strong>
|
<div class="alert alert-info">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Ge en kort sammanfattning av det utvärderade arbetet.
|
The supervisor has requested improvements to your opposition report.
|
||||||
|
You have until <span wicket:id="improvements_requested_deadline"></span>
|
||||||
|
to make the requested changes. See below for the comments from the supervisor.
|
||||||
</p>
|
</p>
|
||||||
<label>
|
<p class="mb-0" wicket:id="improvements_requested_comment"></p>
|
||||||
<textarea class="form-control mb-4" rows="8" wicket:id="thesisSummary"></textarea>
|
</div>
|
||||||
|
</wicket:enclosure>
|
||||||
|
|
||||||
|
<div wicket:id="fillOutReport">
|
||||||
|
<label wicket:for="thesisSummary">
|
||||||
|
Thesis summary
|
||||||
</label>
|
</label>
|
||||||
|
<p>
|
||||||
|
Give a short summary of the evaluated work.
|
||||||
|
</p>
|
||||||
|
<textarea class="form-control mb-4" rows="8" wicket:id="thesisSummary"></textarea>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package se.su.dsv.scipro.finalseminar;
|
package se.su.dsv.scipro.finalseminar;
|
||||||
|
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Optional;
|
||||||
import org.apache.wicket.RestartResponseException;
|
import org.apache.wicket.RestartResponseException;
|
||||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
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.markup.html.form.TextArea;
|
||||||
import org.apache.wicket.model.IModel;
|
import org.apache.wicket.model.IModel;
|
||||||
import org.apache.wicket.model.LambdaModel;
|
import org.apache.wicket.model.LambdaModel;
|
||||||
@ -24,7 +27,7 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements
|
|||||||
public static final String FILL_OUT_REPORT = "fillOutReport";
|
public static final String FILL_OUT_REPORT = "fillOutReport";
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private FinalSeminarOppositionRepo finalSeminarOppositionRepo;
|
private FinalSeminarOppositionService finalSeminarOppositionService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private OppositionReportService oppositionReportService;
|
private OppositionReportService oppositionReportService;
|
||||||
@ -35,13 +38,15 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements
|
|||||||
throw new RestartResponseException(ProjectDetailsPage.class, pp);
|
throw new RestartResponseException(ProjectDetailsPage.class, pp);
|
||||||
}
|
}
|
||||||
|
|
||||||
final FinalSeminarOpposition opposition = finalSeminarOppositionRepo.findOne(pp.get("oid").toLong());
|
final IModel<Opposition> opposition = LoadableDetachableModel.of(() ->
|
||||||
|
finalSeminarOppositionService.getOpposition(pp.get("oid").toLong())
|
||||||
|
);
|
||||||
|
|
||||||
if (opposition == null) {
|
if (opposition.getObject() == null) {
|
||||||
throw new RestartResponseException(ProjectDetailsPage.class, pp);
|
throw new RestartResponseException(ProjectDetailsPage.class, pp);
|
||||||
}
|
}
|
||||||
|
|
||||||
final IModel<OppositionReport> report = getOppositionReport(opposition);
|
final IModel<OppositionReport> report = opposition.map(Opposition::report);
|
||||||
|
|
||||||
add(
|
add(
|
||||||
new ViewAttachmentPanel(
|
new ViewAttachmentPanel(
|
||||||
@ -50,8 +55,35 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
IModel<Opposition.ImprovementsNeeded> improvements = opposition
|
||||||
|
.map(Opposition::improvementsNeeded)
|
||||||
|
.map(OppositionReportPage::orNull);
|
||||||
add(
|
add(
|
||||||
new FillOutReportPanel<>(FILL_OUT_REPORT, report) {
|
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) {
|
||||||
{
|
{
|
||||||
TextArea<String> textArea = new TextArea<>(
|
TextArea<String> textArea = new TextArea<>(
|
||||||
THESIS_SUMMARY,
|
THESIS_SUMMARY,
|
||||||
@ -71,18 +103,13 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements
|
|||||||
@Override
|
@Override
|
||||||
protected void onConfigure() {
|
protected void onConfigure() {
|
||||||
super.onConfigure();
|
super.onConfigure();
|
||||||
setEnabled(opposition.getUser().equals(SciProSession.get().getUser()));
|
setEnabled(opposition.getObject().user().equals(SciProSession.get().getUser()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IModel<OppositionReport> getOppositionReport(final FinalSeminarOpposition opposition) {
|
private static <A> A orNull(Optional<A> optional) {
|
||||||
return new LoadableDetachableModel<>() {
|
return optional.orElse(null);
|
||||||
@Override
|
|
||||||
protected OppositionReport load() {
|
|
||||||
return oppositionReportService.findOrCreateReport(opposition);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<div wicket:id="container">
|
<div wicket:id="container">
|
||||||
<div wicket:id="opponents">
|
<div wicket:id="opponents">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-7 mb-3">
|
<div class="col-lg-7">
|
||||||
<span wicket:id="user"></span><a href="#" wicket:id="remove"><span class="fa fa-times"></span></a><br>
|
<span wicket:id="user"></span><a href="#" wicket:id="remove"><span class="fa fa-times"></span></a><br>
|
||||||
<div wicket:id="report"></div>
|
<div wicket:id="report"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -19,13 +19,31 @@
|
|||||||
<div class="col-lg-5">
|
<div class="col-lg-5">
|
||||||
|
|
||||||
<form wicket:id="form">
|
<form wicket:id="form">
|
||||||
<div class="card mb-3 bg-info text-white">
|
<div class="card mb-3 text-bg-info">
|
||||||
<wicket:message key="criteria"/>
|
<div class="card-body">
|
||||||
|
<p class="card-text">
|
||||||
|
<wicket:message key="criteria"/>
|
||||||
|
</p>
|
||||||
|
<p class="card-text" wicket:id="requirements">
|
||||||
|
<wicket:container wicket:id="requirement"/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<wicket:enclosure>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<p>
|
||||||
|
You've requested improvements to the opposition report with the below comment.
|
||||||
|
If they do not make the requested improvements in time, they will get an automatic failing grade.
|
||||||
|
The system will notify you when they've submitted a new report.
|
||||||
|
</p>
|
||||||
|
<span wicket:id="improvements_requested"></span>
|
||||||
|
</div>
|
||||||
|
</wicket:enclosure>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label>Points:</label>
|
<label>Points:</label>
|
||||||
<input type="text" class="form-control gradingPoints" wicket:id="points"/>
|
<select class="form-select" wicket:id="points"></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label>Motivation:</label>
|
<label>Motivation:</label>
|
||||||
@ -34,6 +52,20 @@
|
|||||||
<button wicket:id="submit" type="submit" class="btn btn-sm btn-success">
|
<button wicket:id="submit" type="submit" class="btn btn-sm btn-success">
|
||||||
<wicket:message key="submit"/>
|
<wicket:message key="submit"/>
|
||||||
</button>
|
</button>
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" wicket:id="request_improvements">
|
||||||
|
Request improvements
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form wicket:id="request_improvements">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" wicket:for="feedback_to_opponent">
|
||||||
|
Provide feedback to the opponent on how to improve the opposition
|
||||||
|
</label>
|
||||||
|
<textarea class="form-control" wicket:id="feedback_to_opponent" rows="8"></textarea>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-sm btn-success">Request improvements</button>
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" wicket:id="cancel">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div wicket:id="gradeContainer">
|
<div wicket:id="gradeContainer">
|
||||||
|
@ -2,18 +2,23 @@ package se.su.dsv.scipro.finalseminar;
|
|||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
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.ajax.markup.html.form.AjaxSubmitLink;
|
||||||
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||||
import org.apache.wicket.markup.html.basic.Label;
|
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.Form;
|
||||||
import org.apache.wicket.markup.html.form.FormComponent;
|
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.TextArea;
|
||||||
import org.apache.wicket.markup.html.form.TextField;
|
|
||||||
import org.apache.wicket.markup.html.link.Link;
|
import org.apache.wicket.markup.html.link.Link;
|
||||||
import org.apache.wicket.markup.html.list.ListItem;
|
import org.apache.wicket.markup.html.list.ListItem;
|
||||||
import org.apache.wicket.markup.html.list.ListView;
|
import org.apache.wicket.markup.html.list.ListView;
|
||||||
@ -22,14 +27,15 @@ import org.apache.wicket.markup.html.panel.FeedbackPanel;
|
|||||||
import org.apache.wicket.markup.html.panel.Panel;
|
import org.apache.wicket.markup.html.panel.Panel;
|
||||||
import org.apache.wicket.model.IModel;
|
import org.apache.wicket.model.IModel;
|
||||||
import org.apache.wicket.model.LambdaModel;
|
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.model.ResourceModel;
|
||||||
import org.apache.wicket.validation.validator.RangeValidator;
|
|
||||||
import org.apache.wicket.validation.validator.StringValidator;
|
import org.apache.wicket.validation.validator.StringValidator;
|
||||||
import se.su.dsv.scipro.components.ListAdapterModel;
|
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.profile.UserLinkPanel;
|
||||||
import se.su.dsv.scipro.report.GradingReportService;
|
import se.su.dsv.scipro.report.GradingReportService;
|
||||||
import se.su.dsv.scipro.report.OppositionReportService;
|
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.security.auth.roles.Roles;
|
||||||
import se.su.dsv.scipro.session.SciProSession;
|
import se.su.dsv.scipro.session.SciProSession;
|
||||||
import se.su.dsv.scipro.system.ProjectModule;
|
import se.su.dsv.scipro.system.ProjectModule;
|
||||||
@ -43,8 +49,6 @@ public class SeminarOppositionPanel extends Panel {
|
|||||||
public static final String REMOVE = "remove";
|
public static final String REMOVE = "remove";
|
||||||
public static final String FORM = "form";
|
public static final String FORM = "form";
|
||||||
public static final String POINTS = "points";
|
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 String GRADING_FEEDBACK = "gradingFeedback";
|
||||||
public static final int FEEDBACK_MAX_LENGTH = 2000;
|
public static final int FEEDBACK_MAX_LENGTH = 2000;
|
||||||
public static final String SUBMIT = "submit";
|
public static final String SUBMIT = "submit";
|
||||||
@ -75,6 +79,9 @@ public class SeminarOppositionPanel extends Panel {
|
|||||||
private final WebMarkupContainer oppositionContainer;
|
private final WebMarkupContainer oppositionContainer;
|
||||||
private final ListView<FinalSeminarOpposition> opponents;
|
private final ListView<FinalSeminarOpposition> opponents;
|
||||||
|
|
||||||
|
private FinalSeminarOppositionForm gradeForm;
|
||||||
|
private RequestImprovementsForm requestImprovementsForm;
|
||||||
|
|
||||||
public SeminarOppositionPanel(String id, final IModel<FinalSeminar> seminar) {
|
public SeminarOppositionPanel(String id, final IModel<FinalSeminar> seminar) {
|
||||||
super(id, seminar);
|
super(id, seminar);
|
||||||
this.seminar = seminar;
|
this.seminar = seminar;
|
||||||
@ -107,6 +114,12 @@ public class SeminarOppositionPanel extends Panel {
|
|||||||
|
|
||||||
private ListView<FinalSeminarOpposition> getOpponentsList(final IModel<List<FinalSeminarOpposition>> oppositions) {
|
private ListView<FinalSeminarOpposition> getOpponentsList(final IModel<List<FinalSeminarOpposition>> oppositions) {
|
||||||
return new ListView<>(OPPONENTS, 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
|
@Override
|
||||||
protected void populateItem(final ListItem<FinalSeminarOpposition> item) {
|
protected void populateItem(final ListItem<FinalSeminarOpposition> item) {
|
||||||
final FinalSeminarOpposition opposition = item.getModelObject();
|
final FinalSeminarOpposition opposition = item.getModelObject();
|
||||||
@ -121,7 +134,14 @@ public class SeminarOppositionPanel extends Panel {
|
|||||||
|
|
||||||
item.add(getRemoveLink(item.getModel()));
|
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()) {
|
if (gradingModuleIsOnForProjectType()) {
|
||||||
item.add(new SeminarOppositionReportPanel("report", item.getModel()));
|
item.add(new SeminarOppositionReportPanel("report", item.getModel()));
|
||||||
@ -211,29 +231,47 @@ public class SeminarOppositionPanel extends Panel {
|
|||||||
|
|
||||||
private class FinalSeminarOppositionForm extends Form<FinalSeminarOpposition> {
|
private class FinalSeminarOppositionForm extends Form<FinalSeminarOpposition> {
|
||||||
|
|
||||||
|
private IModel<OppositionCriteria.Point> pointsModel = new StatelessModel<>();
|
||||||
|
private IModel<String> feedbackModel = new Model<>();
|
||||||
|
|
||||||
public FinalSeminarOppositionForm(String id, final IModel<FinalSeminarOpposition> finalSeminarOpposition) {
|
public FinalSeminarOppositionForm(String id, final IModel<FinalSeminarOpposition> finalSeminarOpposition) {
|
||||||
super(id, finalSeminarOpposition);
|
super(id, finalSeminarOpposition);
|
||||||
FormComponent<Integer> pointsField = new TextField<>(
|
IModel<OppositionCriteria> criteriaModel = LoadableDetachableModel.of(() ->
|
||||||
|
finalSeminarOppositionService.getCriteriaForOpposition(finalSeminarOpposition.getObject())
|
||||||
|
);
|
||||||
|
|
||||||
|
add(
|
||||||
|
new ListView<>("requirements", criteriaModel.map(this::getPointsWithRequirements)) {
|
||||||
|
@Override
|
||||||
|
protected void populateItem(ListItem<OppositionCriteria.Point> item) {
|
||||||
|
item.add(new Label("requirement", item.getModel().map(OppositionCriteria.Point::requirement)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
IModel<String> improvementComment = finalSeminarOpposition.map(
|
||||||
|
FinalSeminarOpposition::getSupervisorCommentForImprovements
|
||||||
|
);
|
||||||
|
add(
|
||||||
|
new Label("improvements_requested", improvementComment) {
|
||||||
|
@Override
|
||||||
|
protected void onConfigure() {
|
||||||
|
super.onConfigure();
|
||||||
|
setVisible(!getDefaultModelObjectAsString().isBlank());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
FormComponent<OppositionCriteria.Point> pointsField = new DropDownChoice<>(
|
||||||
POINTS,
|
POINTS,
|
||||||
LambdaModel.of(
|
pointsModel,
|
||||||
finalSeminarOpposition,
|
criteriaModel.map(OppositionCriteria::pointsAvailable),
|
||||||
FinalSeminarOpposition::getPoints,
|
new LambdaChoiceRenderer<>(OppositionCriteria.Point::value)
|
||||||
FinalSeminarOpposition::setPoints
|
);
|
||||||
)
|
pointsField.setRequired(true);
|
||||||
)
|
|
||||||
.add(RangeValidator.range(MIN_POINTS, MAX_POINTS))
|
|
||||||
.setType(Integer.class)
|
|
||||||
.setRequired(true);
|
|
||||||
add(pointsField);
|
add(pointsField);
|
||||||
|
|
||||||
TextArea<String> feedback = new TextArea<>(
|
TextArea<String> feedback = new TextArea<>(GRADING_FEEDBACK, feedbackModel);
|
||||||
GRADING_FEEDBACK,
|
|
||||||
LambdaModel.of(
|
|
||||||
finalSeminarOpposition,
|
|
||||||
FinalSeminarOpposition::getFeedback,
|
|
||||||
FinalSeminarOpposition::setFeedback
|
|
||||||
)
|
|
||||||
);
|
|
||||||
feedback.add(StringValidator.maximumLength(FEEDBACK_MAX_LENGTH));
|
feedback.add(StringValidator.maximumLength(FEEDBACK_MAX_LENGTH));
|
||||||
feedback.setRequired(true);
|
feedback.setRequired(true);
|
||||||
add(feedback);
|
add(feedback);
|
||||||
@ -242,33 +280,19 @@ public class SeminarOppositionPanel extends Panel {
|
|||||||
new AjaxSubmitLink(SUBMIT) {
|
new AjaxSubmitLink(SUBMIT) {
|
||||||
@Override
|
@Override
|
||||||
protected void onSubmit(AjaxRequestTarget target) {
|
protected void onSubmit(AjaxRequestTarget target) {
|
||||||
if (getModelObject().getPoints().equals(0)) {
|
try {
|
||||||
finalSeminarOpposition.getObject().setGrade(FinalSeminarGrade.NOT_APPROVED);
|
finalSeminarOppositionService.gradeOpponent(
|
||||||
eventBus.post(new OppositionFailedEvent(finalSeminarOpposition.getObject()));
|
finalSeminarOpposition.getObject(),
|
||||||
} else {
|
pointsModel.getObject().value(),
|
||||||
finalSeminarOpposition.getObject().setGrade(FinalSeminarGrade.APPROVED);
|
feedbackModel.getObject()
|
||||||
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("feedback.opponent.updated", finalSeminarOpposition));
|
||||||
|
target.add(feedbackPanel);
|
||||||
|
target.add(oppositionContainer);
|
||||||
|
} catch (PointNotValidException e) {
|
||||||
|
error(getString("point.not.valid"));
|
||||||
|
target.add(feedbackPanel);
|
||||||
}
|
}
|
||||||
success(
|
|
||||||
getString(
|
|
||||||
updated ? "feedback.opponent.updated" : "feedback.opponent.not.updated",
|
|
||||||
finalSeminarOpposition
|
|
||||||
)
|
|
||||||
);
|
|
||||||
target.add(feedbackPanel);
|
|
||||||
target.add(oppositionContainer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -277,6 +301,31 @@ public class SeminarOppositionPanel extends Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add(
|
||||||
|
new AjaxLink<Void>("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();"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<OppositionCriteria.Point> getPointsWithRequirements(OppositionCriteria oppositionCriteria) {
|
||||||
|
return oppositionCriteria
|
||||||
|
.pointsAvailable()
|
||||||
|
.stream()
|
||||||
|
.filter(point -> !point.requirement().isBlank())
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -298,4 +347,47 @@ public class SeminarOppositionPanel extends Panel {
|
|||||||
private boolean hasSubmittedOppositionReport(FinalSeminarOpposition opposition) {
|
private boolean hasSubmittedOppositionReport(FinalSeminarOpposition opposition) {
|
||||||
return oppositionReportService.findOrCreateReport(opposition).isSubmitted();
|
return oppositionReportService.findOrCreateReport(opposition).isSubmitted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class RequestImprovementsForm extends Form<FinalSeminarOpposition> {
|
||||||
|
|
||||||
|
private final Model<String> feedbackToOpponentModel = new Model<>();
|
||||||
|
|
||||||
|
public RequestImprovementsForm(String id, IModel<FinalSeminarOpposition> model) {
|
||||||
|
super(id, model);
|
||||||
|
TextArea<String> feedbackToOpponentField = new TextArea<>("feedback_to_opponent", feedbackToOpponentModel);
|
||||||
|
feedbackToOpponentField.setRequired(true);
|
||||||
|
add(feedbackToOpponentField);
|
||||||
|
|
||||||
|
add(
|
||||||
|
new AjaxLink<Void>("cancel") {
|
||||||
|
@Override
|
||||||
|
public void onClick(AjaxRequestTarget target) {
|
||||||
|
requestImprovementsForm.setVisible(false);
|
||||||
|
target.add(requestImprovementsForm);
|
||||||
|
gradeForm.setVisible(true);
|
||||||
|
target.add(gradeForm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSubmit() {
|
||||||
|
Instant deadline = finalSeminarOppositionService.requestImprovements(
|
||||||
|
getModelObject(),
|
||||||
|
feedbackToOpponentModel.getObject()
|
||||||
|
);
|
||||||
|
|
||||||
|
record ImprovementFeedback(String fullName, ZonedDateTime deadline) {}
|
||||||
|
ZonedDateTime localDeadline = deadline.atZone(ZoneId.systemDefault());
|
||||||
|
success(
|
||||||
|
getString("feedback.opponent.requested.improvements", () ->
|
||||||
|
new ImprovementFeedback(getModelObject().getUser().getFullName(), localDeadline)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
requestImprovementsForm.setVisible(false);
|
||||||
|
gradeForm.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,16 +8,15 @@ gradingFeedback.Required = You need to write a motivation
|
|||||||
points.Required = Points are required
|
points.Required = Points are required
|
||||||
opponents.form.points.RangeValidator.range= Points assigned must be between ${minimum} and ${maximum}
|
opponents.form.points.RangeValidator.range= Points assigned must be between ${minimum} and ${maximum}
|
||||||
feedback.opponent.updated= Opponent ${user.fullName} feedback updated.
|
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.
|
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.\
|
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.
|
|
||||||
opposition.report= Opposition report:
|
opposition.report= Opposition report:
|
||||||
removed= Opponent ${user.fullName} successfully removed
|
removed= Opponent ${user.fullName} successfully removed
|
||||||
opposition.report.removed= Opposition report successfully removed
|
opposition.report.removed= Opposition report successfully removed
|
||||||
are.you.sure= Are you sure you want to remove this opponent report?
|
are.you.sure= Are you sure you want to remove this opponent report?
|
||||||
no.opponents= There are no opponents registered yet.
|
no.opponents= There are no opponents registered yet.
|
||||||
noOppositionReportYet= No opposition report has been submitted yet.
|
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.
|
||||||
|
@ -7,9 +7,13 @@
|
|||||||
<div wicket:id="wmc">
|
<div wicket:id="wmc">
|
||||||
<wicket:enclosure child="newReport">
|
<wicket:enclosure child="newReport">
|
||||||
<wicket:container wicket:id="newReport"/>
|
<wicket:container wicket:id="newReport"/>
|
||||||
|
<div class="alert alert-info mt-1 mb-1" wicket:id="improvements_requested">
|
||||||
|
The supervisor has requested improvements to your opposition report.
|
||||||
|
Click the link below to see detailed comments from the supervisor and to make the requested changes.
|
||||||
|
</div>
|
||||||
<span wicket:id="oppositionReportLabel"></span> <span wicket:id="noOppositionReportYet"></span>
|
<span wicket:id="oppositionReportLabel"></span> <span wicket:id="noOppositionReportYet"></span>
|
||||||
<a href="#" wicket:id="oppositionReportLink">Fill out opposition report</a>
|
<a href="#" wicket:id="oppositionReportLink">Fill out opposition report</a>
|
||||||
<span wicket:id="downloadPdfPanel"></span><br />
|
<div wicket:id="downloadPdfPanel"></div>
|
||||||
<wicket:enclosure child="downloadAttachment">
|
<wicket:enclosure child="downloadAttachment">
|
||||||
Report attachment: <span wicket:id="downloadAttachment"></span>
|
Report attachment: <span wicket:id="downloadAttachment"></span>
|
||||||
</wicket:enclosure>
|
</wicket:enclosure>
|
||||||
|
@ -70,6 +70,19 @@ public class SeminarOppositionReportPanel extends GenericPanel<FinalSeminarOppos
|
|||||||
wmc.add(getDeleteOpponentReportLink(model));
|
wmc.add(getDeleteOpponentReportLink(model));
|
||||||
|
|
||||||
wmc.add(getDeleteOppositionReportLink(model));
|
wmc.add(getDeleteOppositionReportLink(model));
|
||||||
|
|
||||||
|
wmc.add(
|
||||||
|
new WebMarkupContainer("improvements_requested") {
|
||||||
|
@Override
|
||||||
|
protected void onConfigure() {
|
||||||
|
super.onConfigure();
|
||||||
|
FinalSeminarOpposition opp = model.getObject();
|
||||||
|
boolean notGraded = opp.getGrade() == null;
|
||||||
|
boolean improvementsRequested = opp.getImprovementsRequestedAt() != null;
|
||||||
|
setVisible(isOpponentAndNotSubmitted(opp) && notGraded && improvementsRequested);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component getNewReportContainer(ViewAttachmentPanel oldReport) {
|
private Component getNewReportContainer(ViewAttachmentPanel oldReport) {
|
||||||
|
@ -5,30 +5,26 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<wicket:border>
|
<wicket:border>
|
||||||
<div class="row">
|
<div wicket:id="save"></div>
|
||||||
<div class="col-lg-8">
|
<form wicket:id="form">
|
||||||
<div wicket:id="save"></div>
|
<div wicket:id="feedbackPanel"></div>
|
||||||
<form wicket:id="form">
|
<wicket:body/>
|
||||||
<div wicket:id="feedbackPanel"></div>
|
<div wicket:id="criteria">
|
||||||
<wicket:body/>
|
<strong><span wicket:id="title"></span></strong>
|
||||||
<div wicket:id="criteria">
|
|
||||||
<strong><span wicket:id="title"></span></strong>
|
|
||||||
|
|
||||||
<p><span wicket:id="description" class="gradingCriteria"></span></p>
|
<p><span wicket:id="description" class="gradingCriteria"></span></p>
|
||||||
<textarea class="form-control mb-4" rows="8" cols="5" wicket:id="feedback"></textarea>
|
<textarea class="form-control mb-4" rows="8" cols="5" wicket:id="feedback"></textarea>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong><wicket:message key="attachment" /></strong>
|
|
||||||
<span wicket:id="viewAttachment"></span>
|
|
||||||
<a wicket:id="deleteAttachment"><span class="fa fa-times"></span></a>
|
|
||||||
<input type="file" wicket:id="uploadAttachment" class="mb-3"/>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-success btn-sm mb-3" wicket:id="submit">
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
|
<strong><wicket:message key="attachment" /></strong>
|
||||||
|
<span wicket:id="viewAttachment"></span>
|
||||||
|
<a wicket:id="deleteAttachment"><span class="fa fa-times"></span></a>
|
||||||
|
<input type="file" wicket:id="uploadAttachment" class="mb-3"/>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-success btn-sm mb-3" wicket:id="submit">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</wicket:border>
|
</wicket:border>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -25,12 +25,12 @@ import se.su.dsv.scipro.files.WicketFileUpload;
|
|||||||
import se.su.dsv.scipro.report.AttachmentReport;
|
import se.su.dsv.scipro.report.AttachmentReport;
|
||||||
import se.su.dsv.scipro.report.Criterion;
|
import se.su.dsv.scipro.report.Criterion;
|
||||||
import se.su.dsv.scipro.report.OppositionReport;
|
import se.su.dsv.scipro.report.OppositionReport;
|
||||||
import se.su.dsv.scipro.report.ReportService;
|
import se.su.dsv.scipro.report.OppositionReportService;
|
||||||
import se.su.dsv.scipro.repository.panels.ViewAttachmentPanel;
|
import se.su.dsv.scipro.repository.panels.ViewAttachmentPanel;
|
||||||
import se.su.dsv.scipro.system.Language;
|
import se.su.dsv.scipro.system.Language;
|
||||||
import se.su.dsv.scipro.util.JavascriptEventConfirmation;
|
import se.su.dsv.scipro.util.JavascriptEventConfirmation;
|
||||||
|
|
||||||
public class FillOutReportPanel<T extends OppositionReport> extends Border {
|
public class FillOutReportPanel extends Border {
|
||||||
|
|
||||||
public static final String FORM = "form";
|
public static final String FORM = "form";
|
||||||
public static final String GRADING_CRITERIA = "criteria";
|
public static final String GRADING_CRITERIA = "criteria";
|
||||||
@ -42,20 +42,20 @@ public class FillOutReportPanel<T extends OppositionReport> extends Border {
|
|||||||
public static final String FEEDBACK_PANEL = "feedbackPanel";
|
public static final String FEEDBACK_PANEL = "feedbackPanel";
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ReportService reportService;
|
private OppositionReportService reportService;
|
||||||
|
|
||||||
public FillOutReportPanel(String id, final IModel<T> model) {
|
public FillOutReportPanel(String id, final IModel<OppositionReport> model) {
|
||||||
super(id, model);
|
super(id, model);
|
||||||
ReportForm form = new ReportForm(FORM, model);
|
ReportForm form = new ReportForm(FORM, model);
|
||||||
addToBorder(new ScrollingSaveButtonPanel(SAVE, form));
|
addToBorder(new ScrollingSaveButtonPanel(SAVE, form));
|
||||||
addToBorder(form);
|
addToBorder(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReportForm extends StatelessForm<T> {
|
private class ReportForm extends StatelessForm<OppositionReport> {
|
||||||
|
|
||||||
private final FileUploadField attachment;
|
private final FileUploadField attachment;
|
||||||
|
|
||||||
public ReportForm(String id, final IModel<T> model) {
|
public ReportForm(String id, final IModel<OppositionReport> model) {
|
||||||
super(id, model);
|
super(id, model);
|
||||||
add(new ComponentFeedbackPanel(FEEDBACK_PANEL, this));
|
add(new ComponentFeedbackPanel(FEEDBACK_PANEL, this));
|
||||||
IModel<Language> language = model.map(OppositionReport::getLanguage);
|
IModel<Language> language = model.map(OppositionReport::getLanguage);
|
||||||
@ -139,9 +139,9 @@ public class FillOutReportPanel<T extends OppositionReport> extends Border {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DeleteAttachmentLink extends Link<T> {
|
private class DeleteAttachmentLink extends Link<OppositionReport> {
|
||||||
|
|
||||||
public DeleteAttachmentLink(String id, IModel<T> model) {
|
public DeleteAttachmentLink(String id, IModel<OppositionReport> model) {
|
||||||
super(id, model);
|
super(id, model);
|
||||||
add(new JavascriptEventConfirmation("click", new ResourceModel("delete.attachment")));
|
add(new JavascriptEventConfirmation("click", new ResourceModel("delete.attachment")));
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ import org.apache.wicket.util.string.StringValueConversionException;
|
|||||||
import se.su.dsv.scipro.activityplan.ProjectActivityPlanPage;
|
import se.su.dsv.scipro.activityplan.ProjectActivityPlanPage;
|
||||||
import se.su.dsv.scipro.activityplan.SupervisorActivityPlanPage;
|
import se.su.dsv.scipro.activityplan.SupervisorActivityPlanPage;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
||||||
|
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||||
|
import se.su.dsv.scipro.finalseminar.OppositionReportPage;
|
||||||
import se.su.dsv.scipro.finalseminar.ProjectFinalSeminarDetailsPage;
|
import se.su.dsv.scipro.finalseminar.ProjectFinalSeminarDetailsPage;
|
||||||
import se.su.dsv.scipro.finalseminar.ProjectFinalSeminarPage;
|
import se.su.dsv.scipro.finalseminar.ProjectFinalSeminarPage;
|
||||||
import se.su.dsv.scipro.finalseminar.ProjectOppositionPage;
|
import se.su.dsv.scipro.finalseminar.ProjectOppositionPage;
|
||||||
@ -217,6 +219,19 @@ public class NotificationLandingPage extends WebPage {
|
|||||||
} else if (
|
} else if (
|
||||||
seminar.getActiveParticipants().contains(currentUser) || seminar.getOpponents().contains(currentUser)
|
seminar.getActiveParticipants().contains(currentUser) || seminar.getOpponents().contains(currentUser)
|
||||||
) {
|
) {
|
||||||
|
if (seminarEvent.getEvent() == SeminarEvent.Event.OPPOSITION_REPORT_IMPROVEMENTS_REQUESTED) {
|
||||||
|
Optional<FinalSeminarOpposition> opposition = seminar
|
||||||
|
.getOppositions()
|
||||||
|
.stream()
|
||||||
|
.filter(op -> op.getUser().equals(currentUser))
|
||||||
|
.findFirst();
|
||||||
|
if (opposition.isPresent()) {
|
||||||
|
final PageParameters oppPP = new PageParameters();
|
||||||
|
oppPP.set("oid", opposition.get().getId());
|
||||||
|
setResponsePage(OppositionReportPage.class, oppPP);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
setResponsePage(ProjectFinalSeminarDetailsPage.class, pp);
|
setResponsePage(ProjectFinalSeminarDetailsPage.class, pp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,8 @@ SeminarEvent.OPPOSITION_REPORT_UPLOADED = Opposition report created.
|
|||||||
SeminarEvent.THESIS_DELETED = Final seminar thesis deleted.
|
SeminarEvent.THESIS_DELETED = Final seminar thesis deleted.
|
||||||
SeminarEvent.THESIS_UPLOAD_REMIND = Authors reminded to upload final seminar thesis.
|
SeminarEvent.THESIS_UPLOAD_REMIND = Authors reminded to upload final seminar thesis.
|
||||||
SeminarEvent.CANCELLED = Final seminar cancelled.
|
SeminarEvent.CANCELLED = Final seminar cancelled.
|
||||||
|
SeminarEvent.OPPOSITION_REPORT_SUBMITTED = Opposition report submitted.
|
||||||
|
SeminarEvent.OPPOSITION_REPORT_IMPROVEMENTS_REQUESTED = Opposition report improvements requested.
|
||||||
|
|
||||||
IdeaEvent.STATUS_CHANGE = Idea status changed.
|
IdeaEvent.STATUS_CHANGE = Idea status changed.
|
||||||
IdeaEvent.PARTNER_ACCEPT = Partner (author) accepted partnering idea.
|
IdeaEvent.PARTNER_ACCEPT = Partner (author) accepted partnering idea.
|
||||||
|
@ -321,9 +321,6 @@ public abstract class SciProTest {
|
|||||||
@Mock
|
@Mock
|
||||||
protected FinalSeminarUploadController finalSeminarUploadController;
|
protected FinalSeminarUploadController finalSeminarUploadController;
|
||||||
|
|
||||||
@Mock
|
|
||||||
protected FinalSeminarOppositionRepo finalSeminarOppositionRepo;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
protected PlagiarismControl plagiarismControl;
|
protected PlagiarismControl plagiarismControl;
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.ArgumentMatchers;
|
import org.mockito.ArgumentMatchers;
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import se.su.dsv.scipro.SciProTest;
|
import se.su.dsv.scipro.SciProTest;
|
||||||
import se.su.dsv.scipro.file.FileDescription;
|
import se.su.dsv.scipro.file.FileDescription;
|
||||||
@ -28,7 +27,6 @@ import se.su.dsv.scipro.project.pages.ProjectDetailsPage;
|
|||||||
import se.su.dsv.scipro.report.GradingCriterionPointTemplate;
|
import se.su.dsv.scipro.report.GradingCriterionPointTemplate;
|
||||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||||
import se.su.dsv.scipro.report.OppositionReport;
|
import se.su.dsv.scipro.report.OppositionReport;
|
||||||
import se.su.dsv.scipro.report.ReportService;
|
|
||||||
import se.su.dsv.scipro.system.DegreeType;
|
import se.su.dsv.scipro.system.DegreeType;
|
||||||
import se.su.dsv.scipro.system.ProjectType;
|
import se.su.dsv.scipro.system.ProjectType;
|
||||||
import se.su.dsv.scipro.system.User;
|
import se.su.dsv.scipro.system.User;
|
||||||
@ -41,9 +39,6 @@ public class OppositionReportPageTest extends SciProTest {
|
|||||||
public static final String CRITERION_DESCRIPTION = "For 1 point: Be nice to your supervisor";
|
public static final String CRITERION_DESCRIPTION = "For 1 point: Be nice to your supervisor";
|
||||||
public static final String CRITERTION_TITLE = "U1 Sammanfattning";
|
public static final String CRITERTION_TITLE = "U1 Sammanfattning";
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ReportService reportService;
|
|
||||||
|
|
||||||
private FinalSeminarOpposition finalSeminarOpposition;
|
private FinalSeminarOpposition finalSeminarOpposition;
|
||||||
private User user;
|
private User user;
|
||||||
private ProjectType bachelor;
|
private ProjectType bachelor;
|
||||||
@ -76,10 +71,9 @@ public class OppositionReportPageTest extends SciProTest {
|
|||||||
Mockito.when(finalSeminarService.findByProject(opponentsProject)).thenReturn(finalSeminar);
|
Mockito.when(finalSeminarService.findByProject(opponentsProject)).thenReturn(finalSeminar);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mockReport(ProjectType bachelor) {
|
private OppositionReport mockReport(ProjectType bachelor) {
|
||||||
GradingReportTemplate reportTemplate = createTemplate(bachelor);
|
GradingReportTemplate reportTemplate = createTemplate(bachelor);
|
||||||
OppositionReport oppositionReport = reportTemplate.createOppositionReport(finalSeminarOpposition);
|
return reportTemplate.createOppositionReport(finalSeminarOpposition);
|
||||||
Mockito.when(oppositionReportService.findOrCreateReport(finalSeminarOpposition)).thenReturn(oppositionReport);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -104,14 +98,16 @@ public class OppositionReportPageTest extends SciProTest {
|
|||||||
public void disable_form_if_opposition_does_not_belong_to_logged_in_user() {
|
public void disable_form_if_opposition_does_not_belong_to_logged_in_user() {
|
||||||
mockReport(bachelor);
|
mockReport(bachelor);
|
||||||
long oppositionId = 4L;
|
long oppositionId = 4L;
|
||||||
Mockito.when(finalSeminarOppositionRepo.findOne(oppositionId)).thenReturn(finalSeminarOpposition);
|
Mockito.when(finalSeminarOppositionService.getOpposition(oppositionId)).thenReturn(
|
||||||
|
new Opposition(user, mockReport(bachelor), Optional.empty())
|
||||||
|
);
|
||||||
startPage(oppositionId);
|
startPage(oppositionId);
|
||||||
tester.assertDisabled(FILL_OUT_REPORT);
|
tester.assertDisabled(FILL_OUT_REPORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void redirect_if_no_opposition_is_found_from_id() {
|
public void redirect_if_no_opposition_is_found_from_id() {
|
||||||
Mockito.when(finalSeminarOppositionRepo.findOne(ArgumentMatchers.anyLong())).thenReturn(null);
|
Mockito.when(finalSeminarOppositionService.getOpposition(ArgumentMatchers.anyLong())).thenReturn(null);
|
||||||
startPage(1L);
|
startPage(1L);
|
||||||
tester.assertRenderedPage(ProjectDetailsPage.class);
|
tester.assertRenderedPage(ProjectDetailsPage.class);
|
||||||
}
|
}
|
||||||
@ -138,7 +134,7 @@ public class OppositionReportPageTest extends SciProTest {
|
|||||||
formTester.submit();
|
formTester.submit();
|
||||||
|
|
||||||
ArgumentCaptor<OppositionReport> captor = ArgumentCaptor.forClass(OppositionReport.class);
|
ArgumentCaptor<OppositionReport> captor = ArgumentCaptor.forClass(OppositionReport.class);
|
||||||
Mockito.verify(reportService).save(captor.capture(), eq(Optional.empty()));
|
Mockito.verify(oppositionReportService).save(captor.capture(), eq(Optional.empty()));
|
||||||
|
|
||||||
Assertions.assertEquals(summary, captor.getValue().getThesisSummary());
|
Assertions.assertEquals(summary, captor.getValue().getThesisSummary());
|
||||||
}
|
}
|
||||||
@ -156,7 +152,9 @@ public class OppositionReportPageTest extends SciProTest {
|
|||||||
private void startOppositionPage() {
|
private void startOppositionPage() {
|
||||||
long oppositionId = 4L;
|
long oppositionId = 4L;
|
||||||
setLoggedInAs(user);
|
setLoggedInAs(user);
|
||||||
Mockito.when(finalSeminarOppositionRepo.findOne(oppositionId)).thenReturn(finalSeminarOpposition);
|
Mockito.when(finalSeminarOppositionService.getOpposition(oppositionId)).thenReturn(
|
||||||
|
new Opposition(user, mockReport(bachelor), Optional.empty())
|
||||||
|
);
|
||||||
startPage(oppositionId);
|
startPage(oppositionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,15 @@ public class SeminarOppositionPanelTest extends SciProTest {
|
|||||||
finalSeminar.setProject(project);
|
finalSeminar.setProject(project);
|
||||||
|
|
||||||
setLoggedInAs(supervisorUser);
|
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
|
@Test
|
||||||
|
@ -66,6 +66,14 @@ public class SeminarPanelTest extends SciProTest {
|
|||||||
Mockito.when(plagiarismControl.getStatus(any(FileDescription.class))).thenReturn(
|
Mockito.when(plagiarismControl.getStatus(any(FileDescription.class))).thenReturn(
|
||||||
new PlagiarismControl.Status.NotSubmitted()
|
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() {
|
private void addCoSupervisorToProject() {
|
||||||
|
@ -35,7 +35,7 @@ public class FillOutReportPanelTest extends SciProTest {
|
|||||||
private FillOutReportPanel panel;
|
private FillOutReportPanel panel;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ReportService reportService;
|
private OppositionReportService reportService;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
@ -197,6 +197,6 @@ public class FillOutReportPanelTest extends SciProTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void startPanel() {
|
private void startPanel() {
|
||||||
panel = tester.startComponentInPage(new FillOutReportPanel<>("id", Model.of(oppositionReport)));
|
panel = tester.startComponentInPage(new FillOutReportPanel("id", Model.of(oppositionReport)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ import org.springframework.context.annotation.Import;
|
|||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import se.su.dsv.scipro.file.FileService;
|
import se.su.dsv.scipro.file.FileService;
|
||||||
|
import se.su.dsv.scipro.finalseminar.ExpireUnfulfilledOppositionImprovementsWorker;
|
||||||
|
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionServiceImpl;
|
||||||
import se.su.dsv.scipro.finalseminar.FinalSeminarService;
|
import se.su.dsv.scipro.finalseminar.FinalSeminarService;
|
||||||
import se.su.dsv.scipro.firstmeeting.FirstMeetingReminderWorker;
|
import se.su.dsv.scipro.firstmeeting.FirstMeetingReminderWorker;
|
||||||
import se.su.dsv.scipro.firstmeeting.FirstMeetingService;
|
import se.su.dsv.scipro.firstmeeting.FirstMeetingService;
|
||||||
@ -150,6 +152,14 @@ public class WorkerConfig {
|
|||||||
return new SpringManagedWorkerTransactions(platformTransactionManager);
|
return new SpringManagedWorkerTransactions(platformTransactionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ExpireUnfulfilledOppositionImprovementsWorker.Schedule expireUnfulfilledOppositionImprovementsWorkerSchedule(
|
||||||
|
Scheduler scheduler,
|
||||||
|
Provider<ExpireUnfulfilledOppositionImprovementsWorker> worker
|
||||||
|
) {
|
||||||
|
return new ExpireUnfulfilledOppositionImprovementsWorker.Schedule(scheduler, worker);
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public static class Workers {
|
public static class Workers {
|
||||||
|
|
||||||
@ -279,5 +289,12 @@ public class WorkerConfig {
|
|||||||
public ExpiredRequestWorker expiredRequestWorker() {
|
public ExpiredRequestWorker expiredRequestWorker() {
|
||||||
return new ExpiredRequestWorker();
|
return new ExpiredRequestWorker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ExpireUnfulfilledOppositionImprovementsWorker expireUnfulfilledOppositionImprovementsWorker(
|
||||||
|
FinalSeminarOppositionServiceImpl finalSeminarOppositionService
|
||||||
|
) {
|
||||||
|
return new ExpireUnfulfilledOppositionImprovementsWorker(finalSeminarOppositionService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user