commit
d6961627dc
.gitignoreDockerfilecheckstyle.xml
core
pom.xml
pom.xmlsrc
main
java/se/su/dsv/scipro
CoreConfig.java
finalseminar
AbstractOppositionEvent.javaExpireUnfulfilledOppositionImprovementsWorker.javaFinalSeminarOpposition.javaFinalSeminarOppositionGrading.javaFinalSeminarOppositionRepo.javaFinalSeminarOppositionRepoImpl.javaFinalSeminarOppositionService.javaFinalSeminarOppositionServiceImpl.javaFinalSeminarServiceImpl.javaFinalSeminarSettings.javaOpposition.javaOppositionCriteria.javaOppositionReportImprovementsRequestedEvent.javaPointNotValidException.java
forum
match
misc
notifications
report
resources/db/migration
V5__final_seminar_opposition_request_improvements.sqlV6__final_seminar_work_days_to_fix_opposition_report.sql
xsd
test/java/se/su/dsv/scipro
test-data
pom.xml
src/main
java/se/su/dsv/scipro/testdata
BaseData.javaDataInitializer.javaFactory.javaTestDataConfiguration.javaTestDataPopulator.javapackage-info.java
populators
resources/META-INF/services
view/src
main
java/se/su/dsv/scipro
applicationperiod
datatables
finalseminar
AdminFinalSeminarSettingsPage.htmlAdminFinalSeminarSettingsPage.javaDownloadPdfReportPanel.javaOppositionReportPage.htmlOppositionReportPage.javaSeminarOppositionPanel.htmlSeminarOppositionPanel.javaSeminarOppositionPanel.propertiesSeminarOppositionReportPanel.htmlSeminarOppositionReportPanel.java
forum/panels/threaded
grading
group
io
notifications/pages
projectpartner
wicket-package.utf8.propertieswebapp/css
test/java/se/su/dsv/scipro
war
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,4 +25,5 @@ fitnesse/target/
|
||||
daisy-integration/target/
|
||||
war/target/
|
||||
api/target/
|
||||
test-data/target/
|
||||
node_modules/
|
||||
|
@ -12,16 +12,19 @@ COPY core/pom.xml core/pom.xml
|
||||
COPY view/pom.xml view/pom.xml
|
||||
COPY war/pom.xml war/pom.xml
|
||||
COPY daisy-integration/pom.xml daisy-integration/pom.xml
|
||||
COPY test-data/pom.xml test-data/pom.xml
|
||||
|
||||
COPY api/src/ api/src/
|
||||
COPY core/src/ core/src/
|
||||
COPY view/src/ view/src/
|
||||
COPY war/src/ war/src/
|
||||
COPY daisy-integration/src/ daisy-integration/src/
|
||||
COPY test-data/src/ test-data/src/
|
||||
|
||||
RUN ./mvnw package \
|
||||
--define skipTests \
|
||||
--activate-profiles branch,DEV \
|
||||
--define checkstyle.skip=true \
|
||||
--define skip.npm \
|
||||
--define skip.installnodenpm
|
||||
|
||||
|
7
checkstyle.xml
Normal file
7
checkstyle.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
<module name="Checker">
|
||||
</module>
|
20
core/pom.xml
20
core/pom.xml
@ -23,32 +23,14 @@
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-client</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.glassfish.hk2.external</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.media</groupId>
|
||||
<artifactId>jersey-media-jaxb</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.glassfish.hk2.external</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.media</groupId>
|
||||
<artifactId>jersey-media-json-jackson</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.glassfish.hk2.external</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.inject</groupId>
|
||||
@ -135,12 +117,10 @@
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>4.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
<version>4.0.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -31,6 +31,7 @@ import se.su.dsv.scipro.finalseminar.AuthorRepository;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationRepository;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationServiceImpl;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarCreationSubscribers;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionGrading;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionRepo;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionServiceImpl;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarRepository;
|
||||
@ -153,8 +154,8 @@ import se.su.dsv.scipro.report.GradingReportServiceImpl;
|
||||
import se.su.dsv.scipro.report.GradingReportTemplateRepo;
|
||||
import se.su.dsv.scipro.report.GradingReportTemplateRepoImpl;
|
||||
import se.su.dsv.scipro.report.OppositionReportRepo;
|
||||
import se.su.dsv.scipro.report.OppositionReportService;
|
||||
import se.su.dsv.scipro.report.OppositionReportServiceImpl;
|
||||
import se.su.dsv.scipro.report.ReportServiceImpl;
|
||||
import se.su.dsv.scipro.report.SupervisorGradingReportRepository;
|
||||
import se.su.dsv.scipro.reviewing.DecisionRepository;
|
||||
import se.su.dsv.scipro.reviewing.FinalSeminarApprovalService;
|
||||
@ -348,14 +349,16 @@ public class CoreConfig {
|
||||
ForumPostReadStateRepository readStateRepository,
|
||||
AbstractThreadRepository threadRepository,
|
||||
FileService fileService,
|
||||
EventBus eventBus
|
||||
EventBus eventBus,
|
||||
CurrentUser currentUser
|
||||
) {
|
||||
return new BasicForumServiceImpl(
|
||||
forumPostRepository,
|
||||
readStateRepository,
|
||||
threadRepository,
|
||||
fileService,
|
||||
eventBus
|
||||
eventBus,
|
||||
currentUser
|
||||
);
|
||||
}
|
||||
|
||||
@ -430,8 +433,26 @@ public class CoreConfig {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FinalSeminarOppositionServiceImpl finalSeminarOppositionService(Provider<EntityManager> em) {
|
||||
return new FinalSeminarOppositionServiceImpl(em);
|
||||
public FinalSeminarOppositionServiceImpl finalSeminarOppositionService(
|
||||
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
|
||||
@ -669,13 +690,15 @@ public class CoreConfig {
|
||||
OppositionReportRepo oppositionReportRepository,
|
||||
GradingReportTemplateRepo gradingReportTemplateRepository,
|
||||
FileService fileService,
|
||||
FinalSeminarOppositionRepo finalSeminarOppositionRepository
|
||||
FinalSeminarOppositionRepo finalSeminarOppositionRepository,
|
||||
EventBus eventBus
|
||||
) {
|
||||
return new OppositionReportServiceImpl(
|
||||
oppositionReportRepository,
|
||||
gradingReportTemplateRepository,
|
||||
fileService,
|
||||
finalSeminarOppositionRepository
|
||||
finalSeminarOppositionRepository,
|
||||
eventBus
|
||||
);
|
||||
}
|
||||
|
||||
@ -855,11 +878,6 @@ public class CoreConfig {
|
||||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReportServiceImpl reportService(Provider<EntityManager> em, FileService fileService) {
|
||||
return new ReportServiceImpl(em, fileService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResearchAreaServiceImpl researchAreaService(Provider<EntityManager> em) {
|
||||
return new ResearchAreaServiceImpl(em);
|
||||
@ -1017,11 +1035,6 @@ public class CoreConfig {
|
||||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataInitializer dataInitializer() {
|
||||
return new DataInitializer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FinalSeminarActivityHandler finalSeminarActivityHandler(
|
||||
ActivityPlanFacade activityPlanFacade,
|
||||
|
@ -1,5 +1,6 @@
|
||||
package se.su.dsv.scipro.finalseminar;
|
||||
|
||||
import java.util.Objects;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
@ -26,4 +27,17 @@ class AbstractOppositionEvent {
|
||||
public FinalSeminarOpposition getOpposition() {
|
||||
return opposition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
AbstractOppositionEvent that = (AbstractOppositionEvent) o;
|
||||
return Objects.equals(opposition, that.opposition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(opposition);
|
||||
}
|
||||
}
|
||||
|
32
core/src/main/java/se/su/dsv/scipro/finalseminar/ExpireUnfulfilledOppositionImprovementsWorker.java
Normal file
32
core/src/main/java/se/su/dsv/scipro/finalseminar/ExpireUnfulfilledOppositionImprovementsWorker.java
Normal file
@ -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.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import se.su.dsv.scipro.file.FileReference;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
@ -31,6 +32,14 @@ public class FinalSeminarOpposition extends FinalSeminarParticipation {
|
||||
@Column(name = "feedback", length = FEEDBACK_LENGTH)
|
||||
private String feedback;
|
||||
|
||||
@Basic
|
||||
@Column(name = "improvements_requested_at")
|
||||
private Instant improvementsRequestedAt;
|
||||
|
||||
@Basic
|
||||
@Column(name = "supervisor_improvements_comment")
|
||||
private String supervisorCommentForImprovements;
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// JPA-mappings of foreign keys in this table (final_seminar_opposition) referencing
|
||||
// other tables.
|
||||
@ -92,6 +101,22 @@ public class FinalSeminarOpposition extends FinalSeminarParticipation {
|
||||
this.oppositionReport = oppositionReport;
|
||||
}
|
||||
|
||||
public Instant getImprovementsRequestedAt() {
|
||||
return improvementsRequestedAt;
|
||||
}
|
||||
|
||||
public void setImprovementsRequestedAt(Instant improvementsRequestedAt) {
|
||||
this.improvementsRequestedAt = improvementsRequestedAt;
|
||||
}
|
||||
|
||||
public String getSupervisorCommentForImprovements() {
|
||||
return supervisorCommentForImprovements;
|
||||
}
|
||||
|
||||
public void setSupervisorCommentForImprovements(String supervisorCommentsForImprovements) {
|
||||
this.supervisorCommentForImprovements = supervisorCommentsForImprovements;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// Methods Common To All Objects
|
||||
// ----------------------------------------------------------------------------------
|
||||
|
@ -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
|
||||
extends JpaRepository<FinalSeminarOpposition, Long>, QueryDslPredicateExecutor<FinalSeminarOpposition> {
|
||||
List<FinalSeminarOpposition> findByOpposingUserAndType(User user, ProjectType projectType);
|
||||
|
||||
Collection<FinalSeminarOpposition> findUnfulfilledOppositionImprovements();
|
||||
}
|
||||
|
@ -24,4 +24,13 @@ public class FinalSeminarOppositionRepoImpl
|
||||
.where(QFinalSeminarOpposition.finalSeminarOpposition.project.projectType.eq(projectType))
|
||||
.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;
|
||||
|
||||
import java.time.Instant;
|
||||
import se.su.dsv.scipro.system.GenericService;
|
||||
|
||||
public interface FinalSeminarOppositionService extends GenericService<FinalSeminarOpposition, Long> {
|
||||
@Override
|
||||
void delete(Long aLong);
|
||||
public interface FinalSeminarOppositionService {
|
||||
OppositionCriteria getCriteriaForOpposition(FinalSeminarOpposition opposition);
|
||||
|
||||
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;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
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;
|
||||
|
||||
public class FinalSeminarOppositionServiceImpl
|
||||
extends AbstractServiceImpl<FinalSeminarOpposition, Long>
|
||||
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
|
||||
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);
|
||||
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.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.SubQueryExpressionImpl;
|
||||
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.Provider;
|
||||
import jakarta.persistence.EntityManager;
|
||||
@ -542,19 +545,22 @@ public class FinalSeminarServiceImpl extends AbstractServiceImpl<FinalSeminar, L
|
||||
|
||||
private BooleanExpression unfinishedSeminars(Date after, Date before) {
|
||||
QFinalSeminar seminar = QFinalSeminar.finalSeminar;
|
||||
if (after == null && before == null) {
|
||||
return seminar.oppositions
|
||||
QFinalSeminarOpposition opposition = QFinalSeminarOpposition.finalSeminarOpposition;
|
||||
BooleanExpression ungradedParticipant = Expressions.anyOf(
|
||||
seminar.oppositions
|
||||
.any()
|
||||
.grade.isNull()
|
||||
.or(seminar.activeParticipations.any().grade.isNull().or(seminar.respondents.any().grade.isNull()));
|
||||
.id.in(
|
||||
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 {
|
||||
return seminar.startDate
|
||||
.between(after, before)
|
||||
.andAnyOf(
|
||||
seminar.oppositions.any().grade.isNull(),
|
||||
seminar.activeParticipations.any().grade.isNull(),
|
||||
seminar.respondents.any().grade.isNull()
|
||||
);
|
||||
return seminar.startDate.between(after, before).and(ungradedParticipant);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,9 @@ public class FinalSeminarSettings extends DomainObject {
|
||||
@Column(name = "days_ahead_to_upload_thesis", nullable = false)
|
||||
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)
|
||||
private boolean thesisMustBePDF = false;
|
||||
|
||||
@ -113,6 +116,17 @@ public class FinalSeminarSettings extends DomainObject {
|
||||
this.oppositionPriorityDays = oppositionPriorityDays;
|
||||
}
|
||||
|
||||
public int getWorkDaysToFixRequestedImprovementsToOppositionReport() {
|
||||
return workDaysToFixRequestedImprovementsToOppositionReport;
|
||||
}
|
||||
|
||||
public void setWorkDaysToFixRequestedImprovementsToOppositionReport(
|
||||
int workDaysToFixRequestedImprovementsToOppositionReport
|
||||
) {
|
||||
this.workDaysToFixRequestedImprovementsToOppositionReport =
|
||||
workDaysToFixRequestedImprovementsToOppositionReport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
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) {}
|
||||
}
|
9
core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportImprovementsRequestedEvent.java
Normal file
9
core/src/main/java/se/su/dsv/scipro/finalseminar/OppositionReportImprovementsRequestedEvent.java
Normal file
@ -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 + '}';
|
||||
}
|
||||
}
|
@ -23,4 +23,12 @@ public interface BasicForumService extends Serializable {
|
||||
ForumThread createThread(String subject);
|
||||
|
||||
long countUnreadThreads(List<ForumThread> forumThreadList, User user);
|
||||
|
||||
ForumPost getLastPost(ForumThread forumThread);
|
||||
|
||||
boolean hasAttachments(ForumThread forumThread);
|
||||
|
||||
boolean canDelete(ForumPost forumPost);
|
||||
|
||||
void deletePost(ForumPost post);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import se.su.dsv.scipro.file.FileService;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPostReadState;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumThread;
|
||||
import se.su.dsv.scipro.system.CurrentUser;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
public class BasicForumServiceImpl implements BasicForumService {
|
||||
@ -19,6 +20,7 @@ public class BasicForumServiceImpl implements BasicForumService {
|
||||
private final ForumPostReadStateRepository readStateRepository;
|
||||
private final FileService fileService;
|
||||
private final EventBus eventBus;
|
||||
private final CurrentUser currentUserProvider;
|
||||
|
||||
@Inject
|
||||
public BasicForumServiceImpl(
|
||||
@ -26,13 +28,15 @@ public class BasicForumServiceImpl implements BasicForumService {
|
||||
final ForumPostReadStateRepository readStateRepository,
|
||||
AbstractThreadRepository threadRepository,
|
||||
final FileService fileService,
|
||||
final EventBus eventBus
|
||||
final EventBus eventBus,
|
||||
final CurrentUser currentUserProvider
|
||||
) {
|
||||
this.postRepository = postRepository;
|
||||
this.readStateRepository = readStateRepository;
|
||||
this.threadRepository = threadRepository;
|
||||
this.fileService = fileService;
|
||||
this.eventBus = eventBus;
|
||||
this.currentUserProvider = currentUserProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,7 +70,7 @@ public class BasicForumServiceImpl implements BasicForumService {
|
||||
|
||||
@Override
|
||||
public boolean isThreadRead(User user, ForumThread forumThread) {
|
||||
for (ForumPost post : forumThread.getPosts()) {
|
||||
for (ForumPost post : getPosts(forumThread)) {
|
||||
if (!getReadState(user, post).isRead()) {
|
||||
return false;
|
||||
}
|
||||
@ -133,4 +137,56 @@ public class BasicForumServiceImpl implements BasicForumService {
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForumPost getLastPost(ForumThread forumThread) {
|
||||
return Collections.max(
|
||||
getPosts(forumThread),
|
||||
Comparator.comparing(ForumPost::getDateCreated).thenComparing(ForumPost::getId)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAttachments(ForumThread forumThread) {
|
||||
for (ForumPost post : getPosts(forumThread)) {
|
||||
if (!post.getAttachments().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(ForumPost forumPost) {
|
||||
ForumPost initialPost = forumPost.getForumThread().getPosts().get(0);
|
||||
if (forumPost.equals(initialPost)) {
|
||||
// The initial post in a thread can never be deleted
|
||||
return false;
|
||||
}
|
||||
|
||||
User user = currentUserProvider.get();
|
||||
// Current user can be null meaning the call came from the system
|
||||
if (user == null) {
|
||||
// Allow the system to delete any post
|
||||
return true;
|
||||
}
|
||||
return Objects.equals(forumPost.getPostedBy(), user);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deletePost(ForumPost post) {
|
||||
if (!canDelete(post)) {
|
||||
throw new PostCantBeDeletedException();
|
||||
}
|
||||
post.setDeleted(true);
|
||||
postRepository.save(post);
|
||||
}
|
||||
|
||||
private static final class PostCantBeDeletedException extends IllegalArgumentException {
|
||||
|
||||
public PostCantBeDeletedException() {
|
||||
super("User is not allowed to delete post");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,13 +116,4 @@ public class ForumThread extends LazyDeletableDomainObject {
|
||||
public User getCreatedBy() {
|
||||
return getPosts().get(0).getPostedBy();
|
||||
}
|
||||
|
||||
public boolean hasAttachments() {
|
||||
for (ForumPost post : posts) {
|
||||
if (!post.getAttachments().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -176,18 +176,16 @@ public class ApplicationPeriod extends DomainObject {
|
||||
return Collections.unmodifiableSet(answerSet);
|
||||
}
|
||||
|
||||
public void setProjectTypes(Iterable<ProjectType> projectTypes) {
|
||||
this.projectTypes.clear();
|
||||
public void setProjectTypes(Set<ProjectType> projectTypes) {
|
||||
this.projectTypes.removeIf(appt -> !projectTypes.contains(appt.getProjectType()));
|
||||
for (ProjectType pt : projectTypes) {
|
||||
this.projectTypes.add(new ApplicationPeriodProjectType(this, pt));
|
||||
if (this.projectTypes.stream().noneMatch(appt -> appt.getProjectType().equals(pt))) {
|
||||
addProjectType(pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addProjectType(ProjectType projectType) {
|
||||
this.projectTypes.add(new ApplicationPeriodProjectType(this, projectType));
|
||||
}
|
||||
|
||||
public void removeProjectType(ProjectType projectType) {
|
||||
this.projectTypes.removeIf(next -> next.getProjectType().equals(projectType));
|
||||
}
|
||||
}
|
||||
|
@ -235,10 +235,8 @@ public class IdeaServiceImpl extends AbstractServiceImpl<Idea, Long> implements
|
||||
if (authorParticipatingOnActiveIdea(coAuthor, ap)) {
|
||||
return new Pair<>(Boolean.FALSE, PARTNER_ALREADY_PARTICIPATING_ERROR);
|
||||
}
|
||||
if (
|
||||
coAuthor.getDegreeType() != ProjectType.UNKNOWN &&
|
||||
coAuthor.getDegreeType() != idea.getProjectType().getDegreeType()
|
||||
) {
|
||||
List<ProjectType> typesForCoAuthor = applicationPeriodService.getTypesForStudent(ap, coAuthor);
|
||||
if (!typesForCoAuthor.contains(idea.getProjectType())) {
|
||||
return new Pair<>(Boolean.FALSE, WRONG_LEVEL_FOR_YOUR_PARTNER);
|
||||
}
|
||||
if (!projectService.getActiveProjectsByUserAndProjectType(coAuthor, idea.getProjectType()).isEmpty()) {
|
||||
|
@ -1,6 +1,9 @@
|
||||
package se.su.dsv.scipro.misc;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
public interface DaysService {
|
||||
@ -9,4 +12,11 @@ public interface DaysService {
|
||||
int workDaysBetween(Date startDate, Date endDate);
|
||||
LocalDate workDaysAhead(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();
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ import se.su.dsv.scipro.finalseminar.FinalSeminarCreatedEvent;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarDeletedEvent;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarThesisDeletedEvent;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarThesisUploadedEvent;
|
||||
import se.su.dsv.scipro.finalseminar.OppositionApprovedEvent;
|
||||
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.notifications.dataobject.NotificationSource;
|
||||
import se.su.dsv.scipro.notifications.dataobject.PeerEvent;
|
||||
@ -23,6 +25,7 @@ import se.su.dsv.scipro.project.ProjectActivatedEvent;
|
||||
import se.su.dsv.scipro.project.ProjectCompletedEvent;
|
||||
import se.su.dsv.scipro.project.ProjectDeactivatedEvent;
|
||||
import se.su.dsv.scipro.project.ReviewerAssignedEvent;
|
||||
import se.su.dsv.scipro.report.OppositionReportSubmittedEvent;
|
||||
|
||||
@Singleton
|
||||
public class Notifications {
|
||||
@ -168,6 +171,40 @@ public class Notifications {
|
||||
);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void oppositionApproved(OppositionApprovedEvent event) {
|
||||
notificationController.notifySeminar(
|
||||
event.getFinalSeminar(),
|
||||
SeminarEvent.Event.OPPOSITION_APPROVED,
|
||||
new NotificationSource()
|
||||
);
|
||||
}
|
||||
|
||||
@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
|
||||
public void reviewersChanged(ReviewerAssignedEvent event) {
|
||||
notificationController.notifyProject(
|
||||
|
@ -26,6 +26,9 @@ public class SeminarEvent extends NotificationEvent {
|
||||
THESIS_DELETED,
|
||||
THESIS_UPLOAD_REMIND,
|
||||
CANCELLED,
|
||||
OPPOSITION_REPORT_SUBMITTED,
|
||||
OPPOSITION_REPORT_IMPROVEMENTS_REQUESTED,
|
||||
OPPOSITION_APPROVED,
|
||||
}
|
||||
|
||||
@Basic
|
||||
|
@ -140,6 +140,14 @@ 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.
|
||||
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.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.OPPOSITION_APPROVED.title = Opposition approved
|
||||
FINAL_SEMINAR.OPPOSITION_APPROVED.body = Your opposition report has been approved by the final seminar supervisor.
|
||||
FINAL_SEMINAR.compilationSuffix = , project: {0}
|
||||
|
||||
PEER.REVIEW_COMPLETED.title = Peer review completed
|
||||
|
@ -2,7 +2,6 @@ package se.su.dsv.scipro.report;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||
import se.su.dsv.scipro.grading.GradingBasis;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
@ -19,8 +18,6 @@ public interface GradingReportService {
|
||||
SupervisorGradingReport supervisorGradingReport
|
||||
);
|
||||
|
||||
boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition);
|
||||
|
||||
GradingBasis getGradingBasis(Project project);
|
||||
|
||||
GradingBasis updateGradingBasis(Project project, GradingBasis gradingBasis);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package se.su.dsv.scipro.report;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.Clock;
|
||||
@ -8,6 +9,9 @@ import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionGrading;
|
||||
import se.su.dsv.scipro.finalseminar.OppositionApprovedEvent;
|
||||
import se.su.dsv.scipro.finalseminar.OppositionCriteria;
|
||||
import se.su.dsv.scipro.grading.GradingBasis;
|
||||
import se.su.dsv.scipro.grading.GradingReportTemplateService;
|
||||
import se.su.dsv.scipro.grading.GradingReportTemplateUpdate;
|
||||
@ -20,7 +24,8 @@ import se.su.dsv.scipro.system.ProjectTypeService;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.Either;
|
||||
|
||||
public class GradingReportServiceImpl implements GradingReportTemplateService, GradingReportService {
|
||||
public class GradingReportServiceImpl
|
||||
implements GradingReportTemplateService, GradingReportService, FinalSeminarOppositionGrading {
|
||||
|
||||
private final EventBus eventBus;
|
||||
private final ThesisSubmissionHistoryService thesisSubmissionHistoryService;
|
||||
@ -44,11 +49,11 @@ public class GradingReportServiceImpl implements GradingReportTemplateService, G
|
||||
this.supervisorGradingReportRepository = supervisorGradingReportRepository;
|
||||
this.gradingReportTemplateRepo = gradingReportTemplateRepo;
|
||||
this.projectTypeService = projectTypeService;
|
||||
|
||||
eventBus.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition) {
|
||||
private boolean updateOppositionCriteria(SupervisorGradingReport report, FinalSeminarOpposition opposition) {
|
||||
for (GradingCriterion gradingCriterion : report.getIndividualCriteria()) {
|
||||
boolean isOppositionCriterion = gradingCriterion.getFlag() == GradingCriterion.Flag.OPPOSITION;
|
||||
boolean betterGrade =
|
||||
@ -294,4 +299,39 @@ public class GradingReportServiceImpl implements GradingReportTemplateService, G
|
||||
|
||||
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;
|
||||
|
||||
import java.util.Optional;
|
||||
import se.su.dsv.scipro.file.FileUpload;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||
|
||||
public interface OppositionReportService {
|
||||
@ -7,4 +9,10 @@ public interface OppositionReportService {
|
||||
void save(OppositionReport oppositionReport);
|
||||
void deleteOppositionReport(FinalSeminarOpposition finalSeminarOpposition);
|
||||
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;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
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.finalseminar.FinalSeminarOpposition;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionRepo;
|
||||
|
||||
@ -15,18 +18,21 @@ public class OppositionReportServiceImpl implements OppositionReportService {
|
||||
private GradingReportTemplateRepo gradingReportTemplateRepo;
|
||||
private FileService fileService;
|
||||
private FinalSeminarOppositionRepo finalSeminarOppositionRepo;
|
||||
private final EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
public OppositionReportServiceImpl(
|
||||
OppositionReportRepo oppositionReportRepo,
|
||||
GradingReportTemplateRepo gradingReportTemplateRepo,
|
||||
FileService fileService,
|
||||
FinalSeminarOppositionRepo finalSeminarOppositionRepo
|
||||
FinalSeminarOppositionRepo finalSeminarOppositionRepo,
|
||||
EventBus eventBus
|
||||
) {
|
||||
this.oppositionReportRepo = oppositionReportRepo;
|
||||
this.gradingReportTemplateRepo = gradingReportTemplateRepo;
|
||||
this.fileService = fileService;
|
||||
this.finalSeminarOppositionRepo = finalSeminarOppositionRepo;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -74,4 +80,36 @@ public class OppositionReportServiceImpl implements OppositionReportService {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
3
core/src/main/resources/db/migration/V5__final_seminar_opposition_request_improvements.sql
Normal file
3
core/src/main/resources/db/migration/V5__final_seminar_opposition_request_improvements.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE `final_seminar_opposition`
|
||||
ADD COLUMN `improvements_requested_at` DATETIME NULL,
|
||||
ADD COLUMN `supervisor_improvements_comment` TEXT NULL;
|
2
core/src/main/resources/db/migration/V6__final_seminar_work_days_to_fix_opposition_report.sql
Normal file
2
core/src/main/resources/db/migration/V6__final_seminar_work_days_to_fix_opposition_report.sql
Normal file
@ -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;
|
@ -559,7 +559,7 @@
|
||||
|
||||
<xs:complexType name="course">
|
||||
<xs:sequence>
|
||||
<xs:element name="courseCode" type="xs:string" minOccurs="0">
|
||||
<xs:element name="courseCode" type="xs:string" minOccurs="1">
|
||||
</xs:element>
|
||||
<xs:element name="credits" type="xs:float" minOccurs="1">
|
||||
</xs:element>
|
||||
@ -567,6 +567,8 @@
|
||||
</xs:element>
|
||||
<xs:element name="level" type="educationalLevel" minOccurs="0">
|
||||
</xs:element>
|
||||
<xs:element name="degreeThesisCourse" type="xs:boolean" minOccurs="1">
|
||||
</xs:element>
|
||||
<xs:element name="eduInstDesignation" type="xs:string" minOccurs="1">
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
|
@ -1,14 +1,22 @@
|
||||
package se.su.dsv.scipro.finalseminar;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.report.AbstractGradingCriterion;
|
||||
import se.su.dsv.scipro.report.GradingCriterionPointTemplate;
|
||||
import se.su.dsv.scipro.report.GradingReportTemplate;
|
||||
import se.su.dsv.scipro.report.OppositionReport;
|
||||
import se.su.dsv.scipro.system.DegreeType;
|
||||
@ -46,6 +54,76 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio
|
||||
assertEquals(0, finalSeminarOppositionService.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void opposition_criteria_are_taken_from_the_grading_report_template() {
|
||||
FinalSeminarOpposition opposition = createOpposition(finalSeminar, createUser());
|
||||
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) {
|
||||
OppositionReport report = new OppositionReport(createGradingReportTemplate(), opposition);
|
||||
opposition.setOppositionReport(report);
|
||||
@ -93,7 +171,7 @@ public class FinalSeminarOppositionServiceImplIntegrationTest extends Integratio
|
||||
FinalSeminarOpposition opposition = new FinalSeminarOpposition();
|
||||
opposition.setFinalSeminar(finalSeminar);
|
||||
opposition.setUser(student);
|
||||
opposition.setProject(createProject(createProjectType()));
|
||||
opposition.setProject(createProject(projectType));
|
||||
return save(opposition);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import static se.su.dsv.scipro.test.Matchers.isRight;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.ZonedDateTime;
|
||||
@ -30,6 +31,9 @@ public class FinalSeminarServiceImplIntegrationTest extends IntegrationTest {
|
||||
@Inject
|
||||
private FinalSeminarService finalSeminarService;
|
||||
|
||||
@Inject
|
||||
private FinalSeminarOppositionService finalSeminarOppositionService;
|
||||
|
||||
private ProjectType projectType;
|
||||
private FinalSeminar futureFinalSeminar;
|
||||
private User user;
|
||||
@ -309,6 +313,43 @@ public class FinalSeminarServiceImplIntegrationTest extends IntegrationTest {
|
||||
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) {
|
||||
FinalSeminar finalSeminar = initFinalSeminar(createProject(), 5);
|
||||
final Date dateCreated = Date.from(ZonedDateTime.now().minusDays(daysAgo).toInstant());
|
||||
@ -340,10 +381,10 @@ public class FinalSeminarServiceImplIntegrationTest extends IntegrationTest {
|
||||
save(seminar);
|
||||
}
|
||||
|
||||
private void addOpposition(FinalSeminar seminar, FinalSeminarGrade grade) {
|
||||
private FinalSeminarOpposition addOpposition(FinalSeminar seminar, FinalSeminarGrade grade) {
|
||||
FinalSeminarOpposition opposition = createOpposition(seminar);
|
||||
opposition.setGrade(grade);
|
||||
save(opposition);
|
||||
return save(opposition);
|
||||
}
|
||||
|
||||
private OppositionReport createOppositionReport(FinalSeminarOpposition opposition) {
|
||||
|
@ -121,6 +121,8 @@ public class BasicForumServiceImplTest {
|
||||
ForumThread forumThread = new ForumThread();
|
||||
forumThread.addPost(post);
|
||||
|
||||
when(postRepository.findByThread(forumThread)).thenReturn(List.of(post));
|
||||
|
||||
when(readStateRepository.find(eq(goodUser), isA(ForumPost.class))).thenReturn(readState);
|
||||
when(readStateRepository.find(eq(badUser), isA(ForumPost.class))).thenReturn(notReadState);
|
||||
|
||||
|
@ -0,0 +1,83 @@
|
||||
package se.su.dsv.scipro.forum;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumThread;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.IntegrationTest;
|
||||
|
||||
public class BasicForumServiceIntegrationTest extends IntegrationTest {
|
||||
|
||||
@Inject
|
||||
BasicForumService basicForumService;
|
||||
|
||||
private User op;
|
||||
private User commenter;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
User op = User.builder().firstName("Bill").lastName("Gates").emailAddress("bill@example.com").build();
|
||||
this.op = save(op);
|
||||
|
||||
User commenter = User.builder().firstName("Steve").lastName("Jobs").emailAddress("steve@example.com").build();
|
||||
this.commenter = save(commenter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_not_delete_original_post() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost originalPost = basicForumService.createReply(thread, op, "Test post", Set.of());
|
||||
|
||||
setLoggedInAs(op);
|
||||
|
||||
assertFalse(basicForumService.canDelete(originalPost));
|
||||
assertThrows(IllegalArgumentException.class, () -> basicForumService.deletePost(originalPost));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_delete_reply_to_original_post() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost originalPost = basicForumService.createReply(thread, op, "Test post", Set.of());
|
||||
ForumPost reply = basicForumService.createReply(thread, commenter, "Test reply", Set.of());
|
||||
|
||||
setLoggedInAs(commenter);
|
||||
|
||||
assertTrue(basicForumService.canDelete(reply));
|
||||
assertDoesNotThrow(() -> basicForumService.deletePost(reply));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_not_delete_someone_elses_reply() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost originalPost = basicForumService.createReply(thread, op, "Test post", Set.of());
|
||||
ForumPost reply = basicForumService.createReply(thread, commenter, "Test reply", Set.of());
|
||||
|
||||
setLoggedInAs(op);
|
||||
|
||||
assertFalse(basicForumService.canDelete(reply));
|
||||
assertThrows(IllegalArgumentException.class, () -> basicForumService.deletePost(reply));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void system_can_delete_all_replies() {
|
||||
ForumThread thread = basicForumService.createThread("Test thread");
|
||||
ForumPost originalPost = basicForumService.createReply(thread, op, "Test post", Set.of());
|
||||
ForumPost reply = basicForumService.createReply(thread, commenter, "Test reply", Set.of());
|
||||
ForumPost secondReply = basicForumService.createReply(thread, op, "Test post", Set.of());
|
||||
|
||||
setLoggedInAs(null);
|
||||
|
||||
assertTrue(basicForumService.canDelete(reply));
|
||||
assertDoesNotThrow(() -> basicForumService.deletePost(reply));
|
||||
assertTrue(basicForumService.canDelete(secondReply));
|
||||
assertDoesNotThrow(() -> basicForumService.deletePost(secondReply));
|
||||
}
|
||||
}
|
@ -241,6 +241,7 @@ public class IdeaServiceImplTest {
|
||||
when(generalSystemSettingsService.getGeneralSystemSettingsInstance()).thenReturn(new GeneralSystemSettings());
|
||||
Idea idea = createBachelorIdea(Idea.Status.UNMATCHED);
|
||||
when(applicationPeriodService.getTypesForStudent(applicationPeriod, student)).thenReturn(List.of(bachelor));
|
||||
when(applicationPeriodService.getTypesForStudent(applicationPeriod, coAuthor)).thenReturn(List.of(bachelor));
|
||||
|
||||
Pair<Boolean, String> acceptance = ideaService.validateStudentAcceptance(
|
||||
idea,
|
||||
@ -401,6 +402,39 @@ public class IdeaServiceImplTest {
|
||||
assertEquals(expected, ideaService.countAuthorsByApplicationPeriod(applicationPeriod, params));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrong_type_for_author() {
|
||||
when(applicationPeriodService.getTypesForStudent(applicationPeriod, student)).thenReturn(List.of(master));
|
||||
when(applicationPeriodService.getTypesForStudent(applicationPeriod, coAuthor)).thenReturn(List.of(bachelor));
|
||||
|
||||
assertPair(
|
||||
false,
|
||||
"The idea is the wrong level for you, please pick another one.",
|
||||
ideaService.validateStudentAcceptance(
|
||||
createBachelorIdea(Idea.Status.UNMATCHED),
|
||||
student,
|
||||
coAuthor,
|
||||
applicationPeriod
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrong_type_for_partner() {
|
||||
when(applicationPeriodService.getTypesForStudent(applicationPeriod, coAuthor)).thenReturn(List.of(master));
|
||||
|
||||
assertPair(
|
||||
false,
|
||||
"The idea is the wrong level for your partner, please pick another one.",
|
||||
ideaService.validateStudentAcceptance(
|
||||
createBachelorIdea(Idea.Status.UNMATCHED),
|
||||
student,
|
||||
coAuthor,
|
||||
applicationPeriod
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private Idea mockInactiveIdea() {
|
||||
Idea idea = new Idea();
|
||||
Match match = new Match();
|
||||
|
@ -5,7 +5,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.*;
|
||||
@ -13,6 +18,10 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
|
||||
import se.su.dsv.scipro.finalseminar.OppositionApprovedEvent;
|
||||
import se.su.dsv.scipro.grading.GetGradeError;
|
||||
import se.su.dsv.scipro.grading.GradingServiceImpl;
|
||||
import se.su.dsv.scipro.grading.Result;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.system.DegreeType;
|
||||
@ -31,6 +40,9 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
||||
@Inject
|
||||
private GradingReportServiceImpl gradingReportService;
|
||||
|
||||
@Inject
|
||||
private EventBus eventBus;
|
||||
|
||||
private ProjectType projectType;
|
||||
private GradingReportTemplate gradingReportTemplate;
|
||||
private Project project;
|
||||
@ -45,7 +57,6 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
||||
project = createProject(projectType, 30);
|
||||
gradingReportTemplate = createProjectGradingCriterion(gradingReportTemplate, 2);
|
||||
gradingReportTemplate = createIndividualGradingCriterion(gradingReportTemplate, 2);
|
||||
gradingReport = createGradingReport(project, student);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -68,6 +79,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void submit_supervisor_grading_report_flags_report_as_submitted() {
|
||||
gradingReport = createGradingReport(project, student);
|
||||
assessAllCriteria(gradingReport);
|
||||
Either<List<SubmissionError>, SupervisorGradingReport> result = gradingReportService.submitReport(
|
||||
gradingReport
|
||||
@ -77,6 +89,7 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void submitting_supervisor_report_throws_exception_if_report_is_not_finished() {
|
||||
gradingReport = createGradingReport(project, student);
|
||||
Either<List<SubmissionError>, SupervisorGradingReport> result = gradingReportService.submitReport(
|
||||
gradingReport
|
||||
);
|
||||
@ -86,38 +99,35 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
||||
@Test
|
||||
public void update_opposition_criterion() {
|
||||
addOppositionCriterion();
|
||||
boolean updated = updateOppositionCriterion();
|
||||
updateOppositionCriterion();
|
||||
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
||||
assert oppositionCriterion != null;
|
||||
assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
|
||||
assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
|
||||
assertTrue(updated);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void update_opposition_if_title_matches_english_title() {
|
||||
addOppositionCriterion();
|
||||
boolean updated = updateOppositionCriterion();
|
||||
updateOppositionCriterion();
|
||||
|
||||
GradingCriterion oppositionCriterion = findEnglishOppositionCriterion("Ö1 Opposition report");
|
||||
assert oppositionCriterion != null;
|
||||
assertEquals(FEEDBACK_ON_OPPOSITION, oppositionCriterion.getFeedback());
|
||||
assertEquals((Integer) OPPOSITION_CRITERION_POINTS, oppositionCriterion.getPoints());
|
||||
assertTrue(updated);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updating_opposition_criterion_does_nothing_if_criterion_already_has_values() {
|
||||
addOppositionCriterion();
|
||||
assessAllCriteria(gradingReport);
|
||||
boolean updated = updateOppositionCriterion();
|
||||
updateOppositionCriterion();
|
||||
|
||||
GradingCriterion oppositionCriterion = findOppositionCriterion();
|
||||
assert oppositionCriterion != null;
|
||||
assertEquals(FEEDBACK, oppositionCriterion.getFeedback());
|
||||
assertEquals((Integer) oppositionCriterion.getMaxPoints(), oppositionCriterion.getPoints());
|
||||
assertFalse(updated);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -146,14 +156,52 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
||||
assertNull(oppositionCriterion.getFeedback());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_json_deserialization() throws IOException {
|
||||
String json =
|
||||
"""
|
||||
{
|
||||
"grade": "A",
|
||||
"reported": "2021-01-01"
|
||||
}
|
||||
""";
|
||||
HttpServer httpServer = startHttpServerWithJsonResponse(json);
|
||||
|
||||
int port = httpServer.getAddress().getPort();
|
||||
|
||||
GradingServiceImpl gradingService = new GradingServiceImpl("http://localhost:" + port);
|
||||
Either<GetGradeError, Optional<Result>> result = gradingService.getResult("token", 1, 2, 3);
|
||||
|
||||
Optional<Result> right = result.right();
|
||||
assertTrue(right.isPresent());
|
||||
assertEquals(LocalDate.of(2021, 1, 1), right.get().reported());
|
||||
|
||||
httpServer.stop(0);
|
||||
}
|
||||
|
||||
private static HttpServer startHttpServerWithJsonResponse(String json) throws IOException {
|
||||
HttpServer httpServer = HttpServer.create();
|
||||
httpServer.createContext("/", exchange -> {
|
||||
try (exchange) {
|
||||
byte[] response = json.getBytes(StandardCharsets.UTF_8);
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/json");
|
||||
exchange.sendResponseHeaders(200, response.length);
|
||||
exchange.getResponseBody().write(response);
|
||||
}
|
||||
});
|
||||
httpServer.bind(new InetSocketAddress("localhost", 0), 0);
|
||||
httpServer.start();
|
||||
return httpServer;
|
||||
}
|
||||
|
||||
private void addOppositionCriterion() {
|
||||
gradingReportTemplate = createOppositionCriteria(gradingReportTemplate, 2);
|
||||
gradingReport = createGradingReport(project, student);
|
||||
}
|
||||
|
||||
private boolean updateOppositionCriterion() {
|
||||
private void updateOppositionCriterion() {
|
||||
FinalSeminarOpposition opposition = createFinalSeminarOpposition();
|
||||
return gradingReportService.updateOppositionCriteria(gradingReport, opposition);
|
||||
eventBus.post(new OppositionApprovedEvent(opposition));
|
||||
}
|
||||
|
||||
private GradingCriterion findOppositionCriterion() {
|
||||
@ -176,8 +224,8 @@ public class GradingReportServiceImplIntegrationTest extends IntegrationTest {
|
||||
|
||||
private FinalSeminarOpposition createFinalSeminarOpposition() {
|
||||
FinalSeminarOpposition finalSeminarOpposition = new FinalSeminarOpposition();
|
||||
finalSeminarOpposition.setProject(createProject(projectType, 30));
|
||||
finalSeminarOpposition.setUser(createStudent());
|
||||
finalSeminarOpposition.setProject(project);
|
||||
finalSeminarOpposition.setUser(student);
|
||||
finalSeminarOpposition.setFinalSeminar(createFinalSeminar());
|
||||
|
||||
finalSeminarOpposition.setFeedback(FEEDBACK_ON_OPPOSITION);
|
||||
|
@ -1,11 +1,15 @@
|
||||
package se.su.dsv.scipro.test;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.EntityTransaction;
|
||||
import jakarta.persistence.Persistence;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.flywaydb.core.Flyway;
|
||||
@ -25,6 +29,7 @@ import se.su.dsv.scipro.RepositoryConfiguration;
|
||||
import se.su.dsv.scipro.profiles.CurrentProfile;
|
||||
import se.su.dsv.scipro.sukat.Sukat;
|
||||
import se.su.dsv.scipro.system.CurrentUser;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
@Testcontainers
|
||||
public abstract class SpringTest {
|
||||
@ -35,6 +40,11 @@ public abstract class SpringTest {
|
||||
@Container
|
||||
static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>("mariadb:10.11");
|
||||
|
||||
@Inject
|
||||
private TestUser testUser;
|
||||
|
||||
private CapturingEventBus capturingEventBus;
|
||||
|
||||
@BeforeEach
|
||||
public final void prepareSpring() throws SQLException {
|
||||
MariaDbDataSource dataSource = new MariaDbDataSource(mariaDBContainer.getJdbcUrl());
|
||||
@ -50,12 +60,17 @@ public abstract class SpringTest {
|
||||
transaction.begin();
|
||||
transaction.setRollbackOnly();
|
||||
|
||||
capturingEventBus = new CapturingEventBus();
|
||||
|
||||
AnnotationConfigApplicationContext annotationConfigApplicationContext =
|
||||
new AnnotationConfigApplicationContext();
|
||||
annotationConfigApplicationContext.registerBean("eventBus", EventBus.class, () -> this.capturingEventBus);
|
||||
annotationConfigApplicationContext.register(TestContext.class);
|
||||
annotationConfigApplicationContext.getBeanFactory().registerSingleton("entityManager", this.entityManager);
|
||||
annotationConfigApplicationContext.refresh();
|
||||
annotationConfigApplicationContext.getAutowireCapableBeanFactory().autowireBean(this);
|
||||
|
||||
testUser.setUser(null); // default to system
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@ -75,6 +90,14 @@ public abstract class SpringTest {
|
||||
}
|
||||
}
|
||||
|
||||
protected void setLoggedInAs(User user) {
|
||||
this.testUser.setUser(user);
|
||||
}
|
||||
|
||||
protected List<Object> getPublishedEvents() {
|
||||
return capturingEventBus.publishedEvents;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import({ CoreConfig.class, RepositoryConfiguration.class })
|
||||
public static class TestContext {
|
||||
@ -96,7 +119,7 @@ public abstract class SpringTest {
|
||||
|
||||
@Bean
|
||||
public CurrentUser currentUser() {
|
||||
return () -> null;
|
||||
return new TestUser();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -106,4 +129,29 @@ public abstract class SpringTest {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestUser implements CurrentUser {
|
||||
|
||||
private User user;
|
||||
|
||||
@Override
|
||||
public User get() {
|
||||
return user;
|
||||
}
|
||||
|
||||
private void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
174
pom.xml
174
pom.xml
@ -15,6 +15,7 @@
|
||||
<module>daisy-integration</module>
|
||||
<module>war</module>
|
||||
<module>api</module>
|
||||
<module>test-data</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
@ -22,23 +23,10 @@
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
<!-- Dependency versions -->
|
||||
<slf4j.version>2.0.7</slf4j.version>
|
||||
<log4j2.version>2.20.0</log4j2.version>
|
||||
<wicket.version>10.4.0</wicket.version>
|
||||
|
||||
<!-- See https://hibernate.org/orm/releases/ for which version Hibernate implements -->
|
||||
<jakarta.persistence-api.version>3.1.0</jakarta.persistence-api.version>
|
||||
<hibernate.version>6.5.0.Final</hibernate.version>
|
||||
<mariadb-java-client.version>3.2.0</mariadb-java-client.version>
|
||||
|
||||
<querydsl.version>5.0.0</querydsl.version>
|
||||
<jakarta.servlet.version>5.0.0</jakarta.servlet.version>
|
||||
<junit.version>5.9.3</junit.version>
|
||||
<mockito.version>5.3.1</mockito.version>
|
||||
<flyway.version>9.19.1</flyway.version>
|
||||
<jersey.version>3.1.6</jersey.version>
|
||||
<poi.version>5.2.5</poi.version>
|
||||
<jackson.version>2.17.0</jackson.version>
|
||||
<poi.version>5.4.0</poi.version>
|
||||
|
||||
<!--
|
||||
When updating spring-boot check if the transitive dependency on json-smart has been
|
||||
@ -113,47 +101,6 @@
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
|
||||
<!-- Servlet API, needed for compilation. -->
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<version>${jakarta.servlet.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- LOGGING DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-bom</artifactId>
|
||||
<version>${log4j2.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>${mariadb-java-client.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--QueryDSL-->
|
||||
<dependency>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-bom</artifactId>
|
||||
<version>${querydsl.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-jpa</artifactId>
|
||||
@ -161,41 +108,6 @@
|
||||
<classifier>jakarta</classifier>
|
||||
</dependency>
|
||||
|
||||
<!-- JPA -->
|
||||
<dependency>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
<version>${jakarta.persistence-api.version}</version>
|
||||
</dependency>
|
||||
<!-- Hibernate impl -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.orm</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>${hibernate.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jersey/Jax-Rs -->
|
||||
<dependency>
|
||||
<groupId>jakarta.ws.rs</groupId>
|
||||
<artifactId>jakarta.ws.rs-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey</groupId>
|
||||
<artifactId>jersey-bom</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson</groupId>
|
||||
<artifactId>jackson-bom</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Additional dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
@ -203,22 +115,6 @@
|
||||
<version>32.0.1-jre</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.mail</groupId>
|
||||
<artifactId>jakarta.mail-api</artifactId>
|
||||
<version>2.1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
<version>2.1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.angus</groupId>
|
||||
<artifactId>jakarta.mail</artifactId>
|
||||
<version>2.0.2</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!--
|
||||
2.5.1 is brought in transitively by
|
||||
@ -234,32 +130,6 @@
|
||||
<version>2.5.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test stuff -->
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<version>2.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
@ -270,16 +140,6 @@
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>${poi.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>${flyway.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
<version>${flyway.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
@ -295,13 +155,6 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Additional dependencies -->
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
<version>1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test stuff -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
@ -326,7 +179,6 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@ -359,6 +211,11 @@
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
@ -460,6 +317,23 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<configuration>
|
||||
<configLocation>checkstyle.xml</configLocation>
|
||||
<failOnViolation>true</failOnViolation>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>validate</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
20
test-data/pom.xml
Normal file
20
test-data/pom.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>se.su.dsv.scipro</groupId>
|
||||
<artifactId>SciPro</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>test-data</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>se.su.dsv.scipro</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
29
test-data/src/main/java/se/su/dsv/scipro/testdata/BaseData.java
vendored
Normal file
29
test-data/src/main/java/se/su/dsv/scipro/testdata/BaseData.java
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package se.su.dsv.scipro.testdata;
|
||||
|
||||
import java.util.List;
|
||||
import se.su.dsv.scipro.match.Keyword;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ResearchArea;
|
||||
|
||||
/// All the base test data that can be re-used in different test cases.
|
||||
///
|
||||
/// **Do not modify any of this data.** There are many
|
||||
/// [TestDataPopulator]s that rely on this data to be in a specific state.
|
||||
///
|
||||
/// In addition to the data that is available here there is also much additional
|
||||
/// data that has been created;
|
||||
///
|
||||
/// - A grading report template for each [ProjectType]
|
||||
///
|
||||
public interface BaseData {
|
||||
ProjectType bachelor();
|
||||
ProjectType magister();
|
||||
ProjectType master();
|
||||
|
||||
/**
|
||||
* @return generic research area with some keywords attached to it
|
||||
*/
|
||||
ResearchAreaAndKeywords researchArea();
|
||||
|
||||
record ResearchAreaAndKeywords(ResearchArea researchArea, List<Keyword> keywords) {}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package se.su.dsv.scipro;
|
||||
package se.su.dsv.scipro.testdata;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
@ -9,18 +9,35 @@ import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Month;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import se.su.dsv.scipro.checklist.ChecklistCategory;
|
||||
import se.su.dsv.scipro.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.match.*;
|
||||
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.Idea;
|
||||
import se.su.dsv.scipro.match.IdeaService;
|
||||
import se.su.dsv.scipro.match.Keyword;
|
||||
import se.su.dsv.scipro.match.Target;
|
||||
import se.su.dsv.scipro.match.TholanderBox;
|
||||
import se.su.dsv.scipro.milestones.dataobjects.MilestoneActivityTemplate;
|
||||
import se.su.dsv.scipro.milestones.dataobjects.MilestonePhaseTemplate;
|
||||
import se.su.dsv.scipro.milestones.service.MilestoneActivityTemplateService;
|
||||
import se.su.dsv.scipro.notifications.dataobject.CustomEvent;
|
||||
import se.su.dsv.scipro.notifications.dataobject.GroupEvent;
|
||||
import se.su.dsv.scipro.notifications.dataobject.IdeaEvent;
|
||||
import se.su.dsv.scipro.notifications.dataobject.MileStoneEvent;
|
||||
import se.su.dsv.scipro.notifications.dataobject.Notification;
|
||||
import se.su.dsv.scipro.notifications.dataobject.PeerEvent;
|
||||
import se.su.dsv.scipro.notifications.dataobject.ProjectEvent;
|
||||
import se.su.dsv.scipro.notifications.dataobject.ProjectForumEvent;
|
||||
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.Profiles;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
@ -34,13 +51,16 @@ import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.system.*;
|
||||
import se.su.dsv.scipro.util.Pair;
|
||||
|
||||
public class DataInitializer implements Lifecycle {
|
||||
public class DataInitializer implements Lifecycle, BaseData, Factory {
|
||||
|
||||
public static final int APPLICATION_PERIOD_START_MINUS_DAYS = 1;
|
||||
public static final int APPLICATION_PERIOD_END_PLUS_DAYS = 3;
|
||||
public static final int APPLICATION_PERIOD_COURSE_START_PLUS_DAYS = 5;
|
||||
public static final long RESEARCH_AREA_ID = 12L;
|
||||
|
||||
@Inject
|
||||
private Optional<Collection<TestDataPopulator>> testDataPopulators = Optional.empty();
|
||||
|
||||
@Inject
|
||||
private UserService userService;
|
||||
|
||||
@ -53,12 +73,18 @@ public class DataInitializer implements Lifecycle {
|
||||
@Inject
|
||||
private MilestoneActivityTemplateService milestoneActivityTemplateService;
|
||||
|
||||
@Inject
|
||||
private FileService fileService;
|
||||
|
||||
@Inject
|
||||
private CurrentProfile profile;
|
||||
|
||||
@Inject
|
||||
private Provider<EntityManager> em;
|
||||
|
||||
@Inject
|
||||
private ReceiverConfigurationService receiverConfigurationService;
|
||||
|
||||
@Inject
|
||||
private RoughDraftApprovalService roughDraftApprovalService;
|
||||
|
||||
@ -100,6 +126,7 @@ public class DataInitializer implements Lifecycle {
|
||||
private ProjectType masterClass;
|
||||
private ProjectType magisterClass;
|
||||
private ApplicationPeriod applicationPeriod;
|
||||
private Project project1;
|
||||
private Project project2;
|
||||
|
||||
@Transactional
|
||||
@ -120,6 +147,12 @@ public class DataInitializer implements Lifecycle {
|
||||
createTarget();
|
||||
createStudentIdea();
|
||||
createRoughDraftApproval();
|
||||
createPastFinalSeminar();
|
||||
setUpNotifications();
|
||||
Collection<TestDataPopulator> availablePopulators = testDataPopulators.orElseGet(Collections::emptySet);
|
||||
for (TestDataPopulator testDataPopulator : availablePopulators) {
|
||||
testDataPopulator.populate(this, this);
|
||||
}
|
||||
}
|
||||
if (profile.getCurrentProfile() == Profiles.DEV && noAdminUser()) {
|
||||
createAdmin();
|
||||
@ -136,6 +169,47 @@ public class DataInitializer implements Lifecycle {
|
||||
reviewerAssignmentService.assignReviewer(project2, eric_employee);
|
||||
}
|
||||
|
||||
private void setUpNotifications() {
|
||||
enableAllNotifications(Notification.Type.PEER, PeerEvent.Event.values());
|
||||
enableAllNotifications(Notification.Type.FORUM, ProjectForumEvent.Event.values());
|
||||
enableAllNotifications(Notification.Type.GROUP, GroupEvent.Event.values());
|
||||
enableAllNotifications(Notification.Type.MILESTONE, MileStoneEvent.Event.values());
|
||||
enableAllNotifications(Notification.Type.PROJECT, ProjectEvent.Event.values());
|
||||
enableAllNotifications(Notification.Type.IDEA, IdeaEvent.Event.values());
|
||||
enableAllNotifications(Notification.Type.CUSTOM, CustomEvent.Event.values());
|
||||
enableAllNotifications(Notification.Type.FINAL_SEMINAR, SeminarEvent.Event.values());
|
||||
}
|
||||
|
||||
private void enableAllNotifications(Notification.Type type, Enum<?>[] events) {
|
||||
for (Enum<?> event : events) {
|
||||
for (Member.Type member : Member.Type.values()) {
|
||||
receiverConfigurationService.setReceiving(type, event, member, 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
|
||||
public void stop() {}
|
||||
|
||||
@ -195,7 +269,7 @@ public class DataInitializer implements Lifecycle {
|
||||
}
|
||||
|
||||
private void createProjects() {
|
||||
createProject(PROJECT_1, eric_employee, sture_student, stina_student, eve_employee, 135);
|
||||
project1 = createProject(PROJECT_1, eric_employee, sture_student, stina_student, eve_employee, 135);
|
||||
project2 = createProject(PROJECT_2, eve_employee, sid_student, simon_student, eric_employee, 246);
|
||||
}
|
||||
|
||||
@ -243,13 +317,18 @@ public class DataInitializer implements Lifecycle {
|
||||
sofia_student = createStudent("Sofia", 17);
|
||||
}
|
||||
|
||||
private User createStudent(String firstName, int identifier) {
|
||||
private User createStudent(String firstName) {
|
||||
User user = createUser(firstName, STUDENT_LAST);
|
||||
user.setIdentifier(identifier);
|
||||
createBeta(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
private User createStudent(String firstName, int identifier) {
|
||||
User user = createStudent(firstName);
|
||||
user.setIdentifier(identifier);
|
||||
return user;
|
||||
}
|
||||
|
||||
private User createEmployee(String firstName) {
|
||||
User user = createUser(firstName, EMPLOYEE_LAST);
|
||||
Unit u = createUnit();
|
||||
@ -1904,9 +1983,6 @@ public class DataInitializer implements Lifecycle {
|
||||
|
||||
magisterClass = new ProjectType(ProjectType.MAGISTER, "Magister", "One-year-Master degree thesis project");
|
||||
save(magisterClass);
|
||||
|
||||
final ProjectType phdClass = new ProjectType(DegreeType.NONE, "PhD", "PhD project");
|
||||
save(phdClass);
|
||||
}
|
||||
|
||||
private void createDefaultChecklistCategoriesIfNotDone() {
|
||||
@ -2087,6 +2163,41 @@ public class DataInitializer implements Lifecycle {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectType bachelor() {
|
||||
return bachelorClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectType magister() {
|
||||
return magisterClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectType master() {
|
||||
return masterClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User createAuthor(String firstName) {
|
||||
return createStudent(firstName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User createSupervisor(String firstName) {
|
||||
return createEmployee(firstName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User createReviewer(String firstName) {
|
||||
return createEmployee(firstName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResearchAreaAndKeywords researchArea() {
|
||||
return new ResearchAreaAndKeywords(researchArea1, List.of(keyword1, keyword2));
|
||||
}
|
||||
|
||||
private static final class SimpleTextFile implements FileUpload {
|
||||
|
||||
private final User uploader;
|
46
test-data/src/main/java/se/su/dsv/scipro/testdata/Factory.java
vendored
Normal file
46
test-data/src/main/java/se/su/dsv/scipro/testdata/Factory.java
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package se.su.dsv.scipro.testdata;
|
||||
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
/**
|
||||
* A factory to help with repetitive tasks when populating test data.
|
||||
*/
|
||||
public interface Factory {
|
||||
/**
|
||||
* Creates a user with the given first name and last name "Student".
|
||||
* The user is given the role {@link Roles#AUTHOR}.
|
||||
* <p>
|
||||
* A username is created of the form {@code <first_name>@example.com} that
|
||||
* can be used to log in.
|
||||
*/
|
||||
User createAuthor(String firstName);
|
||||
|
||||
/**
|
||||
* Creates a user with the given first name and last name "Employee".
|
||||
* <p>
|
||||
* The user is given the role {@link Roles#SUPERVISOR}, {@link Roles#REVIEWER},
|
||||
* and {@link Roles#EXAMINER}.
|
||||
* <p>
|
||||
* The user gets a default research area, unit, and language. It is also
|
||||
* marked as {@link User#setActiveAsSupervisor(boolean) an active supervisor}.
|
||||
* <p>
|
||||
* A username is created of the form {@code <first_name>@example.com} that
|
||||
* can be used to log in.
|
||||
*/
|
||||
User createSupervisor(String firstName);
|
||||
|
||||
/**
|
||||
* Creates a user with the given first name and last name "Employee".
|
||||
* <p>
|
||||
* The user is given the role {@link Roles#SUPERVISOR}, {@link Roles#REVIEWER},
|
||||
* and {@link Roles#EXAMINER}.
|
||||
* <p>
|
||||
* The user gets a default research area, unit, and language. It is also
|
||||
* marked as {@link User#setActiveAsSupervisor(boolean) an active supervisor}.
|
||||
* <p>
|
||||
* A username is created of the form {@code <first_name>@example.com} that
|
||||
* can be used to log in.
|
||||
*/
|
||||
User createReviewer(String firstName);
|
||||
}
|
16
test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataConfiguration.java
vendored
Normal file
16
test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataConfiguration.java
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package se.su.dsv.scipro.testdata;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import se.su.dsv.scipro.war.PluginConfiguration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ComponentScan(basePackages = "se.su.dsv.scipro.testdata.populators")
|
||||
public class TestDataConfiguration implements PluginConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataInitializer dataInitializer() {
|
||||
return new DataInitializer();
|
||||
}
|
||||
}
|
11
test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataPopulator.java
vendored
Normal file
11
test-data/src/main/java/se/su/dsv/scipro/testdata/TestDataPopulator.java
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package se.su.dsv.scipro.testdata;
|
||||
|
||||
public interface TestDataPopulator {
|
||||
/**
|
||||
* Add test data to the system to help with testing a specific feature.
|
||||
*
|
||||
* @param baseData the base data already populated
|
||||
* @param factory helper object to make repetitive tasks easier (such as creating users)
|
||||
*/
|
||||
void populate(BaseData baseData, Factory factory);
|
||||
}
|
8
test-data/src/main/java/se/su/dsv/scipro/testdata/package-info.java
vendored
Normal file
8
test-data/src/main/java/se/su/dsv/scipro/testdata/package-info.java
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* This package contains the infrastructure that is used when generating test data for the application. To add new test
|
||||
* data to the system, add a new class to the {@link se.su.dsv.scipro.testdata.populators} package that implements the
|
||||
* {@link se.su.dsv.scipro.testdata.TestDataPopulator} interface and annotate it with
|
||||
* {@link org.springframework.stereotype.Service @Service}. Inject dependencies as needed using
|
||||
* {@link jakarta.inject.Inject @Inject}.
|
||||
*/
|
||||
package se.su.dsv.scipro.testdata;
|
65
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/GroupCreationUXImprovement.java
vendored
Normal file
65
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/GroupCreationUXImprovement.java
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
package se.su.dsv.scipro.testdata.populators;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.springframework.stereotype.Service;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.testdata.BaseData;
|
||||
import se.su.dsv.scipro.testdata.Factory;
|
||||
import se.su.dsv.scipro.testdata.TestDataPopulator;
|
||||
|
||||
@Service
|
||||
public class GroupCreationUXImprovement implements TestDataPopulator {
|
||||
|
||||
private static final String[] STUDENT_NAMES = { "Alice", "Bob", "Charlie", "David", "Emma" };
|
||||
|
||||
private final ProjectService projectService;
|
||||
|
||||
@Inject
|
||||
public GroupCreationUXImprovement(ProjectService projectService) {
|
||||
this.projectService = projectService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate(BaseData baseData, Factory factory) {
|
||||
User supervisor = factory.createSupervisor("Evan");
|
||||
List<User> students = createStudents(factory);
|
||||
for (int i = 1; i <= 20; i++) {
|
||||
projectService.save(createProject(baseData, i, supervisor, students));
|
||||
}
|
||||
}
|
||||
|
||||
private List<User> createStudents(Factory factory) {
|
||||
return Arrays.stream(STUDENT_NAMES).map(factory::createAuthor).toList();
|
||||
}
|
||||
|
||||
private Project createProject(BaseData baseData, int i, User supervisor, List<User> students) {
|
||||
User author1 = students.get(i % students.size());
|
||||
User author2 = students.get((i + 1) % students.size());
|
||||
|
||||
String title = "Test project " + i;
|
||||
if (i % 6 == 0) {
|
||||
title = title + " with a very long title that makes the project special";
|
||||
}
|
||||
|
||||
ProjectType projectType =
|
||||
switch (i % 3) {
|
||||
case 1 -> baseData.magister();
|
||||
case 2 -> baseData.master();
|
||||
default -> baseData.bachelor();
|
||||
};
|
||||
return Project.builder()
|
||||
.title(title)
|
||||
.projectType(projectType)
|
||||
.startDate(LocalDate.now())
|
||||
.headSupervisor(supervisor)
|
||||
.projectParticipants(Set.of(author1, author2))
|
||||
.build();
|
||||
}
|
||||
}
|
68
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/PartnerTypeExemption.java
vendored
Normal file
68
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/PartnerTypeExemption.java
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
package se.su.dsv.scipro.testdata.populators;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
import org.springframework.stereotype.Service;
|
||||
import se.su.dsv.scipro.match.ApplicationPeriod;
|
||||
import se.su.dsv.scipro.match.ApplicationPeriodService;
|
||||
import se.su.dsv.scipro.match.Idea;
|
||||
import se.su.dsv.scipro.match.IdeaService;
|
||||
import se.su.dsv.scipro.match.Target;
|
||||
import se.su.dsv.scipro.match.TargetService;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.testdata.BaseData;
|
||||
import se.su.dsv.scipro.testdata.Factory;
|
||||
import se.su.dsv.scipro.testdata.TestDataPopulator;
|
||||
|
||||
@Service
|
||||
public class PartnerTypeExemption implements TestDataPopulator {
|
||||
|
||||
private final ApplicationPeriodService applicationPeriodService;
|
||||
private final IdeaService ideaService;
|
||||
private final TargetService targetService;
|
||||
|
||||
@Inject
|
||||
public PartnerTypeExemption(
|
||||
ApplicationPeriodService applicationPeriodService,
|
||||
IdeaService ideaService,
|
||||
TargetService targetService
|
||||
) {
|
||||
this.applicationPeriodService = applicationPeriodService;
|
||||
this.ideaService = ideaService;
|
||||
this.targetService = targetService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate(BaseData baseData, Factory factory) {
|
||||
factory.createAuthor("Oskar");
|
||||
|
||||
User johan = factory.createAuthor("Johan");
|
||||
johan.setDegreeType(baseData.master().getDegreeType());
|
||||
|
||||
User supervisor = factory.createSupervisor("Elsa");
|
||||
|
||||
ApplicationPeriod applicationPeriod = new ApplicationPeriod("Supervisor ideas");
|
||||
applicationPeriod.setStartDate(LocalDate.now());
|
||||
applicationPeriod.setEndDate(LocalDate.now().plusDays(14));
|
||||
applicationPeriod.setCourseStartDateTime(LocalDateTime.now().plusDays(15));
|
||||
applicationPeriod.setProjectTypes(Set.of(baseData.bachelor()));
|
||||
applicationPeriodService.save(applicationPeriod);
|
||||
|
||||
Target target = targetService.findOne(applicationPeriod, supervisor, baseData.bachelor());
|
||||
target.setTarget(10);
|
||||
targetService.save(target);
|
||||
|
||||
Idea idea = new Idea();
|
||||
idea.setPublished(true);
|
||||
idea.setTitle("The next gen AI 2.0 turbo edition");
|
||||
idea.setPrerequisites("Hacker experience");
|
||||
idea.setDescription("Better than all the rest");
|
||||
idea.setProjectType(baseData.bachelor());
|
||||
idea.setApplicationPeriod(applicationPeriod);
|
||||
idea.setResearchArea(baseData.researchArea().researchArea());
|
||||
|
||||
ideaService.saveSupervisorIdea(idea, supervisor, baseData.researchArea().keywords(), true);
|
||||
}
|
||||
}
|
13
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/package-info.java
vendored
Normal file
13
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/package-info.java
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Contains classes that populate the database with test data.
|
||||
* <p>
|
||||
* Prefer to use methods on the various services to create data, rather than directly interacting with the database
|
||||
* using an {@link jakarta.persistence.EntityManager}. This is to make sure all business rules are enforced and that
|
||||
* any additional logic is executed such as sending notifications or calculating statistics.
|
||||
*
|
||||
* @see se.su.dsv.scipro.testdata how to add new populators
|
||||
* @see se.su.dsv.scipro.testdata.TestDataPopulator
|
||||
* @see se.su.dsv.scipro.testdata.BaseData
|
||||
* @see se.su.dsv.scipro.testdata.Factory
|
||||
*/
|
||||
package se.su.dsv.scipro.testdata.populators;
|
1
test-data/src/main/resources/META-INF/services/se.su.dsv.scipro.war.PluginConfiguration
Normal file
1
test-data/src/main/resources/META-INF/services/se.su.dsv.scipro.war.PluginConfiguration
Normal file
@ -0,0 +1 @@
|
||||
se.su.dsv.scipro.testdata.TestDataConfiguration
|
@ -123,13 +123,7 @@ public class AdminApplicationPeriodsPanel extends Panel {
|
||||
item.add(
|
||||
new DisplayMultiplesPanel<>(
|
||||
s,
|
||||
new ListAdapterModel<>(
|
||||
LambdaModel.of(
|
||||
iModel,
|
||||
ApplicationPeriod::getProjectTypes,
|
||||
ApplicationPeriod::setProjectTypes
|
||||
)
|
||||
)
|
||||
new ListAdapterModel<>(iModel.map(ApplicationPeriod::getProjectTypes))
|
||||
) {
|
||||
@Override
|
||||
public Component getComponent(String componentId, IModel<ProjectType> t) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package se.su.dsv.scipro.datatables.project;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.*;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
@ -33,7 +32,6 @@ import se.su.dsv.scipro.components.datatables.MultipleUsersColumn;
|
||||
import se.su.dsv.scipro.components.datatables.UserColumn;
|
||||
import se.su.dsv.scipro.dataproviders.FilteredDataProvider;
|
||||
import se.su.dsv.scipro.datatables.AjaxCheckboxWrapper;
|
||||
import se.su.dsv.scipro.notifications.NotificationController;
|
||||
import se.su.dsv.scipro.profile.UserLinkPanel;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
@ -45,7 +43,6 @@ import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ProjectTypeService;
|
||||
import se.su.dsv.scipro.system.ResearchArea;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.system.UserService;
|
||||
import se.su.dsv.scipro.util.PageParameterKeys;
|
||||
|
||||
public class ProjectDataPanel extends Panel {
|
||||
@ -170,6 +167,11 @@ public class ProjectDataPanel extends Panel {
|
||||
) {
|
||||
cellItem.add(new ReviewerColumnCell(componentId, rowModel));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IModel<?> getDataModel(IModel<Project> rowModel) {
|
||||
return rowModel.map(Project::getReviewer).map(User::getFullName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -22,12 +22,7 @@ public class AddTargetLinkPanel extends Panel {
|
||||
public AddTargetLinkPanel(String id, final IModel<ApplicationPeriod> model) {
|
||||
super(id, model);
|
||||
add(
|
||||
new ListView<>(
|
||||
"list",
|
||||
new ListAdapterModel<>(
|
||||
LambdaModel.of(model, ApplicationPeriod::getProjectTypes, ApplicationPeriod::setProjectTypes)
|
||||
)
|
||||
) {
|
||||
new ListView<>("list", new ListAdapterModel<>(model.map(ApplicationPeriod::getProjectTypes))) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<ProjectType> item) {
|
||||
item.add(new Label("pc", item.getModelObject().getName()));
|
||||
|
@ -42,6 +42,13 @@
|
||||
</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="col-lg-offset-4 col-lg-4">
|
||||
<div class="form-check">
|
||||
|
@ -131,6 +131,17 @@ public class AdminFinalSeminarSettingsPage extends AbstractAdminSystemPage {
|
||||
Integer.class
|
||||
)
|
||||
);
|
||||
add(
|
||||
new RequiredTextField<>(
|
||||
"work_days_to_fix_requested_improvements_to_opposition_report",
|
||||
LambdaModel.of(
|
||||
model,
|
||||
FinalSeminarSettings::getWorkDaysToFixRequestedImprovementsToOppositionReport,
|
||||
FinalSeminarSettings::setWorkDaysToFixRequestedImprovementsToOppositionReport
|
||||
),
|
||||
Integer.class
|
||||
)
|
||||
);
|
||||
add(
|
||||
new CheckBox(
|
||||
SEMINAR_PDF,
|
||||
|
@ -6,6 +6,7 @@ import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
import se.su.dsv.scipro.components.DateLabel;
|
||||
import se.su.dsv.scipro.data.enums.DateStyle;
|
||||
import se.su.dsv.scipro.grading.ReportPdfResource;
|
||||
import se.su.dsv.scipro.report.Report;
|
||||
|
||||
@ -32,7 +33,11 @@ public class DownloadPdfReportPanel extends Panel {
|
||||
add(resourceLink);
|
||||
resourceLink.add(new Label(PDF_LABEL, reportPdfResource.getFileName()));
|
||||
resourceLink.add(
|
||||
new DateLabel(PDF_UPLOAD_DATE, LambdaModel.of(report, Report::getLastModified, Report::setLastModified))
|
||||
new DateLabel(
|
||||
PDF_UPLOAD_DATE,
|
||||
LambdaModel.of(report, Report::getLastModified, Report::setLastModified),
|
||||
DateStyle.DATETIME
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,29 +6,36 @@
|
||||
<div class="col-lg-8">
|
||||
<h4>Opposition report</h4>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-lg-8">
|
||||
<div class="help-box">
|
||||
Använd bedömningskriterierna i denna rapport och skriv dina synpunkter som opponent i fritextfälten
|
||||
under varje kriterium. Du gör dock ingen poängbedömning men
|
||||
är fri att skriva så mycket som du önskar på varje bedömningskriterium.
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-box mb-3">
|
||||
Use the assessment criteria in this report and write your views as an opponent in the text
|
||||
fields under each criterion. However, you do not make a point assessment but are free to
|
||||
write as much as you wish on each criterion.
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<strong>Final seminar file:</strong> <span wicket:id="thesisFile"></span>
|
||||
</div>
|
||||
|
||||
<div wicket:id="fillOutReport">
|
||||
<strong>Thesis summary</strong>
|
||||
|
||||
<wicket:enclosure child="improvements_requested_comment">
|
||||
<div class="alert alert-info">
|
||||
<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>
|
||||
<label>
|
||||
<textarea class="form-control mb-4" rows="8" wicket:id="thesisSummary"></textarea>
|
||||
<p class="mb-0" wicket:id="improvements_requested_comment"></p>
|
||||
</div>
|
||||
</wicket:enclosure>
|
||||
|
||||
<div wicket:id="fillOutReport">
|
||||
<label wicket:for="thesisSummary">
|
||||
Thesis summary
|
||||
</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>
|
||||
|
@ -1,8 +1,11 @@
|
||||
package se.su.dsv.scipro.finalseminar;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Optional;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.TextArea;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
@ -24,7 +27,7 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements
|
||||
public static final String FILL_OUT_REPORT = "fillOutReport";
|
||||
|
||||
@Inject
|
||||
private FinalSeminarOppositionRepo finalSeminarOppositionRepo;
|
||||
private FinalSeminarOppositionService finalSeminarOppositionService;
|
||||
|
||||
@Inject
|
||||
private OppositionReportService oppositionReportService;
|
||||
@ -35,13 +38,15 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements
|
||||
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);
|
||||
}
|
||||
|
||||
final IModel<OppositionReport> report = getOppositionReport(opposition);
|
||||
final IModel<OppositionReport> report = opposition.map(Opposition::report);
|
||||
|
||||
add(
|
||||
new ViewAttachmentPanel(
|
||||
@ -50,8 +55,35 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements
|
||||
)
|
||||
);
|
||||
|
||||
IModel<Opposition.ImprovementsNeeded> improvements = opposition
|
||||
.map(Opposition::improvementsNeeded)
|
||||
.map(OppositionReportPage::orNull);
|
||||
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<>(
|
||||
THESIS_SUMMARY,
|
||||
@ -71,18 +103,13 @@ public class OppositionReportPage extends AbstractProjectDetailsPage implements
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setEnabled(opposition.getUser().equals(SciProSession.get().getUser()));
|
||||
setEnabled(opposition.getObject().user().equals(SciProSession.get().getUser()));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private IModel<OppositionReport> getOppositionReport(final FinalSeminarOpposition opposition) {
|
||||
return new LoadableDetachableModel<>() {
|
||||
@Override
|
||||
protected OppositionReport load() {
|
||||
return oppositionReportService.findOrCreateReport(opposition);
|
||||
}
|
||||
};
|
||||
private static <A> A orNull(Optional<A> optional) {
|
||||
return optional.orElse(null);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<div wicket:id="container">
|
||||
<div wicket:id="opponents">
|
||||
<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>
|
||||
<div wicket:id="report"></div>
|
||||
</div>
|
||||
@ -19,13 +19,31 @@
|
||||
<div class="col-lg-5">
|
||||
|
||||
<form wicket:id="form">
|
||||
<div class="card mb-3 bg-info text-white">
|
||||
<wicket:message key="criteria"/>
|
||||
<div class="card mb-3 text-bg-info">
|
||||
<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>
|
||||
|
||||
<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">
|
||||
<label>Points:</label>
|
||||
<input type="text" class="form-control gradingPoints" wicket:id="points"/>
|
||||
<select class="form-select" wicket:id="points"></select>
|
||||
</div>
|
||||
|
||||
<label>Motivation:</label>
|
||||
@ -34,6 +52,25 @@
|
||||
<button wicket:id="submit" type="submit" class="btn btn-sm btn-success">
|
||||
<wicket:message key="submit"/>
|
||||
</button>
|
||||
<a class="btn btn-outline-secondary btn-sm" wicket:id="request_improvements">
|
||||
Request improvements
|
||||
</a>
|
||||
</form>
|
||||
|
||||
<form wicket:id="request_improvements">
|
||||
<p>
|
||||
Once you request improvements the student have a limited time to make the requested changes.
|
||||
If they do not make the requested improvements in time, they will get an automatic failing grade.
|
||||
You will be notified when they've submitted a new report.
|
||||
</p>
|
||||
<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>
|
||||
|
||||
<div wicket:id="gradeContainer">
|
||||
|
@ -1,19 +1,23 @@
|
||||
package se.su.dsv.scipro.finalseminar;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
|
||||
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.DropDownChoice;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.form.FormComponent;
|
||||
import org.apache.wicket.markup.html.form.LambdaChoiceRenderer;
|
||||
import org.apache.wicket.markup.html.form.TextArea;
|
||||
import org.apache.wicket.markup.html.form.TextField;
|
||||
import org.apache.wicket.markup.html.link.Link;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
import org.apache.wicket.markup.html.list.ListView;
|
||||
@ -22,14 +26,14 @@ import org.apache.wicket.markup.html.panel.FeedbackPanel;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.model.ResourceModel;
|
||||
import org.apache.wicket.validation.validator.RangeValidator;
|
||||
import org.apache.wicket.validation.validator.StringValidator;
|
||||
import se.su.dsv.scipro.components.ListAdapterModel;
|
||||
import se.su.dsv.scipro.components.StatelessModel;
|
||||
import se.su.dsv.scipro.profile.UserLinkPanel;
|
||||
import se.su.dsv.scipro.report.GradingReportService;
|
||||
import se.su.dsv.scipro.report.OppositionReportService;
|
||||
import se.su.dsv.scipro.report.SupervisorGradingReport;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.session.SciProSession;
|
||||
import se.su.dsv.scipro.system.ProjectModule;
|
||||
@ -43,8 +47,6 @@ public class SeminarOppositionPanel extends Panel {
|
||||
public static final String REMOVE = "remove";
|
||||
public static final String FORM = "form";
|
||||
public static final String POINTS = "points";
|
||||
public static final int MIN_POINTS = 0;
|
||||
public static final int MAX_POINTS = 2;
|
||||
public static final String GRADING_FEEDBACK = "gradingFeedback";
|
||||
public static final int FEEDBACK_MAX_LENGTH = 2000;
|
||||
public static final String SUBMIT = "submit";
|
||||
@ -55,18 +57,12 @@ public class SeminarOppositionPanel extends Panel {
|
||||
@Inject
|
||||
private FinalSeminarOppositionService finalSeminarOppositionService;
|
||||
|
||||
@Inject
|
||||
private EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
private ProjectTypeService projectTypeService;
|
||||
|
||||
@Inject
|
||||
private FinalSeminarService finalSeminarService;
|
||||
|
||||
@Inject
|
||||
private GradingReportService gradingReportService;
|
||||
|
||||
@Inject
|
||||
private OppositionReportService oppositionReportService;
|
||||
|
||||
@ -75,6 +71,9 @@ public class SeminarOppositionPanel extends Panel {
|
||||
private final WebMarkupContainer oppositionContainer;
|
||||
private final ListView<FinalSeminarOpposition> opponents;
|
||||
|
||||
private FinalSeminarOppositionForm gradeForm;
|
||||
private RequestImprovementsForm requestImprovementsForm;
|
||||
|
||||
public SeminarOppositionPanel(String id, final IModel<FinalSeminar> seminar) {
|
||||
super(id, seminar);
|
||||
this.seminar = seminar;
|
||||
@ -107,6 +106,12 @@ public class SeminarOppositionPanel extends Panel {
|
||||
|
||||
private ListView<FinalSeminarOpposition> getOpponentsList(final IModel<List<FinalSeminarOpposition>> oppositions) {
|
||||
return new ListView<>(OPPONENTS, oppositions) {
|
||||
{
|
||||
// Need to reuse child list items since they contain form components
|
||||
// and if they're recreated all the state and error messages are lost
|
||||
setReuseItems(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateItem(final ListItem<FinalSeminarOpposition> item) {
|
||||
final FinalSeminarOpposition opposition = item.getModelObject();
|
||||
@ -121,7 +126,14 @@ public class SeminarOppositionPanel extends Panel {
|
||||
|
||||
item.add(getRemoveLink(item.getModel()));
|
||||
|
||||
item.add(getFinalSeminarOppositionForm(item));
|
||||
gradeForm = getFinalSeminarOppositionForm(item);
|
||||
gradeForm.setOutputMarkupPlaceholderTag(true);
|
||||
item.add(gradeForm);
|
||||
|
||||
requestImprovementsForm = new RequestImprovementsForm("request_improvements", item.getModel());
|
||||
requestImprovementsForm.setVisible(false);
|
||||
requestImprovementsForm.setOutputMarkupPlaceholderTag(true);
|
||||
item.add(requestImprovementsForm);
|
||||
|
||||
if (gradingModuleIsOnForProjectType()) {
|
||||
item.add(new SeminarOppositionReportPanel("report", item.getModel()));
|
||||
@ -211,29 +223,47 @@ public class SeminarOppositionPanel extends Panel {
|
||||
|
||||
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) {
|
||||
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,
|
||||
LambdaModel.of(
|
||||
finalSeminarOpposition,
|
||||
FinalSeminarOpposition::getPoints,
|
||||
FinalSeminarOpposition::setPoints
|
||||
)
|
||||
)
|
||||
.add(RangeValidator.range(MIN_POINTS, MAX_POINTS))
|
||||
.setType(Integer.class)
|
||||
.setRequired(true);
|
||||
pointsModel,
|
||||
criteriaModel.map(OppositionCriteria::pointsAvailable),
|
||||
new LambdaChoiceRenderer<>(OppositionCriteria.Point::value)
|
||||
);
|
||||
pointsField.setRequired(true);
|
||||
add(pointsField);
|
||||
|
||||
TextArea<String> feedback = new TextArea<>(
|
||||
GRADING_FEEDBACK,
|
||||
LambdaModel.of(
|
||||
finalSeminarOpposition,
|
||||
FinalSeminarOpposition::getFeedback,
|
||||
FinalSeminarOpposition::setFeedback
|
||||
)
|
||||
);
|
||||
TextArea<String> feedback = new TextArea<>(GRADING_FEEDBACK, feedbackModel);
|
||||
feedback.add(StringValidator.maximumLength(FEEDBACK_MAX_LENGTH));
|
||||
feedback.setRequired(true);
|
||||
add(feedback);
|
||||
@ -242,33 +272,19 @@ public class SeminarOppositionPanel extends Panel {
|
||||
new AjaxSubmitLink(SUBMIT) {
|
||||
@Override
|
||||
protected void onSubmit(AjaxRequestTarget target) {
|
||||
if (getModelObject().getPoints().equals(0)) {
|
||||
finalSeminarOpposition.getObject().setGrade(FinalSeminarGrade.NOT_APPROVED);
|
||||
eventBus.post(new OppositionFailedEvent(finalSeminarOpposition.getObject()));
|
||||
} else {
|
||||
finalSeminarOpposition.getObject().setGrade(FinalSeminarGrade.APPROVED);
|
||||
eventBus.post(new OppositionApprovedEvent(finalSeminarOpposition.getObject()));
|
||||
}
|
||||
finalSeminarOppositionService.save(finalSeminarOpposition.getObject());
|
||||
boolean updated = true;
|
||||
if (gradingModuleIsOnForProjectType()) {
|
||||
SupervisorGradingReport report = gradingReportService.getSupervisorGradingReport(
|
||||
finalSeminarOpposition.getObject().getProject(),
|
||||
finalSeminarOpposition.getObject().getUser()
|
||||
);
|
||||
updated = gradingReportService.updateOppositionCriteria(
|
||||
report,
|
||||
finalSeminarOpposition.getObject()
|
||||
try {
|
||||
finalSeminarOppositionService.gradeOpponent(
|
||||
finalSeminarOpposition.getObject(),
|
||||
pointsModel.getObject().value(),
|
||||
feedbackModel.getObject()
|
||||
);
|
||||
success(getString("feedback.opponent.updated", finalSeminarOpposition));
|
||||
target.add(feedbackPanel);
|
||||
target.add(oppositionContainer);
|
||||
} catch (PointNotValidException e) {
|
||||
error(getString("point.not.valid"));
|
||||
target.add(feedbackPanel);
|
||||
}
|
||||
success(
|
||||
getString(
|
||||
updated ? "feedback.opponent.updated" : "feedback.opponent.not.updated",
|
||||
finalSeminarOpposition
|
||||
)
|
||||
);
|
||||
target.add(feedbackPanel);
|
||||
target.add(oppositionContainer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -277,17 +293,47 @@ 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
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
FinalSeminarOpposition opposition = getModelObject();
|
||||
setVisibilityAllowed(
|
||||
startDateHasPassed() &&
|
||||
getModelObject().getPoints() == null &&
|
||||
getModelObject().getFeedback() == null &&
|
||||
opposition.getPoints() == null &&
|
||||
opposition.getFeedback() == null &&
|
||||
isHeadSupervisor()
|
||||
);
|
||||
boolean hasRequestedImprovements = opposition.getImprovementsRequestedAt() != null;
|
||||
boolean reportIsSubmitted =
|
||||
opposition.getOppositionReport() != null && opposition.getOppositionReport().isSubmitted();
|
||||
setEnabled(!hasRequestedImprovements || reportIsSubmitted);
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,4 +344,47 @@ public class SeminarOppositionPanel extends Panel {
|
||||
private boolean hasSubmittedOppositionReport(FinalSeminarOpposition opposition) {
|
||||
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
|
||||
opponents.form.points.RangeValidator.range= Points assigned must be between ${minimum} and ${maximum}
|
||||
feedback.opponent.updated= Opponent ${user.fullName} feedback updated.
|
||||
point.not.valid=You need to assign points from the available selection.
|
||||
feedback.opponent.not.updated= Opponent ${user.fullName} feedback updated. Point and motivation could not be transferred to the students final grading report since the opponents supervisor already filled it in.
|
||||
criteria= As the supervisor on this final seminar you are also required to grade the opponents opposition report.\
|
||||
<\br><\br>Requirement for 1 point: <\br>That the opposition report provides a short summary of the evaluated thesis, that it deliberates about the scientific basis, originality, \
|
||||
significance, and formulation of the problem and research question, as well as that it contains clear suggestions for improvements. \
|
||||
<\br><\br>For 2 points the following is also required: \
|
||||
<\br>That the opposition report thoroughly and in a well-balanced way describes from numerous aspects the strengths and weaknesses of the evaluated thesis and that it \
|
||||
offers clear and well- motivated suggestions for improvements.
|
||||
criteria= As the supervisor on this final seminar you are also required to grade the opponents opposition report.
|
||||
opposition.report= Opposition report:
|
||||
removed= Opponent ${user.fullName} successfully removed
|
||||
opposition.report.removed= Opposition report successfully removed
|
||||
are.you.sure= Are you sure you want to remove this opponent report?
|
||||
no.opponents= There are no opponents registered yet.
|
||||
noOppositionReportYet= No opposition report has been submitted yet.
|
||||
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">
|
||||
<wicket:enclosure child="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>
|
||||
<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">
|
||||
Report attachment: <span wicket:id="downloadAttachment"></span>
|
||||
</wicket:enclosure>
|
||||
|
@ -70,6 +70,19 @@ public class SeminarOppositionReportPanel extends GenericPanel<FinalSeminarOppos
|
||||
wmc.add(getDeleteOpponentReportLink(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) {
|
||||
|
@ -7,9 +7,10 @@
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<div class="messageWrap">
|
||||
<div class="forumBlueBackground">
|
||||
<div class="forumBlueBackground d-flex justify-content-between">
|
||||
<!-- DATE ROW-->
|
||||
<wicket:container wicket:id="dateCreated"/>
|
||||
<span wicket:id="dateCreated"></span>
|
||||
<button wicket:id="delete" class="btn btn-sm btn-outline-danger ms-auto">Delete</button>
|
||||
</div>
|
||||
<div class="forumGrayBackground">
|
||||
<div class="vertAlign">
|
||||
|
@ -1,7 +1,9 @@
|
||||
package se.su.dsv.scipro.forum.panels.threaded;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.link.Link;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
@ -11,9 +13,11 @@ import se.su.dsv.scipro.components.ListAdapterModel;
|
||||
import se.su.dsv.scipro.components.SmarterLinkMultiLineLabel;
|
||||
import se.su.dsv.scipro.data.enums.DateStyle;
|
||||
import se.su.dsv.scipro.file.FileReference;
|
||||
import se.su.dsv.scipro.forum.BasicForumService;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.profile.UserLinkPanel;
|
||||
import se.su.dsv.scipro.repository.panels.ViewAttachmentPanel;
|
||||
import se.su.dsv.scipro.session.SciProSession;
|
||||
|
||||
public class ForumPostPanel extends Panel {
|
||||
|
||||
@ -22,6 +26,9 @@ public class ForumPostPanel extends Panel {
|
||||
public static final String CONTENT = "content";
|
||||
public static final String ATTACHMENT = "attachment";
|
||||
|
||||
@Inject
|
||||
private BasicForumService basicForumService;
|
||||
|
||||
public ForumPostPanel(String id, final IModel<ForumPost> model) {
|
||||
super(id);
|
||||
add(new UserLinkPanel(POSTED_BY, LambdaModel.of(model, ForumPost::getPostedBy, ForumPost::setPostedBy)));
|
||||
@ -62,5 +69,28 @@ public class ForumPostPanel extends Panel {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add(
|
||||
new Link<>("delete", model) {
|
||||
@Override
|
||||
public void onClick() {
|
||||
ForumPost post = getModelObject();
|
||||
basicForumService.deletePost(post);
|
||||
onPostDeleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(allowDeletion() && basicForumService.canDelete(getModelObject()));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected boolean allowDeletion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onPostDeleted() {}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
package se.su.dsv.scipro.forum.panels.threaded;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
@ -15,6 +14,7 @@ import org.apache.wicket.model.LambdaModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import se.su.dsv.scipro.components.DateLabel;
|
||||
import se.su.dsv.scipro.data.enums.DateStyle;
|
||||
import se.su.dsv.scipro.forum.BasicForumService;
|
||||
import se.su.dsv.scipro.forum.Discussable;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumThread;
|
||||
@ -23,6 +23,9 @@ import se.su.dsv.scipro.system.User;
|
||||
|
||||
public class ThreadsOverviewPanel<A> extends Panel {
|
||||
|
||||
@Inject
|
||||
private BasicForumService basicForumService;
|
||||
|
||||
public ThreadsOverviewPanel(
|
||||
final String id,
|
||||
final IModel<List<A>> model,
|
||||
@ -41,7 +44,7 @@ public class ThreadsOverviewPanel<A> extends Panel {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisibilityAllowed(discussion.getObject().hasAttachments());
|
||||
setVisibilityAllowed(basicForumService.hasAttachments(discussion.getObject()));
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -80,7 +83,7 @@ public class ThreadsOverviewPanel<A> extends Panel {
|
||||
BookmarkablePageLink<Void> newThreadLink(String id, IModel<A> thread);
|
||||
}
|
||||
|
||||
private static class LastPostColumn extends WebMarkupContainer {
|
||||
private class LastPostColumn extends WebMarkupContainer {
|
||||
|
||||
public LastPostColumn(String id, final IModel<ForumThread> model) {
|
||||
super(id);
|
||||
@ -110,10 +113,7 @@ public class ThreadsOverviewPanel<A> extends Panel {
|
||||
return new LoadableDetachableModel<>() {
|
||||
@Override
|
||||
protected ForumPost load() {
|
||||
return Collections.max(
|
||||
model.getObject().getPosts(),
|
||||
Comparator.comparing(ForumPost::getDateCreated).thenComparing(ForumPost::getId)
|
||||
);
|
||||
return basicForumService.getLastPost(model.getObject());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -58,7 +58,21 @@ public class ViewForumThreadPanel<A> extends GenericPanel<A> {
|
||||
new ListView<>(POST_LIST, new PostProvider()) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<ForumPost> item) {
|
||||
item.add(new ForumPostPanel(POST, item.getModel()));
|
||||
ListView<ForumPost> listView = this;
|
||||
item.add(
|
||||
new ForumPostPanel(POST, item.getModel()) {
|
||||
@Override
|
||||
protected boolean allowDeletion() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostDeleted() {
|
||||
// Refresh the list of posts
|
||||
listView.detach();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -5,30 +5,26 @@
|
||||
</head>
|
||||
<body>
|
||||
<wicket:border>
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div wicket:id="save"></div>
|
||||
<form wicket:id="form">
|
||||
<div wicket:id="feedbackPanel"></div>
|
||||
<wicket:body/>
|
||||
<div wicket:id="criteria">
|
||||
<strong><span wicket:id="title"></span></strong>
|
||||
<div wicket:id="save"></div>
|
||||
<form wicket:id="form">
|
||||
<div wicket:id="feedbackPanel"></div>
|
||||
<wicket:body/>
|
||||
<div wicket:id="criteria">
|
||||
<strong><span wicket:id="title"></span></strong>
|
||||
|
||||
<p><span wicket:id="description" class="gradingCriteria"></span></p>
|
||||
<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>
|
||||
<p><span wicket:id="description" class="gradingCriteria"></span></p>
|
||||
<textarea class="form-control mb-4" rows="8" cols="5" wicket:id="feedback"></textarea>
|
||||
</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>
|
||||
</body>
|
||||
</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.Criterion;
|
||||
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.system.Language;
|
||||
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 GRADING_CRITERIA = "criteria";
|
||||
@ -42,20 +42,20 @@ public class FillOutReportPanel<T extends OppositionReport> extends Border {
|
||||
public static final String FEEDBACK_PANEL = "feedbackPanel";
|
||||
|
||||
@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);
|
||||
ReportForm form = new ReportForm(FORM, model);
|
||||
addToBorder(new ScrollingSaveButtonPanel(SAVE, form));
|
||||
addToBorder(form);
|
||||
}
|
||||
|
||||
private class ReportForm extends StatelessForm<T> {
|
||||
private class ReportForm extends StatelessForm<OppositionReport> {
|
||||
|
||||
private final FileUploadField attachment;
|
||||
|
||||
public ReportForm(String id, final IModel<T> model) {
|
||||
public ReportForm(String id, final IModel<OppositionReport> model) {
|
||||
super(id, model);
|
||||
add(new ComponentFeedbackPanel(FEEDBACK_PANEL, this));
|
||||
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);
|
||||
add(new JavascriptEventConfirmation("click", new ResourceModel("delete.attachment")));
|
||||
}
|
||||
|
@ -2,85 +2,50 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<form wicket:id="form">
|
||||
<div class="line-length-limit">
|
||||
<form wicket:id="form">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div wicket:id="feedback"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div wicket:id="feedback"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-5 col-md-10">
|
||||
<label wicket:for="title">Title: </label>
|
||||
<input type="text" wicket:id="title" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label wicket:for="title" class="form-label">Title</label>
|
||||
<input type="text" wicket:id="title" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-5 col-md-10">
|
||||
<label wicket:for="description">Description: </label>
|
||||
<textarea wicket:id="description" class="form-control"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label wicket:for="description" class="form-label">Description</label>
|
||||
<textarea wicket:id="description" class="form-control"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-5 col-md-10">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" wicket:id="active" type="checkbox"/>
|
||||
<label class="form-check-label" wicket:for="active">Active</label>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" wicket:id="active" type="checkbox"/>
|
||||
<label class="form-check-label" wicket:for="active">Active</label>
|
||||
</div>
|
||||
|
||||
<fieldset class="mb-3">
|
||||
<legend>Projects</legend>
|
||||
<div class="group-project-grid">
|
||||
<label wicket:id="available_projects">
|
||||
<div>
|
||||
<input class="form-check-input mt-0" type="checkbox" wicket:id="selected">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div wicket:id="wmc">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-5 col-md-10">
|
||||
<strong>Add projects to group: </strong>
|
||||
<div wicket:id="projectTypes"></div>
|
||||
<select class="form-select" wicket:id="addProjects"></select>
|
||||
</div>
|
||||
<div>
|
||||
<h4 wicket:id="title"></h4>
|
||||
<span wicket:id="type"></span>
|
||||
<br>
|
||||
Started at <span wicket:id="start_date"></span>
|
||||
<div wicket:id="authors">
|
||||
<span wicket:id="author"></span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<strong>Projects in group: </strong>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Title</th>
|
||||
<th>Authors</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr wicket:id="projects">
|
||||
<td><span wicket:id="type"></span></td>
|
||||
<td><span wicket:id="title"></span></td>
|
||||
<td><div wicket:id="authors">
|
||||
<div wicket:id="author"></div>
|
||||
</div></td>
|
||||
<td><a wicket:id="remove"><span class="fa fa-times"></span></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div wicket:id="noProjects"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" class="btn btn-success" >Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</fieldset>
|
||||
<button type="submit" class="btn btn-success">Save</button>
|
||||
<button type="submit" wicket:id="save_and_close" class="btn btn-success">Save and close</button>
|
||||
<button type="submit" wicket:id="save_and_create" class="btn btn-success">Save and create another</button>
|
||||
<a wicket:id="cancel" class="btn btn-outline-secondary">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
</wicket:panel>
|
||||
</body>
|
||||
|
@ -2,11 +2,10 @@ package se.su.dsv.scipro.group;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.*;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.extensions.model.AbstractCheckBoxModel;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.*;
|
||||
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
|
||||
import org.apache.wicket.markup.html.list.ListItem;
|
||||
import org.apache.wicket.markup.html.list.ListView;
|
||||
import org.apache.wicket.markup.html.panel.FeedbackPanel;
|
||||
@ -14,10 +13,6 @@ import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.model.util.ListModel;
|
||||
import se.su.dsv.scipro.components.AjaxCheckBoxMultipleChoice;
|
||||
import se.su.dsv.scipro.components.AjaxDropDownChoice;
|
||||
import se.su.dsv.scipro.components.ListAdapterModel;
|
||||
import se.su.dsv.scipro.profile.UserLinkPanel;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
@ -25,8 +20,8 @@ import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.project.ProjectStatus;
|
||||
import se.su.dsv.scipro.project.ProjectTeamMemberRoles;
|
||||
import se.su.dsv.scipro.session.SciProSession;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ProjectTypeService;
|
||||
import se.su.dsv.scipro.supervisor.pages.SupervisorEditGroupPage;
|
||||
import se.su.dsv.scipro.supervisor.pages.SupervisorMyGroupsPage;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
public class EditGroupPanel extends Panel {
|
||||
@ -37,9 +32,6 @@ public class EditGroupPanel extends Panel {
|
||||
@Inject
|
||||
private GroupService groupService;
|
||||
|
||||
@Inject
|
||||
private ProjectTypeService projectTypeService;
|
||||
|
||||
public EditGroupPanel(String id, final IModel<Group> model) {
|
||||
super(id, model);
|
||||
add(new GroupForm("form", model));
|
||||
@ -47,18 +39,51 @@ public class EditGroupPanel extends Panel {
|
||||
|
||||
private class GroupForm extends Form<Group> {
|
||||
|
||||
private final AjaxDropDownChoice<Project> addProjects;
|
||||
private final ListView<Project> projects;
|
||||
private final List<Project> currentProjects;
|
||||
private final AjaxCheckBoxMultipleChoice<ProjectType> projectTypes;
|
||||
|
||||
public GroupForm(String form, final IModel<Group> model) {
|
||||
super(form, model);
|
||||
final FeedbackPanel feedbackPanel = new FeedbackPanel("feedback");
|
||||
feedbackPanel.setOutputMarkupId(true);
|
||||
add(feedbackPanel);
|
||||
|
||||
currentProjects = new ArrayList<>(getModelObject().getProjects());
|
||||
IModel<List<Project>> availableProjects = LoadableDetachableModel.of(() -> {
|
||||
Set<Project> projects = new HashSet<>();
|
||||
projects.addAll(getAllRelevantProjects());
|
||||
// Have to add the projects that are already in the group to the list of available projects
|
||||
// since they may not be included in the relevant projects if they're inactive or completed.
|
||||
// To allow them to be removed from the group, it will not be possible to add them again.
|
||||
projects.addAll(model.getObject().getProjects());
|
||||
return projects
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(Project::getStartDate).reversed().thenComparing(Project::getTitle))
|
||||
.toList();
|
||||
});
|
||||
add(
|
||||
new ListView<>("available_projects", availableProjects) {
|
||||
{
|
||||
// must re-use list items to maintain form component (checkboxes) state
|
||||
setReuseItems(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateItem(ListItem<Project> item) {
|
||||
CheckBox checkbox = new CheckBox("selected", new SelectProjectModel(model, item.getModel()));
|
||||
checkbox.setOutputMarkupId(true);
|
||||
item.add(checkbox);
|
||||
item.add(new Label("title", item.getModel().map(Project::getTitle)));
|
||||
item.add(new Label("type", item.getModel().map(Project::getProjectTypeName)));
|
||||
item.add(new Label("start_date", item.getModel().map(Project::getStartDate)));
|
||||
IModel<SortedSet<User>> authors = item.getModel().map(Project::getProjectParticipants);
|
||||
item.add(
|
||||
new ListView<>("authors", new ListAdapterModel<>(authors)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<User> item) {
|
||||
item.add(new UserLinkPanel("author", item.getModel()));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add(new RequiredTextField<>("title", LambdaModel.of(model, Group::getTitle, Group::setTitle)));
|
||||
add(new TextArea<>("description", LambdaModel.of(model, Group::getDescription, Group::setDescription)));
|
||||
@ -66,120 +91,64 @@ public class EditGroupPanel extends Panel {
|
||||
new CheckBox("active", LambdaModel.of(model, Group::isActive, Group::setActive)).setOutputMarkupId(true)
|
||||
);
|
||||
|
||||
final WebMarkupContainer wmc = new WebMarkupContainer("wmc");
|
||||
wmc.setOutputMarkupId(true);
|
||||
|
||||
projectTypes = projectTypeSelection(wmc);
|
||||
wmc.add(projectTypes);
|
||||
|
||||
addProjects = new AjaxDropDownChoice<>(
|
||||
"addProjects",
|
||||
new Model<>(),
|
||||
getSelectableProjects(currentProjects),
|
||||
new LambdaChoiceRenderer<>(Project::getTitle, Project::getId)
|
||||
) {
|
||||
@Override
|
||||
public void onNewSelection(AjaxRequestTarget target, Project objectSelected) {
|
||||
if (objectSelected != null && !currentProjects.contains(objectSelected)) {
|
||||
currentProjects.add(objectSelected);
|
||||
projects.setList(currentProjects);
|
||||
addProjects.setChoices(getSelectableProjects(currentProjects));
|
||||
target.add(wmc);
|
||||
}
|
||||
}
|
||||
};
|
||||
addProjects.setRequired(false);
|
||||
addProjects.setNullValid(true);
|
||||
wmc.add(addProjects);
|
||||
|
||||
projects = new ListView<>("projects", new ArrayList<>(currentProjects)) {
|
||||
@Override
|
||||
protected void populateItem(final ListItem<Project> item) {
|
||||
item.add(new Label("type", item.getModel().map(Project::getProjectTypeName)));
|
||||
item.add(new Label("title", item.getModel().map(Project::getTitle)));
|
||||
item.add(
|
||||
new ListView<>(
|
||||
"authors",
|
||||
new ListAdapterModel<>(
|
||||
getLoaded(item.getModelObject()).map(Project::getProjectParticipants)
|
||||
)
|
||||
) {
|
||||
@Override
|
||||
public void populateItem(ListItem<User> item) {
|
||||
item.add(new UserLinkPanel("author", item.getModel()));
|
||||
}
|
||||
}
|
||||
);
|
||||
item.add(
|
||||
new AjaxLink<>("remove", item.getModel()) {
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
currentProjects.remove(item.getModelObject());
|
||||
projects.setList(currentProjects);
|
||||
addProjects.setChoices(getSelectableProjects(currentProjects));
|
||||
target.add(wmc);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
wmc.add(projects);
|
||||
|
||||
wmc.add(
|
||||
new Label("noProjects", "None") {
|
||||
add(
|
||||
new SubmitLink("save_and_close") {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisibilityAllowed(currentProjects.isEmpty());
|
||||
public void onAfterSubmit() {
|
||||
setResponsePage(SupervisorMyGroupsPage.class);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add(wmc);
|
||||
}
|
||||
|
||||
private AjaxCheckBoxMultipleChoice<ProjectType> projectTypeSelection(final WebMarkupContainer wmc) {
|
||||
return new AjaxCheckBoxMultipleChoice<>(
|
||||
"projectTypes",
|
||||
projectTypeService.findAllActive(),
|
||||
projectTypeService.findAllActive(),
|
||||
new LambdaChoiceRenderer<>(ProjectType::getName, ProjectType::getId)
|
||||
) {
|
||||
@Override
|
||||
public void onUpdate(AjaxRequestTarget target) {
|
||||
addProjects.setChoices(getSelectableProjects(currentProjects));
|
||||
target.add(wmc);
|
||||
add(
|
||||
new SubmitLink("save_and_create") {
|
||||
@Override
|
||||
public void onAfterSubmit() {
|
||||
setResponsePage(SupervisorEditGroupPage.class);
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
add(new BookmarkablePageLink<>("cancel", SupervisorMyGroupsPage.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSubmit() {
|
||||
Group group = getModelObject();
|
||||
group.setProjects(new HashSet<>(currentProjects));
|
||||
groupService.save(group);
|
||||
info(getString("saved"));
|
||||
}
|
||||
|
||||
private ListModel<Project> getSelectableProjects(List<Project> currentProjects) {
|
||||
private List<Project> getAllRelevantProjects() {
|
||||
final ProjectService.Filter filter = new ProjectService.Filter();
|
||||
filter.setSupervisor(SciProSession.get().getUser());
|
||||
filter.setRoles(Collections.singleton(ProjectTeamMemberRoles.CO_SUPERVISOR));
|
||||
filter.setStatuses(Collections.singletonList(ProjectStatus.ACTIVE));
|
||||
filter.setProjectTypes(projectTypes.getModelObject());
|
||||
List<Project> all = projectService.findAll(filter);
|
||||
all.removeAll(currentProjects);
|
||||
all.remove(null);
|
||||
return new ListModel<>(all);
|
||||
return projectService.findAll(filter);
|
||||
}
|
||||
|
||||
private LoadableDetachableModel<Project> getLoaded(final Project project) {
|
||||
return new LoadableDetachableModel<>() {
|
||||
@Override
|
||||
protected Project load() {
|
||||
return projectService.findOne(project.getId());
|
||||
}
|
||||
};
|
||||
private static final class SelectProjectModel extends AbstractCheckBoxModel {
|
||||
|
||||
private final IModel<Group> groupModel;
|
||||
private final IModel<Project> projectModel;
|
||||
|
||||
public SelectProjectModel(IModel<Group> groupModel, IModel<Project> projectModel) {
|
||||
this.groupModel = groupModel;
|
||||
this.projectModel = projectModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected() {
|
||||
return groupModel.getObject().getProjects().contains(projectModel.getObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void select() {
|
||||
groupModel.getObject().getProjects().add(projectModel.getObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unselect() {
|
||||
groupModel.getObject().getProjects().remove(projectModel.getObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,9 @@ public class ExcelExporter extends AbstractDataExporter {
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
Object cellValue = columns.get(i).getDataModel(data).getObject();
|
||||
Cell cell = row.createCell(i);
|
||||
cell.setCellValue(String.valueOf(cellValue));
|
||||
if (cellValue != null) {
|
||||
cell.setCellValue(cellValue.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import org.apache.wicket.util.string.StringValueConversionException;
|
||||
import se.su.dsv.scipro.activityplan.ProjectActivityPlanPage;
|
||||
import se.su.dsv.scipro.activityplan.SupervisorActivityPlanPage;
|
||||
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.ProjectFinalSeminarPage;
|
||||
import se.su.dsv.scipro.finalseminar.ProjectOppositionPage;
|
||||
@ -217,6 +219,19 @@ public class NotificationLandingPage extends WebPage {
|
||||
} else if (
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -76,13 +76,7 @@ public class ProjectPartnerPage extends AbstractIdeaProjectPage implements MenuH
|
||||
}
|
||||
);
|
||||
final IModel<? extends List<ProjectType>> matchableTypes = getMatchableTypes(
|
||||
new ListAdapterModel<>(
|
||||
LambdaModel.of(
|
||||
applicationPeriod,
|
||||
ApplicationPeriod::getProjectTypes,
|
||||
ApplicationPeriod::setProjectTypes
|
||||
)
|
||||
)
|
||||
new ListAdapterModel<>(applicationPeriod.map(ApplicationPeriod::getProjectTypes))
|
||||
);
|
||||
panelContainer.add(
|
||||
new ListView<>("ads", matchableTypes) {
|
||||
|
@ -83,6 +83,9 @@ SeminarEvent.OPPOSITION_REPORT_UPLOADED = Opposition report created.
|
||||
SeminarEvent.THESIS_DELETED = Final seminar thesis deleted.
|
||||
SeminarEvent.THESIS_UPLOAD_REMIND = Authors reminded to upload final seminar thesis.
|
||||
SeminarEvent.CANCELLED = Final seminar cancelled.
|
||||
SeminarEvent.OPPOSITION_REPORT_SUBMITTED = Opposition report submitted.
|
||||
SeminarEvent.OPPOSITION_REPORT_IMPROVEMENTS_REQUESTED = Opposition report improvements requested.
|
||||
SeminarEvent.OPPOSITION_APPROVED = Opposition approved.
|
||||
|
||||
IdeaEvent.STATUS_CHANGE = Idea status changed.
|
||||
IdeaEvent.PARTNER_ACCEPT = Partner (author) accepted partnering idea.
|
||||
|
@ -607,3 +607,27 @@ th.wicket_orderUp, th.sorting_asc {
|
||||
.line-length-limit {
|
||||
max-width: 80em;
|
||||
}
|
||||
.group-project-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
grid-template-columns: repeat(auto-fill, minmax(30em, 1fr));
|
||||
}
|
||||
.group-project-grid > * {
|
||||
background: linear-gradient(to left, white 40%, var(--bs-success-bg-subtle) 60%) right;
|
||||
background-size: 250% 100%;
|
||||
transition: background 0.4s ease;
|
||||
cursor: pointer;
|
||||
border: 1px solid black;
|
||||
border-radius: 0.25em;
|
||||
display: flex;
|
||||
padding: 0.5em;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.group-project-grid > *:has(:checked) {
|
||||
background-position: left;
|
||||
}
|
||||
.group-project-grid label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
@ -327,9 +327,6 @@ public abstract class SciProTest {
|
||||
@Mock
|
||||
protected FinalSeminarUploadController finalSeminarUploadController;
|
||||
|
||||
@Mock
|
||||
protected FinalSeminarOppositionRepo finalSeminarOppositionRepo;
|
||||
|
||||
@Mock
|
||||
protected PlagiarismControl plagiarismControl;
|
||||
|
||||
|
@ -17,7 +17,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import se.su.dsv.scipro.SciProTest;
|
||||
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.GradingReportTemplate;
|
||||
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.ProjectType;
|
||||
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 CRITERTION_TITLE = "U1 Sammanfattning";
|
||||
|
||||
@Mock
|
||||
private ReportService reportService;
|
||||
|
||||
private FinalSeminarOpposition finalSeminarOpposition;
|
||||
private User user;
|
||||
private ProjectType bachelor;
|
||||
@ -76,10 +71,9 @@ public class OppositionReportPageTest extends SciProTest {
|
||||
Mockito.when(finalSeminarService.findByProject(opponentsProject)).thenReturn(finalSeminar);
|
||||
}
|
||||
|
||||
private void mockReport(ProjectType bachelor) {
|
||||
private OppositionReport mockReport(ProjectType bachelor) {
|
||||
GradingReportTemplate reportTemplate = createTemplate(bachelor);
|
||||
OppositionReport oppositionReport = reportTemplate.createOppositionReport(finalSeminarOpposition);
|
||||
Mockito.when(oppositionReportService.findOrCreateReport(finalSeminarOpposition)).thenReturn(oppositionReport);
|
||||
return reportTemplate.createOppositionReport(finalSeminarOpposition);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -104,14 +98,16 @@ public class OppositionReportPageTest extends SciProTest {
|
||||
public void disable_form_if_opposition_does_not_belong_to_logged_in_user() {
|
||||
mockReport(bachelor);
|
||||
long oppositionId = 4L;
|
||||
Mockito.when(finalSeminarOppositionRepo.findOne(oppositionId)).thenReturn(finalSeminarOpposition);
|
||||
Mockito.when(finalSeminarOppositionService.getOpposition(oppositionId)).thenReturn(
|
||||
new Opposition(user, mockReport(bachelor), Optional.empty())
|
||||
);
|
||||
startPage(oppositionId);
|
||||
tester.assertDisabled(FILL_OUT_REPORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void redirect_if_no_opposition_is_found_from_id() {
|
||||
Mockito.when(finalSeminarOppositionRepo.findOne(ArgumentMatchers.anyLong())).thenReturn(null);
|
||||
Mockito.when(finalSeminarOppositionService.getOpposition(ArgumentMatchers.anyLong())).thenReturn(null);
|
||||
startPage(1L);
|
||||
tester.assertRenderedPage(ProjectDetailsPage.class);
|
||||
}
|
||||
@ -138,7 +134,7 @@ public class OppositionReportPageTest extends SciProTest {
|
||||
formTester.submit();
|
||||
|
||||
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());
|
||||
}
|
||||
@ -156,7 +152,9 @@ public class OppositionReportPageTest extends SciProTest {
|
||||
private void startOppositionPage() {
|
||||
long oppositionId = 4L;
|
||||
setLoggedInAs(user);
|
||||
Mockito.when(finalSeminarOppositionRepo.findOne(oppositionId)).thenReturn(finalSeminarOpposition);
|
||||
Mockito.when(finalSeminarOppositionService.getOpposition(oppositionId)).thenReturn(
|
||||
new Opposition(user, mockReport(bachelor), Optional.empty())
|
||||
);
|
||||
startPage(oppositionId);
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,15 @@ public class SeminarOppositionPanelTest extends SciProTest {
|
||||
finalSeminar.setProject(project);
|
||||
|
||||
setLoggedInAs(supervisorUser);
|
||||
|
||||
Mockito.lenient()
|
||||
.when(finalSeminarOppositionService.getCriteriaForOpposition(opposition))
|
||||
.thenReturn(
|
||||
new OppositionCriteria(
|
||||
1,
|
||||
List.of(new OppositionCriteria.Point(0, ""), new OppositionCriteria.Point(1, "Filled in report"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -66,6 +66,14 @@ public class SeminarPanelTest extends SciProTest {
|
||||
Mockito.when(plagiarismControl.getStatus(any(FileDescription.class))).thenReturn(
|
||||
new PlagiarismControl.Status.NotSubmitted()
|
||||
);
|
||||
Mockito.lenient()
|
||||
.when(finalSeminarOppositionService.getCriteriaForOpposition(opposition))
|
||||
.thenReturn(
|
||||
new OppositionCriteria(
|
||||
1,
|
||||
List.of(new OppositionCriteria.Point(0, ""), new OppositionCriteria.Point(1, "Filled in report"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void addCoSupervisorToProject() {
|
||||
|
@ -9,6 +9,7 @@ import org.apache.wicket.model.Model;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import se.su.dsv.scipro.SciProTest;
|
||||
import se.su.dsv.scipro.forum.Discussable;
|
||||
@ -20,11 +21,13 @@ import se.su.dsv.scipro.system.User;
|
||||
public class ThreadsOverviewPanelTest extends SciProTest {
|
||||
|
||||
private List<ForumThread> threads;
|
||||
private ForumPost post;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
ForumThread forumThread = createThread();
|
||||
threads = Arrays.asList(forumThread);
|
||||
Mockito.when(basicForumService.getLastPost(forumThread)).thenReturn(post);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -54,7 +57,7 @@ public class ThreadsOverviewPanelTest extends SciProTest {
|
||||
|
||||
private ForumThread createThread() {
|
||||
User bob = User.builder().firstName("Bob").lastName("the Builder").emailAddress("bob@building.com").build();
|
||||
ForumPost post = new ForumPost();
|
||||
post = new ForumPost();
|
||||
post.setPostedBy(bob);
|
||||
ForumThread groupForumThread = new ForumThread();
|
||||
groupForumThread.addPost(post);
|
||||
|
@ -35,7 +35,7 @@ public class FillOutReportPanelTest extends SciProTest {
|
||||
private FillOutReportPanel panel;
|
||||
|
||||
@Mock
|
||||
private ReportService reportService;
|
||||
private OppositionReportService reportService;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
@ -197,6 +197,6 @@ public class FillOutReportPanelTest extends SciProTest {
|
||||
}
|
||||
|
||||
private void startPanel() {
|
||||
panel = tester.startComponentInPage(new FillOutReportPanel<>("id", Model.of(oppositionReport)));
|
||||
panel = tester.startComponentInPage(new FillOutReportPanel("id", Model.of(oppositionReport)));
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ public class EditGroupPanelTest extends SciProTest {
|
||||
group.setId(1L);
|
||||
Project project = createProject();
|
||||
group.setProjects(new HashSet<>(Collections.singletonList(project)));
|
||||
when(projectService.findOne(anyLong())).thenReturn(project);
|
||||
startPanel();
|
||||
}
|
||||
|
||||
|
13
war/pom.xml
13
war/pom.xml
@ -140,5 +140,18 @@
|
||||
<spring.profile.active>branch</spring.profile.active>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>DEV</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>se.su.dsv.scipro</groupId>
|
||||
<artifactId>test-data</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
@ -4,6 +4,7 @@ import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.Serializable;
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
@ -81,7 +82,7 @@ public class CurrentUserFromSpringSecurity implements AuthenticationContext {
|
||||
return authentication.getName();
|
||||
}
|
||||
|
||||
private static final class WicketControlledPrincipal implements Principal {
|
||||
private static final class WicketControlledPrincipal implements Principal, Serializable {
|
||||
|
||||
private final String username;
|
||||
|
||||
|
@ -13,6 +13,8 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
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.firstmeeting.FirstMeetingReminderWorker;
|
||||
import se.su.dsv.scipro.firstmeeting.FirstMeetingService;
|
||||
@ -150,6 +152,14 @@ public class WorkerConfig {
|
||||
return new SpringManagedWorkerTransactions(platformTransactionManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExpireUnfulfilledOppositionImprovementsWorker.Schedule expireUnfulfilledOppositionImprovementsWorkerSchedule(
|
||||
Scheduler scheduler,
|
||||
Provider<ExpireUnfulfilledOppositionImprovementsWorker> worker
|
||||
) {
|
||||
return new ExpireUnfulfilledOppositionImprovementsWorker.Schedule(scheduler, worker);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class Workers {
|
||||
|
||||
@ -279,5 +289,12 @@ public class WorkerConfig {
|
||||
public ExpiredRequestWorker expiredRequestWorker() {
|
||||
return new ExpiredRequestWorker();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExpireUnfulfilledOppositionImprovementsWorker expireUnfulfilledOppositionImprovementsWorker(
|
||||
FinalSeminarOppositionServiceImpl finalSeminarOppositionService
|
||||
) {
|
||||
return new ExpireUnfulfilledOppositionImprovementsWorker(finalSeminarOppositionService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user