Split Project #146
core/src
main
test/java/se/su/dsv/scipro/project/split
test-data/src/main/java/se/su/dsv/scipro/testdata
view/src
main/java/se/su/dsv/scipro
SciProApplication.java
admin/pages
AdminEditProjectPage.htmlAdminEditProjectPage.javaAdminSplitProjectPage.htmlAdminSplitProjectPage.javaAdminSplitProjectPanel.htmlAdminSplitProjectPanel.javaAdminSplitProjectPanel.utf8.propertiesAdminViewParentProjectPage.htmlAdminViewParentProjectPage.java
reviewer
test/java/se/su/dsv/scipro
@ -145,6 +145,7 @@ import se.su.dsv.scipro.project.ProjectPeopleStatisticsServiceImpl;
|
||||
import se.su.dsv.scipro.project.ProjectRepo;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.project.ProjectServiceImpl;
|
||||
import se.su.dsv.scipro.project.split.SplitOrRestartProjectServiceImpl;
|
||||
import se.su.dsv.scipro.projectpartner.ProjectPartnerServiceImpl;
|
||||
import se.su.dsv.scipro.reflection.ReflectionService;
|
||||
import se.su.dsv.scipro.reflection.ReflectionServiceImpl;
|
||||
@ -814,6 +815,23 @@ public class CoreConfig {
|
||||
return new ProjectServiceImpl(projectRepo, clock, eventBus, em);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SplitOrRestartProjectServiceImpl SplitOrRestartProjectService(
|
||||
ProjectService projectService,
|
||||
FinalSeminarService finalSeminarService,
|
||||
RoughDraftApprovalService roughDraftApprovalService,
|
||||
Clock clock,
|
||||
EventBus eventBus
|
||||
) {
|
||||
return new SplitOrRestartProjectServiceImpl(
|
||||
projectService,
|
||||
finalSeminarService,
|
||||
roughDraftApprovalService,
|
||||
clock,
|
||||
eventBus
|
||||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ProjectTypeServiceImpl projectTypeService(Provider<EntityManager> em) {
|
||||
return new ProjectTypeServiceImpl(em);
|
||||
|
@ -21,6 +21,7 @@ import se.su.dsv.scipro.peer.SecondPeerReviewCompletedEvent;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.report.SupervisorGradingReportSubmittedEvent;
|
||||
import se.su.dsv.scipro.reviewing.FinalSeminarApprovalApprovedEvent;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedClonedEvent;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedEvent;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApprovalRequestedEvent;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
@ -136,6 +137,11 @@ public class MilestoneActivator {
|
||||
activateProjectMilestone(Set.of(event.getName()), event.getProject());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void reviewerApprovalApprovedClone(RoughDraftApprovalApprovedClonedEvent event) {
|
||||
activateProjectMilestone(Set.of(event.getName()), event.getProject());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void finalSeminarThesisDeleted(FinalSeminarThesisDeletedEvent event) {
|
||||
deactivateProjectMilestone(Set.of("FinalSeminarThesisUploaded"), event.getFinalSeminar().getProject());
|
||||
|
@ -23,6 +23,7 @@ import jakarta.persistence.MapKeyJoinColumn;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.PreUpdate;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -113,6 +114,18 @@ public class Project extends DomainObject {
|
||||
@Column(name = "daisy_identifier", unique = true)
|
||||
private Integer identifier;
|
||||
|
||||
@Basic
|
||||
@Column(name = "parent_project_id")
|
||||
private Long parentProjectId;
|
||||
|
||||
@Basic
|
||||
@Column(name = "root_project_id")
|
||||
private Long rootProjectId;
|
||||
|
||||
@Basic
|
||||
@Column(name = "clone_timestamp")
|
||||
private Instant cloneTimestamp;
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// Embedded JPA-mapping
|
||||
// ----------------------------------------------------------------------------------
|
||||
@ -365,6 +378,30 @@ public class Project extends DomainObject {
|
||||
this.userNotes = userNotes;
|
||||
}
|
||||
|
||||
public Long getParentProjectId() {
|
||||
return parentProjectId;
|
||||
}
|
||||
|
||||
public void setParentProjectId(Long parentProjectId) {
|
||||
this.parentProjectId = parentProjectId;
|
||||
}
|
||||
|
||||
public Long getRootProjectId() {
|
||||
return rootProjectId;
|
||||
}
|
||||
|
||||
public void setRootProjectId(Long rootProjectId) {
|
||||
this.rootProjectId = rootProjectId;
|
||||
}
|
||||
|
||||
public Instant getCloneTimestamp() {
|
||||
return cloneTimestamp;
|
||||
}
|
||||
|
||||
public void setCloneTimestamp(Instant cloneTimestamp) {
|
||||
this.cloneTimestamp = cloneTimestamp;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// Methods Common To All Objects
|
||||
// ----------------------------------------------------------------------------------
|
||||
|
@ -0,0 +1,28 @@
|
||||
package se.su.dsv.scipro.project.split;
|
||||
|
||||
import java.util.List;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApproval;
|
||||
|
||||
public interface SplitOrRestartProjectService {
|
||||
enum SplittableStatus {
|
||||
NOT_EXIST,
|
||||
NOT_ACTIVE,
|
||||
NOT_TWO_PARTICIPANTS,
|
||||
PHASE_TWO_STARTED,
|
||||
FINAL_SEMINAR_PHASE_STARTED,
|
||||
OK,
|
||||
}
|
||||
|
||||
record SplittableStatusRecord(
|
||||
SplittableStatus splittableStatus,
|
||||
Project project,
|
||||
RoughDraftApproval roughDraftApproval
|
||||
) {}
|
||||
|
||||
SplittableStatusRecord getSplittableStatus(long projectId);
|
||||
|
||||
void splitProject(long projectId);
|
||||
|
||||
List<Project> getChildProjects(long parentProjectId);
|
||||
}
|
133
core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
Normal file
133
core/src/main/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceImpl.java
Normal file
@ -0,0 +1,133 @@
|
||||
package se.su.dsv.scipro.project.split;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminarService;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.project.ProjectStatus;
|
||||
import se.su.dsv.scipro.project.QProject;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApproval;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApprovalApprovedClonedEvent;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
public class SplitOrRestartProjectServiceImpl implements SplitOrRestartProjectService {
|
||||
|
||||
private final ProjectService projectService;
|
||||
private final FinalSeminarService finalSeminarService;
|
||||
private final RoughDraftApprovalService roughDraftApprovalService;
|
||||
private final Clock clock;
|
||||
private final EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
public SplitOrRestartProjectServiceImpl(
|
||||
ProjectService projectService,
|
||||
FinalSeminarService finalSeminarService,
|
||||
RoughDraftApprovalService roughDraftApprovalService,
|
||||
Clock clock,
|
||||
EventBus eventBus
|
||||
) {
|
||||
this.projectService = projectService;
|
||||
this.finalSeminarService = finalSeminarService;
|
||||
this.roughDraftApprovalService = roughDraftApprovalService;
|
||||
this.clock = clock;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public SplittableStatusRecord getSplittableStatus(long projectId) {
|
||||
Project project = projectService.findOne(projectId);
|
||||
if (project == null) return new SplittableStatusRecord(SplittableStatus.NOT_EXIST, null, null);
|
||||
|
||||
if (project.getProjectStatus() != ProjectStatus.ACTIVE) {
|
||||
return new SplittableStatusRecord(SplittableStatus.NOT_ACTIVE, project, null);
|
||||
}
|
||||
|
||||
if (project.getProjectParticipants().size() != 2) {
|
||||
return new SplittableStatusRecord(SplittableStatus.NOT_TWO_PARTICIPANTS, project, null);
|
||||
}
|
||||
|
||||
if (finalSeminarService.findByProject(project) != null) {
|
||||
return new SplittableStatusRecord(SplittableStatus.FINAL_SEMINAR_PHASE_STARTED, project, null);
|
||||
}
|
||||
|
||||
Optional<RoughDraftApproval> o = roughDraftApprovalService.findBy(project);
|
||||
if (o.isPresent() && !o.get().isDecided()) {
|
||||
return new SplittableStatusRecord(SplittableStatus.PHASE_TWO_STARTED, project, null);
|
||||
}
|
||||
|
||||
if (o.isPresent() && o.get().isApproved()) {
|
||||
return new SplittableStatusRecord(SplittableStatus.OK, project, o.get());
|
||||
} else {
|
||||
return new SplittableStatusRecord(SplittableStatus.OK, project, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void splitProject(long projectId) {
|
||||
SplittableStatusRecord result = getSplittableStatus(projectId);
|
||||
if (result.splittableStatus() != SplittableStatus.OK) {
|
||||
throw new IllegalStateException(
|
||||
"Project must to be verified to be able to split before this method can be called."
|
||||
);
|
||||
}
|
||||
|
||||
Project project = result.project();
|
||||
|
||||
for (User author : project.getProjectParticipants()) {
|
||||
Project childProject = new Project();
|
||||
|
||||
childProject.setTitle(project.getTitle());
|
||||
childProject.setProjectType(project.getProjectType());
|
||||
childProject.setProjectStatus(ProjectStatus.ACTIVE);
|
||||
childProject.setResearchArea(project.getResearchArea());
|
||||
|
||||
childProject.setStartDate(project.getStartDate());
|
||||
childProject.setExpectedEndDate(project.getExpectedEndDate());
|
||||
|
||||
childProject.setHeadSupervisor(project.getHeadSupervisor());
|
||||
childProject.setProjectParticipants(List.of(author));
|
||||
childProject.setCoSupervisors(project.getCoSupervisors());
|
||||
|
||||
childProject.setReviewers(project.getReviewers());
|
||||
|
||||
childProject.setParentProjectId(project.getId());
|
||||
childProject.setRootProjectId(
|
||||
project.getRootProjectId() != null ? project.getRootProjectId() : project.getId()
|
||||
);
|
||||
childProject.setCloneTimestamp(Instant.now());
|
||||
|
||||
childProject = projectService.save(childProject);
|
||||
|
||||
// Add cloned RoughDraftApproval if it's 'APPROVED'
|
||||
RoughDraftApproval rda = result.roughDraftApproval();
|
||||
if (rda != null && rda.isApproved()) {
|
||||
RoughDraftApproval clonedRda = roughDraftApprovalService.saveCloned(
|
||||
rda.cloneToProject(childProject, clock.instant())
|
||||
);
|
||||
|
||||
// Send event to eventBus to synchronize eventual Phase Two Approval with MileStone
|
||||
eventBus.post(new RoughDraftApprovalApprovedClonedEvent(clonedRda));
|
||||
}
|
||||
}
|
||||
|
||||
// Parent project will set as inactive
|
||||
project.setProjectStatus(ProjectStatus.INACTIVE);
|
||||
projectService.save(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<Project> getChildProjects(long parentProjectId) {
|
||||
return projectService.findAll(QProject.project.rootProjectId.eq(parentProjectId));
|
||||
}
|
||||
}
|
@ -196,6 +196,25 @@ public class Decision {
|
||||
decideWithDecisionDate(status, reason, attachment, Instant.now());
|
||||
}
|
||||
|
||||
Decision cloneToReviewerApproval(RoughDraftApproval roughDraftApproval) {
|
||||
Decision clonedDecision = new Decision();
|
||||
clonedDecision.reviewerApproval = roughDraftApproval;
|
||||
|
||||
clonedDecision.status = this.status;
|
||||
clonedDecision.reason = this.reason;
|
||||
clonedDecision.comment = this.comment;
|
||||
clonedDecision.requested = this.requested;
|
||||
clonedDecision.decisionDate = this.decisionDate;
|
||||
clonedDecision.deadline = this.deadline;
|
||||
clonedDecision.reviewerAssignedAt = this.reviewerAssignedAt;
|
||||
|
||||
clonedDecision.assignedReviewer = this.assignedReviewer;
|
||||
clonedDecision.attachment = this.attachment;
|
||||
clonedDecision.thesis = this.thesis;
|
||||
|
||||
return clonedDecision;
|
||||
}
|
||||
|
||||
private void decideWithDecisionDate(
|
||||
final Status status,
|
||||
final String reason,
|
||||
|
@ -25,6 +25,7 @@ public class DecisionRepositoryImpl extends AbstractRepository implements Decisi
|
||||
.eq(reviewer)
|
||||
.and(QDecision.decision.reviewerAssignedAt.goe(fromDate))
|
||||
.and(QDecision.decision.reviewerAssignedAt.loe(toDate))
|
||||
.and(QDecision.decision.reviewerApproval.isCloned.isFalse())
|
||||
)
|
||||
.distinct()
|
||||
.fetchCount();
|
||||
|
@ -1,6 +1,8 @@
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
@ -11,6 +13,7 @@ import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.OrderBy;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
@ -32,6 +35,14 @@ public abstract class ReviewerApproval extends DomainObject {
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Basic
|
||||
@Column(name = "is_cloned")
|
||||
protected Boolean isCloned = false;
|
||||
|
||||
@Basic
|
||||
@Column(name = "clone_timestamp")
|
||||
protected Instant cloneTimestamp;
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// JPA-mappings of foreign keys in this table (reviewer_approval) referencing other
|
||||
// tables.
|
||||
@ -59,6 +70,22 @@ public abstract class ReviewerApproval extends DomainObject {
|
||||
return this.project;
|
||||
}
|
||||
|
||||
public Boolean getCloned() {
|
||||
return isCloned;
|
||||
}
|
||||
|
||||
public void setCloned(Boolean cloned) {
|
||||
isCloned = cloned;
|
||||
}
|
||||
|
||||
public Instant getCloneTimestamp() {
|
||||
return cloneTimestamp;
|
||||
}
|
||||
|
||||
public void setCloneTimestamp(Instant cloneDate) {
|
||||
this.cloneTimestamp = cloneDate;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// Other methods
|
||||
// ----------------------------------------------------------------------------------
|
||||
|
@ -1,7 +1,8 @@
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import java.util.*;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import se.su.dsv.scipro.file.FileReference;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
|
||||
@ -24,4 +25,15 @@ public class RoughDraftApproval extends ReviewerApproval {
|
||||
public Step getStep() {
|
||||
return Step.ROUGH_DRAFT_APPROVAL;
|
||||
}
|
||||
|
||||
public RoughDraftApproval cloneToProject(final Project newProject, Instant instant) {
|
||||
RoughDraftApproval rda = new RoughDraftApproval();
|
||||
rda.project = newProject;
|
||||
this.decisions.forEach(decision -> rda.decisions.add(decision.cloneToReviewerApproval(rda)));
|
||||
|
||||
rda.isCloned = true;
|
||||
rda.cloneTimestamp = instant;
|
||||
|
||||
return rda;
|
||||
}
|
||||
}
|
||||
|
14
core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalApprovedClonedEvent.java
Normal file
14
core/src/main/java/se/su/dsv/scipro/reviewing/RoughDraftApprovalApprovedClonedEvent.java
Normal file
@ -0,0 +1,14 @@
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
|
||||
public record RoughDraftApprovalApprovedClonedEvent(ReviewerApproval process) {
|
||||
public Project getProject() {
|
||||
return process.getProject();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
ReviewerApproval.Step step = process.getStep();
|
||||
return step.getDeclaringClass().getSimpleName() + "." + step.name();
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
package se.su.dsv.scipro.reviewing;
|
||||
|
||||
public interface RoughDraftApprovalService extends ReviewerApprovalService<RoughDraftApproval> {}
|
||||
public interface RoughDraftApprovalService extends ReviewerApprovalService<RoughDraftApproval> {
|
||||
RoughDraftApproval saveCloned(RoughDraftApproval approval);
|
||||
}
|
||||
|
@ -71,4 +71,10 @@ public class RoughDraftApprovalServiceImpl
|
||||
public Optional<RoughDraftApproval> findBy(Project project) {
|
||||
return Optional.ofNullable(findOne(QRoughDraftApproval.roughDraftApproval.project.eq(project)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public RoughDraftApproval saveCloned(RoughDraftApproval approval) {
|
||||
return save(approval);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
|
||||
alter table `project`
|
||||
add column `parent_project_id` bigint(20) null default null;
|
||||
|
||||
alter table `project`
|
||||
add column `root_project_id` bigint(20) null default null;
|
||||
|
||||
alter table `project`
|
||||
add column `clone_timestamp` datetime null default null;
|
||||
|
||||
alter table `project`
|
||||
add constraint fk_project_parent_project_id_project_id
|
||||
foreign key (parent_project_id) references project(id)
|
||||
on delete cascade on update cascade;
|
||||
|
||||
alter table `project`
|
||||
add constraint fk_project_root_project_id_project_id
|
||||
foreign key (root_project_id) references project(id)
|
||||
on delete cascade on update cascade;
|
||||
|
||||
|
||||
alter table `reviewer_approval`
|
||||
add column `is_cloned` boolean not null default false;
|
||||
|
||||
update `reviewer_approval` set is_cloned = false;
|
||||
|
||||
alter table `reviewer_approval`
|
||||
add column `clone_timestamp` datetime null default null;
|
296
core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
Normal file
296
core/src/test/java/se/su/dsv/scipro/project/split/SplitOrRestartProjectServiceIntegrationTest.java
Normal file
@ -0,0 +1,296 @@
|
||||
package se.su.dsv.scipro.project.split;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
|
||||
import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatusRecord;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Year;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import se.su.dsv.scipro.file.FileUpload;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.ProjectStatus;
|
||||
import se.su.dsv.scipro.reviewing.FinalSeminarApprovalService;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerCapacityService;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApproval;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.system.DegreeType;
|
||||
import se.su.dsv.scipro.system.Language;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.ResearchArea;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.IntegrationTest;
|
||||
import se.su.dsv.scipro.test.MutableFixedClock;
|
||||
|
||||
public class SplitOrRestartProjectServiceIntegrationTest extends IntegrationTest {
|
||||
|
||||
private static int TARGET = 3;
|
||||
private static int REMAINING_TARGET = 2;
|
||||
|
||||
@Inject
|
||||
private SplitOrRestartProjectService splitOrRestartProjectService;
|
||||
|
||||
@Inject
|
||||
private RoughDraftApprovalService roughDraftApprovalService;
|
||||
|
||||
@Inject
|
||||
private FinalSeminarApprovalService finalSeminarApprovalService;
|
||||
|
||||
@Inject
|
||||
MutableFixedClock clock;
|
||||
|
||||
@Inject
|
||||
private ReviewerAssignmentService reviewerAssignmentService;
|
||||
|
||||
@Inject
|
||||
private ReviewerCapacityService reviewerCapacityService;
|
||||
|
||||
private ResearchArea researchArea;
|
||||
private Project parentProject;
|
||||
private User supervisor;
|
||||
private User reviewer;
|
||||
private User author1;
|
||||
private User author2;
|
||||
|
||||
tozh4728 marked this conversation as resolved
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ProjectType bachelor = save(new ProjectType(DegreeType.BACHELOR, "Bachelor", "Bachelor"));
|
||||
|
||||
researchArea = new ResearchArea();
|
||||
researchArea.setTitle("Computer Science");
|
||||
researchArea = save(researchArea);
|
||||
|
||||
parentProject = createProject(bachelor, ProjectStatus.ACTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void project_must_exist() {
|
||||
SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(0);
|
||||
SplittableStatus status = record.splittableStatus();
|
||||
|
||||
assertEquals(SplittableStatus.NOT_EXIST, status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void project_must_be_active() {
|
||||
parentProject.setProjectStatus(ProjectStatus.INACTIVE);
|
||||
parentProject = save(parentProject);
|
||||
|
||||
SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
|
||||
SplittableStatus status = record.splittableStatus();
|
||||
|
||||
assertEquals(SplittableStatus.NOT_ACTIVE, status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void project_must_have_two_participants() {
|
||||
SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
|
||||
SplittableStatus status = record.splittableStatus();
|
||||
|
||||
assertEquals(SplittableStatus.NOT_TWO_PARTICIPANTS, status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void project_phase_two_started() {
|
||||
setUpBeforePhaseTwo();
|
||||
|
||||
SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
|
||||
SplittableStatus status = record.splittableStatus();
|
||||
|
||||
assertEquals(SplittableStatus.PHASE_TWO_STARTED, status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void split_on_failed_phase_two() {
|
||||
setUpBeforePhaseTwo();
|
||||
|
||||
Optional<RoughDraftApproval> optional = this.roughDraftApprovalService.findBy(parentProject);
|
||||
optional.ifPresent(rda -> rda.reject("Fail", Optional.empty()));
|
||||
|
||||
SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
|
||||
SplittableStatus status = record.splittableStatus();
|
||||
|
||||
assertEquals(SplittableStatus.OK, status);
|
||||
|
||||
splitOrRestartProjectService.splitProject(parentProject.getId());
|
||||
|
||||
ReviewerCapacityService.RemainingTargets remainingTargets = reviewerCapacityService.getRemainingTargets(
|
||||
reviewer,
|
||||
Year.now()
|
||||
);
|
||||
|
||||
assertEquals(REMAINING_TARGET, remainingTargets.spring());
|
||||
assertEquals(TARGET, remainingTargets.autumn());
|
||||
|
||||
List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
|
||||
|
||||
assertEquals(ProjectStatus.INACTIVE, parentProject.getProjectStatus());
|
||||
assertEquals(2, childProjects.size());
|
||||
|
||||
childProjects.forEach(project -> {
|
||||
assertEquals(1, project.getProjectParticipants().size());
|
||||
assertEquals(supervisor, project.getHeadSupervisor());
|
||||
assertEquals(ProjectStatus.ACTIVE, project.getProjectStatus());
|
||||
assertTrue(roughDraftApprovalService.findBy(project).isEmpty());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void split_on_approved_phase_two() {
|
||||
setUpBeforePhaseTwo();
|
||||
|
||||
Optional<RoughDraftApproval> optional = this.roughDraftApprovalService.findBy(parentProject);
|
||||
optional.ifPresent(rda -> rda.approve("Approve", Optional.empty()));
|
||||
|
||||
SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
|
||||
SplittableStatus status = record.splittableStatus();
|
||||
|
||||
assertEquals(SplittableStatus.OK, status);
|
||||
|
||||
splitOrRestartProjectService.splitProject(parentProject.getId());
|
||||
|
||||
ReviewerCapacityService.RemainingTargets remainingTargets = reviewerCapacityService.getRemainingTargets(
|
||||
reviewer,
|
||||
Year.now()
|
||||
);
|
||||
|
||||
assertEquals(REMAINING_TARGET, remainingTargets.spring());
|
||||
assertEquals(TARGET, remainingTargets.autumn());
|
||||
|
||||
List<Project> childProjects = splitOrRestartProjectService.getChildProjects(parentProject.getId());
|
||||
|
||||
assertEquals(ProjectStatus.INACTIVE, parentProject.getProjectStatus());
|
||||
assertEquals(2, childProjects.size());
|
||||
|
||||
childProjects.forEach(project -> {
|
||||
assertEquals(1, project.getProjectParticipants().size());
|
||||
assertEquals(supervisor, project.getHeadSupervisor());
|
||||
assertEquals(ProjectStatus.ACTIVE, project.getProjectStatus());
|
||||
|
||||
Optional<RoughDraftApproval> optionalRda = roughDraftApprovalService.findBy(project);
|
||||
assertTrue(optionalRda.isPresent());
|
||||
assertTrue(optional.get().isApproved());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void split_on_final_seminar_phase_started() {
|
||||
setUpBeforePhaseTwo();
|
||||
|
||||
Optional<RoughDraftApproval> optional = this.roughDraftApprovalService.findBy(parentProject);
|
||||
optional.ifPresent(rda -> rda.approve("Approve", Optional.empty()));
|
||||
|
||||
FinalSeminar finalSeminar = new FinalSeminar();
|
||||
finalSeminar.setProject(parentProject);
|
||||
|
||||
LocalDate plus30Days = LocalDate.now(clock).plusDays(30);
|
||||
finalSeminar.setStartDate(Date.from(plus30Days.atStartOfDay(ZoneId.systemDefault()).toInstant()));
|
||||
finalSeminar.setRoom("room");
|
||||
finalSeminar.setPresentationLanguage(Language.ENGLISH);
|
||||
parentProject.setLanguage(Language.ENGLISH);
|
||||
|
||||
save(finalSeminar);
|
||||
|
||||
SplittableStatusRecord record = splitOrRestartProjectService.getSplittableStatus(parentProject.getId());
|
||||
SplittableStatus status = record.splittableStatus();
|
||||
|
||||
assertEquals(SplittableStatus.FINAL_SEMINAR_PHASE_STARTED, status);
|
||||
}
|
||||
|
||||
private Project createProject(ProjectType projectType, ProjectStatus active) {
|
||||
supervisor = createEmployee("Eric", "Employee", "eric@example.com", false);
|
||||
|
||||
Project project = new Project();
|
||||
project.setTitle("Some title");
|
||||
project.setProjectType(projectType);
|
||||
project.setProjectStatus(active);
|
||||
project.setHeadSupervisor(supervisor);
|
||||
project.setStartDate(LocalDate.now().withMonth(1).withDayOfMonth(1));
|
||||
|
||||
author1 = createUser("Adam", "Student", "adam.student@example.com");
|
||||
project.addProjectParticipant(author1);
|
||||
|
||||
project = save(project);
|
||||
return project;
|
||||
}
|
||||
|
||||
private User createEmployee(String firstName, String lastName, String email, boolean isReviewer) {
|
||||
User employee = createUser(firstName, lastName, email);
|
||||
|
||||
Set<ResearchArea> researchAreas = new HashSet<>();
|
||||
researchAreas.add(researchArea);
|
||||
employee.setResearchAreas(researchAreas);
|
||||
|
||||
if (isReviewer) {
|
||||
employee.addRole(Roles.REVIEWER);
|
||||
}
|
||||
|
||||
save(employee);
|
||||
return employee;
|
||||
}
|
||||
|
||||
private User createUser(String firstName, String lastName, String email) {
|
||||
User user = User.builder().firstName(firstName).lastName(lastName).emailAddress(email).build();
|
||||
return save(user);
|
||||
}
|
||||
|
||||
private void setUpBeforePhaseTwo() {
|
||||
author2 = createUser("Bertil", "Student", "bertil.student@example.com");
|
||||
parentProject.addProjectParticipant(author2);
|
||||
save(parentProject);
|
||||
|
||||
clock.setDate(LocalDate.now().withMonth(3).withDayOfMonth(1));
|
||||
|
||||
roughDraftApprovalService.requestApproval(parentProject, dummyFile(), "comment");
|
||||
|
||||
reviewer = createEmployee("Lisa", "Employee", "lisa.employee@example.com", true);
|
||||
reviewerCapacityService.assignTarget(
|
||||
reviewer,
|
||||
new ReviewerCapacityService.Target(Year.now(), TARGET, TARGET, "")
|
||||
);
|
||||
reviewerAssignmentService.assignReviewer(parentProject, reviewer);
|
||||
}
|
||||
|
||||
private FileUpload dummyFile() {
|
||||
return new FileUpload() {
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return "dummy.tmp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUploader() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T handleData(Function<InputStream, T> handler) {
|
||||
return handler.apply(InputStream.nullInputStream());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -6,10 +6,7 @@ import jakarta.persistence.EntityManager;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Month;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import se.su.dsv.scipro.checklist.ChecklistCategory;
|
||||
@ -28,6 +25,7 @@ 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.milestones.service.MilestonePhaseTemplateService;
|
||||
import se.su.dsv.scipro.notifications.dataobject.CustomEvent;
|
||||
import se.su.dsv.scipro.notifications.dataobject.GroupEvent;
|
||||
import se.su.dsv.scipro.notifications.dataobject.IdeaEvent;
|
||||
@ -73,6 +71,12 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
|
||||
@Inject
|
||||
private MilestoneActivityTemplateService milestoneActivityTemplateService;
|
||||
|
||||
@Inject
|
||||
private MilestonePhaseTemplateService milestonePhaseTemplateService;
|
||||
|
||||
@Inject
|
||||
private EventService eventService;
|
||||
|
||||
@Inject
|
||||
private FileService fileService;
|
||||
|
||||
@ -2016,34 +2020,37 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
|
||||
MilestoneActivityTemplate.Type.STUDENT,
|
||||
"First meeting held",
|
||||
"First meeting with supervisor.",
|
||||
milestonePhaseTemplate1,
|
||||
null
|
||||
);
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Project plan approved",
|
||||
"Project plan approved by supervisor.",
|
||||
milestonePhaseTemplate1,
|
||||
null
|
||||
milestonePhaseTemplate1
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Rough draft sent to reviewer for approval",
|
||||
"Rough draft approved by reviewer.",
|
||||
milestonePhaseTemplate2,
|
||||
null
|
||||
"Project plan approved",
|
||||
"Project plan approved by supervisor.",
|
||||
milestonePhaseTemplate1
|
||||
);
|
||||
|
||||
List<Event> events = eventService.findAll();
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Rough draft approved by reviewer",
|
||||
"Rough draft sent to reviewer for approval (Auto)",
|
||||
"Rough draft sent to the reviewer for the first time.",
|
||||
milestonePhaseTemplate2,
|
||||
events.stream().filter(event -> event.getName().equals("RoughDraftApprovalRequested")).findFirst().get()
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Rough draft approved by reviewer (Auto)",
|
||||
"Rough draft approved.",
|
||||
milestonePhaseTemplate2,
|
||||
null
|
||||
events.stream().filter(event -> event.getName().equals("Step.ROUGH_DRAFT_APPROVAL")).findFirst().get()
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.STUDENT,
|
||||
"Peer review 1",
|
||||
"Peer review 1 (Auto)",
|
||||
"This is a recommendation of when to perform peer review 1.",
|
||||
milestonePhaseTemplate2,
|
||||
MilestoneActivityTemplate.PEER_REVIEW_ONE
|
||||
@ -2053,9 +2060,9 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Result and discussion completed and approved",
|
||||
"Result and discussion.",
|
||||
milestonePhaseTemplate3,
|
||||
null
|
||||
milestonePhaseTemplate3
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.STUDENT,
|
||||
"Peer review 2",
|
||||
@ -2068,9 +2075,9 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Thesis approved for final seminar presentation",
|
||||
"Thesis approved for final seminar.",
|
||||
milestonePhaseTemplate4,
|
||||
null
|
||||
milestonePhaseTemplate4
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Final seminar created",
|
||||
@ -2078,6 +2085,7 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
|
||||
milestonePhaseTemplate4,
|
||||
MilestoneActivityTemplate.CREATE_SEMINAR
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Final seminar thesis uploaded",
|
||||
@ -2085,77 +2093,111 @@ public class DataInitializer implements Lifecycle, BaseData, Factory {
|
||||
milestonePhaseTemplate4,
|
||||
MilestoneActivityTemplate.THESIS_UPLOADED
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.STUDENT,
|
||||
"Perform an oral and written opposition",
|
||||
"Opposition.",
|
||||
milestonePhaseTemplate4,
|
||||
null
|
||||
milestonePhaseTemplate4
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.STUDENT,
|
||||
"Active participation in a final seminar",
|
||||
"Active participation.",
|
||||
milestonePhaseTemplate4,
|
||||
null
|
||||
milestonePhaseTemplate4
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.STUDENT,
|
||||
"Defend the thesis in a final seminar",
|
||||
"Defence of final thesis.",
|
||||
milestonePhaseTemplate4,
|
||||
null
|
||||
milestonePhaseTemplate4
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Revised final thesis of the submitted thesis",
|
||||
"Revised final thesis.",
|
||||
milestonePhaseTemplate5,
|
||||
null
|
||||
milestonePhaseTemplate5
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Originality report approved",
|
||||
"Originality report.",
|
||||
milestonePhaseTemplate5,
|
||||
null
|
||||
milestonePhaseTemplate5
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.PROJECT,
|
||||
"Supervisor and reviewer final grading report submitted",
|
||||
"Final grading report.",
|
||||
milestonePhaseTemplate5,
|
||||
null
|
||||
milestonePhaseTemplate5
|
||||
);
|
||||
|
||||
createMileStone(
|
||||
MilestoneActivityTemplate.Type.STUDENT,
|
||||
"Grading completed",
|
||||
"Grading completed by examiner.",
|
||||
milestonePhaseTemplate5,
|
||||
null
|
||||
milestonePhaseTemplate5
|
||||
);
|
||||
}
|
||||
|
||||
private void createMileStone(
|
||||
MilestoneActivityTemplate.Type type,
|
||||
String title,
|
||||
String description,
|
||||
MilestonePhaseTemplate milestonePhaseTemplate
|
||||
) {
|
||||
createMileStone(type, title, description, milestonePhaseTemplate, null, null);
|
||||
}
|
||||
|
||||
private void createMileStone(
|
||||
MilestoneActivityTemplate.Type type,
|
||||
String title,
|
||||
String description,
|
||||
MilestonePhaseTemplate milestonePhaseTemplate,
|
||||
Event event
|
||||
) {
|
||||
createMileStone(type, title, description, milestonePhaseTemplate, null, event);
|
||||
}
|
||||
|
||||
private void createMileStone(
|
||||
MilestoneActivityTemplate.Type type,
|
||||
String title,
|
||||
String description,
|
||||
MilestonePhaseTemplate milestonePhaseTemplate,
|
||||
String code
|
||||
) {
|
||||
createMileStone(type, title, description, milestonePhaseTemplate, code, null);
|
||||
}
|
||||
|
||||
private void createMileStone(
|
||||
MilestoneActivityTemplate.Type type,
|
||||
String title,
|
||||
String description,
|
||||
MilestonePhaseTemplate milestonePhaseTemplate,
|
||||
String code,
|
||||
Event event
|
||||
) {
|
||||
MilestoneActivityTemplate milestoneActivityTemplate = new MilestoneActivityTemplate(type, title, description);
|
||||
LocalDate ld = LocalDate.now().minusYears(1);
|
||||
milestoneActivityTemplate.setDateCreated(Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant()));
|
||||
milestoneActivityTemplate.addProjectType(bachelorClass);
|
||||
milestoneActivityTemplate.addProjectType(masterClass);
|
||||
milestoneActivityTemplate.addProjectType(magisterClass);
|
||||
milestoneActivityTemplate.setMilestonePhaseTemplate(milestonePhaseTemplate);
|
||||
milestoneActivityTemplate.setCode(code);
|
||||
milestoneActivityTemplate.setActivatedBy(event);
|
||||
milestoneActivityTemplateService.save(milestoneActivityTemplate, milestonePhaseTemplate);
|
||||
}
|
||||
|
||||
private MilestonePhaseTemplate createMileStonePhase(String title, String description) {
|
||||
MilestonePhaseTemplate milestonePhaseTemplate1 = new MilestonePhaseTemplate(title, description);
|
||||
return save(milestonePhaseTemplate1);
|
||||
MilestonePhaseTemplate milestonePhaseTemplate = new MilestonePhaseTemplate(title, description);
|
||||
LocalDate ld = LocalDate.now().minusYears(1);
|
||||
milestonePhaseTemplate.setDateCreated(Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant()));
|
||||
return milestonePhaseTemplateService.save(milestonePhaseTemplate);
|
||||
}
|
||||
|
||||
private <T> T save(T entity) {
|
||||
|
106
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
vendored
Normal file
106
test-data/src/main/java/se/su/dsv/scipro/testdata/populators/SplitProjectPopulator.java
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
package se.su.dsv.scipro.testdata.populators;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Year;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import org.springframework.stereotype.Service;
|
||||
import se.su.dsv.scipro.file.FileUpload;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerCapacityService;
|
||||
import se.su.dsv.scipro.reviewing.ReviewerDecisionService;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApproval;
|
||||
import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
|
||||
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 SplitProjectPopulator implements TestDataPopulator {
|
||||
|
||||
private final ProjectService projectService;
|
||||
private final ReviewerCapacityService reviewerCapacityService;
|
||||
private final RoughDraftApprovalService roughDraftApprovalService;
|
||||
private final ReviewerAssignmentService reviewerAssignmentService;
|
||||
private final ReviewerDecisionService reviewerDecisionService;
|
||||
|
||||
@Inject
|
||||
public SplitProjectPopulator(
|
||||
ProjectService projectService,
|
||||
ReviewerCapacityService reviewerCapacityService,
|
||||
RoughDraftApprovalService roughDraftApprovalService,
|
||||
ReviewerAssignmentService reviewerAssignmentService,
|
||||
ReviewerDecisionService reviewerDecisionService
|
||||
) {
|
||||
this.projectService = projectService;
|
||||
this.reviewerCapacityService = reviewerCapacityService;
|
||||
this.roughDraftApprovalService = roughDraftApprovalService;
|
||||
this.reviewerAssignmentService = reviewerAssignmentService;
|
||||
this.reviewerDecisionService = reviewerDecisionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate(BaseData baseData, Factory factory) {
|
||||
User supervisor = factory.createSupervisor("Emil");
|
||||
|
||||
User author1 = factory.createAuthor("Scott");
|
||||
User author2 = factory.createAuthor("Scarlett");
|
||||
|
||||
User reviewer = factory.createReviewer("Elias");
|
||||
|
||||
reviewerCapacityService.assignTarget(reviewer, new ReviewerCapacityService.Target(Year.now(), 3, 3, ""));
|
||||
|
||||
Project project = Project.builder()
|
||||
.title("Operating System Boot Time Security")
|
||||
.projectType(baseData.bachelor())
|
||||
.startDate(LocalDate.now())
|
||||
.headSupervisor(supervisor)
|
||||
.reviewers(Set.of(reviewer))
|
||||
.projectParticipants(Set.of(author1, author2))
|
||||
.build();
|
||||
|
||||
projectService.save(project);
|
||||
|
||||
roughDraftApprovalService.requestApproval(project, dummyFile(), "Request review.");
|
||||
|
||||
reviewerAssignmentService.assignReviewer(project, reviewer);
|
||||
|
||||
Optional<RoughDraftApproval> optional = roughDraftApprovalService.findBy(project);
|
||||
optional.ifPresent(rda -> reviewerDecisionService.approve(rda, "Approved! Good Work!", Optional.empty()));
|
||||
}
|
||||
|
||||
private FileUpload dummyFile() {
|
||||
return new FileUpload() {
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return "dummy.tmp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUploader() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T handleData(Function<InputStream, T> handler) {
|
||||
return handler.apply(InputStream.nullInputStream());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -294,6 +294,8 @@ public class SciProApplication extends LifecycleManagedWebApplication {
|
||||
mountPage("admin/maintenance", SystemMaintenancePage.class);
|
||||
mountPage("admin/project", ProjectManagementPage.class);
|
||||
mountPage("admin/project/create", AdminCreateProjectPage.class);
|
||||
mountPage("admin/project/split", AdminSplitProjectPage.class);
|
||||
mountPage("admin/project/viewparentproject", AdminViewParentProjectPage.class);
|
||||
mountPage("admin/project/survey", AdminSurveyPage.class);
|
||||
mountPage("admin/project/reviewer", AdminAssignReviewerPage.class);
|
||||
mountPage("admin/project/reviewer/capacity", AdminReviewerCapacityManagementPage.class);
|
||||
|
@ -49,6 +49,9 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<wicket:container wicket:id="splitPanel"/>
|
||||
|
||||
</wicket:extend>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,14 +3,28 @@ package se.su.dsv.scipro.admin.pages;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
import org.apache.wicket.markup.html.form.*;
|
||||
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.RequiredTextField;
|
||||
import org.apache.wicket.markup.html.form.TextField;
|
||||
import org.apache.wicket.markup.html.panel.FeedbackPanel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LambdaModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.components.*;
|
||||
import se.su.dsv.scipro.components.AutoCompleteRoleProvider;
|
||||
import se.su.dsv.scipro.components.BootstrapDatePicker;
|
||||
import se.su.dsv.scipro.components.DatesValidator;
|
||||
import se.su.dsv.scipro.components.DefaultSelect2MultiChoice;
|
||||
import se.su.dsv.scipro.components.ResearchAreaChoiceRenderer;
|
||||
import se.su.dsv.scipro.components.ResearchAreasModel;
|
||||
import se.su.dsv.scipro.components.SupervisorAutoComplete;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectManagement;
|
||||
import se.su.dsv.scipro.data.DetachableServiceModel;
|
||||
import se.su.dsv.scipro.data.DetachableServiceModelCollection;
|
||||
@ -26,7 +40,13 @@ import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.project.ReviewerAssignedEvent;
|
||||
import se.su.dsv.scipro.security.auth.Authorization;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.system.*;
|
||||
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.ResearchAreaService;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.system.UserSearchService;
|
||||
import se.su.dsv.scipro.system.UserService;
|
||||
import se.su.dsv.scipro.util.PageParameterKeys;
|
||||
|
||||
@Authorization(authorizedRoles = { Roles.SYSADMIN })
|
||||
@ -74,7 +94,10 @@ public class AdminEditProjectPage extends AbstractAdminProjectPage implements Me
|
||||
if (project == null) {
|
||||
throw new RestartResponseException(AdminCreateProjectPage.class);
|
||||
}
|
||||
add(new ProjectForm(FORM, new DetachableServiceModel<>(projectService, project)));
|
||||
DetachableServiceModel<Project> dsModel = new DetachableServiceModel<>(projectService, project);
|
||||
|
||||
add(new ProjectForm(FORM, dsModel));
|
||||
add(new AdminSplitProjectPanel("splitPanel", dsModel));
|
||||
}
|
||||
|
||||
private class ProjectForm extends Form<Project> {
|
||||
|
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org" lang="en">
|
||||
<body>
|
||||
<wicket:extend>
|
||||
<div class="row">
|
||||
<div class="col-lg-5">
|
||||
<h4 wicket:id="projectTitle"></h4>
|
||||
|
||||
<form class="form-horizontal" wicket:id="splitProjectForm">
|
||||
<p>This project will be split between following authors:</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<ul wicket:id="authorList">
|
||||
<li wicket:id="author"></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-success" type="submit">Split Project</button>
|
||||
|
||||
<a wicket:id="cancelLink">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</wicket:extend>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,73 @@
|
||||
package se.su.dsv.scipro.admin.pages;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.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.model.IModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectManagement;
|
||||
import se.su.dsv.scipro.data.DetachableServiceModel;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
|
||||
import se.su.dsv.scipro.security.auth.Authorization;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.PageParameterKeys;
|
||||
|
||||
@Authorization(authorizedRoles = { Roles.SYSADMIN })
|
||||
public class AdminSplitProjectPage extends AbstractAdminProjectPage implements MenuHighlightAdminProjectManagement {
|
||||
|
||||
@Inject
|
||||
private ProjectService projectService;
|
||||
|
||||
@Inject
|
||||
private SplitOrRestartProjectService splitOrRestartProjectService;
|
||||
|
||||
public AdminSplitProjectPage(PageParameters pp) {
|
||||
final long id = pp.get(PageParameterKeys.MAP.get(Project.class)).toLong(0);
|
||||
final Project project = projectService.findOne(id);
|
||||
if (project == null) {
|
||||
throw new RestartResponseException(AdminCreateProjectPage.class);
|
||||
}
|
||||
DetachableServiceModel<Project> dsModel = new DetachableServiceModel<>(projectService, project);
|
||||
|
||||
add(new Label("projectTitle", dsModel.map(Project::getTitle)));
|
||||
|
||||
add(new SplitProjectForm("splitProjectForm", dsModel));
|
||||
}
|
||||
|
||||
private class SplitProjectForm extends Form<Project> {
|
||||
|
||||
public SplitProjectForm(String id, final IModel<Project> model) {
|
||||
super(id, model);
|
||||
add(
|
||||
new ListView<>("authorList", model.map(Project::getProjectParticipants).map(ArrayList::new)) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<User> item) {
|
||||
item.add(new Label("author", item.getModel().map(User::getFullName)));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add(new BookmarkablePageLink<Void>("cancelLink", ProjectManagementPage.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSubmit() {
|
||||
Long projectId = getModel().getObject().getId();
|
||||
|
||||
splitOrRestartProjectService.splitProject(projectId);
|
||||
|
||||
final PageParameters pp = new PageParameters();
|
||||
pp.set(PageParameterKeys.MAP.get(Project.class), projectId);
|
||||
|
||||
setResponsePage(AdminViewParentProjectPage.class, pp);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<div class="row mt-5">
|
||||
<div class="col-md-12 col-lg-8 col-xl-5">
|
||||
<p>
|
||||
<a class="btn" wicket:id="splitProjectLink"><wicket:message key="splitButton" /></a>
|
||||
</p>
|
||||
<p wicket:id="splitInfo"></p>
|
||||
</div>
|
||||
</div>
|
||||
</wicket:panel>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,78 @@
|
||||
package se.su.dsv.scipro.admin.pages;
|
||||
|
||||
import static se.su.dsv.scipro.project.split.SplitOrRestartProjectService.SplittableStatus;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.wicket.behavior.AttributeAppender;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.link.AbstractLink;
|
||||
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.session.SciProSession;
|
||||
import se.su.dsv.scipro.util.PageParameterKeys;
|
||||
|
||||
public class AdminSplitProjectPanel extends Panel {
|
||||
|
||||
@Inject
|
||||
private SplitOrRestartProjectService splitOrRestartProjectService;
|
||||
|
||||
public AdminSplitProjectPanel(String id, final IModel<Project> projectModel) {
|
||||
super(id, projectModel);
|
||||
Project project = projectModel.getObject();
|
||||
|
||||
LoadableDetachableModel<SplittableStatus> ldModel = LoadableDetachableModel.of(() -> {
|
||||
SplitOrRestartProjectService.SplittableStatusRecord status =
|
||||
splitOrRestartProjectService.getSplittableStatus(project.getId());
|
||||
return status.splittableStatus();
|
||||
});
|
||||
|
||||
final PageParameters pp = new PageParameters();
|
||||
pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
|
||||
|
||||
AbstractLink splitProjectLink = new BookmarkablePageLink<Void>(
|
||||
"splitProjectLink",
|
||||
AdminSplitProjectPage.class,
|
||||
pp
|
||||
) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
|
||||
if (ldModel.getObject() == SplittableStatus.OK) {
|
||||
setEnabled(true);
|
||||
this.add(new AttributeAppender("class", Model.of(" btn-success")));
|
||||
} else {
|
||||
setEnabled(false);
|
||||
this.add(new AttributeAppender("class", Model.of(" btn-secondary disabled")));
|
||||
}
|
||||
}
|
||||
};
|
||||
add(splitProjectLink);
|
||||
|
||||
add(new Label("splitInfo", ldModel.map(this::getStatusMessage)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisibilityAllowed(SciProSession.get().authorizedForRole(Roles.ADMIN));
|
||||
}
|
||||
|
||||
private String getStatusMessage(SplittableStatus status) {
|
||||
return switch (status) {
|
||||
case OK -> "";
|
||||
case NOT_EXIST -> getString("status_not_exist");
|
||||
case NOT_ACTIVE -> getString("status_not_active");
|
||||
case NOT_TWO_PARTICIPANTS -> getString("status_not_two_participants");
|
||||
case PHASE_TWO_STARTED -> getString("status_phase_two_started");
|
||||
case FINAL_SEMINAR_PHASE_STARTED -> getString("status_final_seminar_phase_started");
|
||||
};
|
||||
}
|
||||
}
|
6
view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
Normal file
6
view/src/main/java/se/su/dsv/scipro/admin/pages/AdminSplitProjectPanel.utf8.properties
Normal file
@ -0,0 +1,6 @@
|
||||
splitButton = Split Project
|
||||
status_not_exit = Project does not exist.
|
||||
status_not_active = Only active project can be split.
|
||||
status_not_two_participants = To be able to split a project, it needs to have 2 participants.
|
||||
status_phase_two_started = Phase 2 (Review) is already started, can't split right now.
|
||||
status_final_seminar_phase_started = Final seminar phase has been started, too late to split.
|
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org" lang="en">
|
||||
<body>
|
||||
<wicket:extend>
|
||||
<div class="row">
|
||||
<div class="col-lg-5">
|
||||
<h4 wicket:id="projectTitle"></h4>
|
||||
|
||||
<p>The project has following children projects:</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<ul wicket:id="projectList">
|
||||
<li><a wicket:id="editLink"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<a class="btn btn-success" wicket:id="link">Projects</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</wicket:extend>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,67 @@
|
||||
package se.su.dsv.scipro.admin.pages;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
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.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminProjectManagement;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
|
||||
import se.su.dsv.scipro.security.auth.Authorization;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.util.PageParameterKeys;
|
||||
|
||||
@Authorization(authorizedRoles = { Roles.SYSADMIN })
|
||||
public class AdminViewParentProjectPage
|
||||
extends AbstractAdminProjectPage
|
||||
implements MenuHighlightAdminProjectManagement {
|
||||
|
||||
@Inject
|
||||
private ProjectService projectService;
|
||||
|
||||
@Inject
|
||||
private SplitOrRestartProjectService splitOrRestartProjectService;
|
||||
|
||||
public AdminViewParentProjectPage(PageParameters pp) {
|
||||
final long id = pp.get(PageParameterKeys.MAP.get(Project.class)).toLong(0);
|
||||
final Project project = projectService.findOne(id);
|
||||
if (project == null) {
|
||||
throw new RestartResponseException(AdminCreateProjectPage.class);
|
||||
}
|
||||
|
||||
add(new Label("projectTitle", Model.of(project.getTitle())));
|
||||
|
||||
LoadableDetachableModel<List<Project>> ldModel = LoadableDetachableModel.of(() ->
|
||||
splitOrRestartProjectService.getChildProjects(id)
|
||||
);
|
||||
|
||||
add(
|
||||
new ListView<>("projectList", ldModel) {
|
||||
@Override
|
||||
protected void populateItem(ListItem<Project> item) {
|
||||
Project project = item.getModelObject();
|
||||
|
||||
final PageParameters pp = new PageParameters();
|
||||
pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
|
||||
|
||||
BookmarkablePageLink<Void> link = new BookmarkablePageLink<Void>(
|
||||
"editLink",
|
||||
AdminEditProjectPage.class,
|
||||
pp
|
||||
);
|
||||
link.setBody(item.getModel().map(p -> p.getTitle() + " - " + p.getAuthorNames()));
|
||||
item.add(link);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add(new BookmarkablePageLink<Void>("link", ProjectManagementPage.class));
|
||||
}
|
||||
}
|
@ -83,10 +83,11 @@ public abstract class ApprovalReviewerPanel extends Panel {
|
||||
LinkWrapper.apply(componentId, id -> {
|
||||
AbstractLink link = newDecisionLink(id, rowModel.map(Decision::getReviewerApproval));
|
||||
link.setBody(
|
||||
rowModel
|
||||
.map(Decision::getReviewerApproval)
|
||||
.map(ReviewerApproval::getProject)
|
||||
.map(Project::getTitle)
|
||||
rowModel.map(dec -> {
|
||||
ReviewerApproval ra = dec.getReviewerApproval();
|
||||
Project p = ra.getProject();
|
||||
return ra.getCloned() ? p.getTitle() + " (Cloned)" : p.getTitle();
|
||||
})
|
||||
);
|
||||
return link;
|
||||
})
|
||||
|
@ -97,6 +97,7 @@ import se.su.dsv.scipro.project.ProjectNoteService;
|
||||
import se.su.dsv.scipro.project.ProjectPeopleStatisticsService;
|
||||
import se.su.dsv.scipro.project.ProjectService;
|
||||
import se.su.dsv.scipro.project.pages.ProjectStartPage;
|
||||
import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
|
||||
import se.su.dsv.scipro.projectpartner.ProjectPartnerService;
|
||||
import se.su.dsv.scipro.reflection.ReflectionService;
|
||||
import se.su.dsv.scipro.report.GradeCalculatorService;
|
||||
@ -252,6 +253,9 @@ public abstract class SciProTest {
|
||||
@Mock
|
||||
protected ProjectService projectService;
|
||||
|
||||
@Mock
|
||||
protected SplitOrRestartProjectService splitOrRestartProjectService;
|
||||
|
||||
@Mock
|
||||
protected ResearchAreaService researchAreaService;
|
||||
|
||||
|
@ -20,11 +20,13 @@ import se.su.dsv.scipro.mail.MailEvent;
|
||||
import se.su.dsv.scipro.notifications.dataobject.NotificationSource;
|
||||
import se.su.dsv.scipro.notifications.dataobject.ProjectEvent;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.project.split.SplitOrRestartProjectService;
|
||||
import se.su.dsv.scipro.security.auth.roles.Roles;
|
||||
import se.su.dsv.scipro.system.DegreeType;
|
||||
import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.PageParameterKeys;
|
||||
import se.su.dsv.scipro.util.Pair;
|
||||
|
||||
public class AdminEditProjectPageTest extends SciProTest {
|
||||
|
||||
@ -269,6 +271,16 @@ public class AdminEditProjectPageTest extends SciProTest {
|
||||
|
||||
private void startPage(Project project) {
|
||||
if (project.getId() != null) when(projectService.findOne(project.getId())).thenReturn(project);
|
||||
lenient()
|
||||
.when(splitOrRestartProjectService.getSplittableStatus(project.getId() != null ? project.getId() : 0L))
|
||||
.thenReturn(
|
||||
new SplitOrRestartProjectService.SplittableStatusRecord(
|
||||
SplitOrRestartProjectService.SplittableStatus.OK,
|
||||
project,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
PageParameters pp = new PageParameters();
|
||||
pp.set(PageParameterKeys.MAP.get(Project.class), project.getId());
|
||||
tester.startPage(AdminEditProjectPage.class, pp);
|
||||
|
Loading…
x
Reference in New Issue
Block a user
Use
assertEquals
instead ofassertTrue
. (This goes for every test).