Merge branch 'develop' into wicket-10
# Conflicts: # view/src/main/java/se/su/dsv/scipro/supervisor/pages/SupervisorViewGroupThreadPage.java
This commit is contained in:
commit
42cd644e74
core/src
main
java/se/su/dsv/scipro
finalseminar
forum
BasicForumServiceImpl.javaForumPostReadEvent.javaForumPostReadStateRepository.javaForumPostReadStateRepositoryImpl.java
notifications
grading
GradingService.javaGradingServiceImpl.javaPublicationMetadataService.javaPublicationMetadataServiceImpl.java
match
report
resources
test/java/se/su/dsv/scipro
view
pom.xml
src
main/java/se/su/dsv/scipro
SciProApplication.java
admin/pages
components
finalthesis
grading
CriteriaPanel.javaGradingBasisPanel.utf8.propertiesIndividualAuthorAssessmentPanel.htmlIndividualAuthorAssessmentPanel.javaIndividualAuthorAssessmentPanel.utf8.propertiesPublicationMetadataFormComponentPanel.htmlPublicationMetadataFormComponentPanel.javaSendToExaminer.htmlSendToExaminer.javaSupervisorGradingPage.javaSupervisorGradingReportPage.java
match
ProjectIdeaSubmissionPage.htmlProjectIdeaSubmissionPanel.javaProjectIdeaSubmissionPanel.propertiesProjectMyIdeasPanel.java
notifications/pages
project/panels
reviewer
supervisor/pages
test/java/se/su/dsv/scipro
@ -71,6 +71,7 @@ public class FinalSeminarServiceImpl extends AbstractServiceImpl<FinalSeminar, L
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Either<SchedulingError, FinalSeminar> schedule(Project project, LocalDateTime when, FinalSeminarDetails details) {
|
||||
if (project.isFinalSeminarRuleExempted()) {
|
||||
return createSeminar(project, when, details);
|
||||
@ -86,7 +87,14 @@ public class FinalSeminarServiceImpl extends AbstractServiceImpl<FinalSeminar, L
|
||||
return Either.left(new RoughDraftNotApproved());
|
||||
}
|
||||
|
||||
return createSeminar(project, when, details);
|
||||
final FinalSeminar current = findByProject(project);
|
||||
if (current == null) {
|
||||
return createSeminar(project, when, details);
|
||||
}
|
||||
else {
|
||||
// Assume double click sends the same data so no need to change anything
|
||||
return Either.right(current);
|
||||
}
|
||||
}
|
||||
|
||||
private MovingError validateSchedulingRules(LocalDate date) {
|
||||
|
@ -51,8 +51,11 @@ public class BasicForumServiceImpl implements BasicForumService {
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean setThreadRead(User user, ForumThread forumThread, boolean read) {
|
||||
for (ForumPost post : forumThread.getPosts()) {
|
||||
setRead(user, post, read);
|
||||
readStateRepository.setThreadRead(user, forumThread, read);
|
||||
if (read) {
|
||||
for (ForumPost post : forumThread.getPosts()) {
|
||||
eventBus.post(new ForumPostReadEvent(post, user));
|
||||
}
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
@ -3,20 +3,5 @@ package se.su.dsv.scipro.forum;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
public final class ForumPostReadEvent {
|
||||
private final ForumPost post;
|
||||
private final User user;
|
||||
|
||||
public ForumPostReadEvent(final ForumPost post, final User user) {
|
||||
this.post = post;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public ForumPost getPost() {
|
||||
return post;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
public record ForumPostReadEvent(ForumPost post, User user) {
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import org.springframework.data.querydsl.QueryDslPredicateExecutor;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPostReadState;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPostReadStateId;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumThread;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
@Transactional
|
||||
@ -13,4 +14,6 @@ public interface ForumPostReadStateRepository
|
||||
extends JpaRepository<ForumPostReadState, ForumPostReadStateId>, QueryDslPredicateExecutor<ForumPostReadState> {
|
||||
|
||||
ForumPostReadState find(User user, ForumPost post);
|
||||
|
||||
void setThreadRead(User user, ForumThread forumThread, boolean read);
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
package se.su.dsv.scipro.forum;
|
||||
|
||||
import com.google.inject.persist.Transactional;
|
||||
import jakarta.persistence.LockModeType;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPost;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPostReadState;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumPostReadStateId;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumThread;
|
||||
import se.su.dsv.scipro.forum.dataobjects.QForumPostReadState;
|
||||
import se.su.dsv.scipro.system.GenericRepo;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
@ -23,4 +26,21 @@ public class ForumPostReadStateRepositoryImpl extends GenericRepo<ForumPostReadS
|
||||
public ForumPostReadState find(User user, ForumPost post) {
|
||||
return findOne(allOf(QForumPostReadState.forumPostReadState.id.user.eq(user), QForumPostReadState.forumPostReadState.id.post.eq(post)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void setThreadRead(User user, ForumThread forumThread, boolean read) {
|
||||
EntityManager em = em();
|
||||
em.lock(forumThread, LockModeType.PESSIMISTIC_WRITE);
|
||||
for (ForumPost post : forumThread.getPosts()) {
|
||||
ForumPostReadState state = find(user, post);
|
||||
if (state == null) {
|
||||
state = new ForumPostReadState();
|
||||
state.setId(new ForumPostReadStateId(user, post));
|
||||
}
|
||||
state.setRead(read);
|
||||
em.persist(state);
|
||||
}
|
||||
em.lock(forumThread, LockModeType.NONE);
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ public class ForumNotifications {
|
||||
@Subscribe
|
||||
@Transactional
|
||||
public void forumPostRead(ForumPostReadEvent forumPostReadEvent) {
|
||||
forumNotificationRepository.findByForumPost(forumPostReadEvent.getPost()).ifPresent(connection ->
|
||||
notificationService.setRead(forumPostReadEvent.getUser(), connection.getNotificationEvent(), true));
|
||||
forumNotificationRepository.findByForumPost(forumPostReadEvent.post()).ifPresent(connection ->
|
||||
notificationService.setRead(forumPostReadEvent.user(), connection.getNotificationEvent(), true));
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,9 @@ import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
|
||||
public interface GradingService {
|
||||
/**
|
||||
* @return the list of examinations for the given project and author, or {@code null} if the request failed
|
||||
*/
|
||||
List<Examination> getExaminations(String token, long projectId, long authorId);
|
||||
|
||||
Either<GetGradeError, Optional<Result>> getResult(String token, long projectId, long authorId, long examinationId);
|
||||
|
@ -46,7 +46,7 @@ public class GradingServiceImpl implements GradingService {
|
||||
return response.readEntity(EXAMINATION_LIST);
|
||||
}
|
||||
else {
|
||||
return Collections.emptyList();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,4 +6,6 @@ public interface PublicationMetadataService {
|
||||
PublicationMetadata getByProject(Project project);
|
||||
|
||||
void save(PublicationMetadata publicationMetadata);
|
||||
|
||||
boolean hasSuppliedPublicationMetadata(Project project, boolean noNationalSubjectCategoriesAvailable);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package se.su.dsv.scipro.grading;
|
||||
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.system.Language;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Objects;
|
||||
@ -29,4 +30,17 @@ class PublicationMetadataServiceImpl implements PublicationMetadataService {
|
||||
public void save(PublicationMetadata publicationMetadata) {
|
||||
publicationMetadataRepository.save(publicationMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSuppliedPublicationMetadata(Project project, boolean noNationalSubjectCategoriesAvailable) {
|
||||
final PublicationMetadata metadata = getByProject(project);
|
||||
return notBlank(metadata.getAbstractEnglish()) &&
|
||||
(project.getLanguage() == Language.ENGLISH || notBlank(metadata.getAbstractSwedish())) &&
|
||||
(noNationalSubjectCategoriesAvailable || metadata.getNationalSubjectCategory() != null);
|
||||
}
|
||||
|
||||
private boolean notBlank(String s) {
|
||||
return s != null && !s.isBlank();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ public class IdeaServiceImpl extends AbstractServiceImpl<Idea, Long> implements
|
||||
public static final String NO_LONGER_AVAILABLE_ERROR = "Idea is no longer available";
|
||||
public static final String ALREADY_PARTICIPATING_ERROR = "You are already participating in another idea";
|
||||
public static final String PARTNER_ALREADY_PARTICIPATING_ERROR = "Your partner is already participating in another idea";
|
||||
public static final String SELECTED_IDEA = "You selected idea: ";
|
||||
public static final String BACHELOR_NEED_PARTNER_ERROR = "You need to select a partner when the idea is on bachelor level";
|
||||
public static final String ADD_SELF_AS_PARTNER_ERROR = "You may not add yourself as project partner";
|
||||
public static final String NO_AUTHORS_ERROR = "The idea is submitted by a student, number of students is not allowed";
|
||||
@ -217,7 +216,8 @@ public class IdeaServiceImpl extends AbstractServiceImpl<Idea, Long> implements
|
||||
return new Pair<>(Boolean.FALSE, WRONG_LEVEL_FOR_YOU);
|
||||
}
|
||||
|
||||
return new Pair<>(Boolean.TRUE, SELECTED_IDEA + idea.getTitle());
|
||||
return new Pair<>(Boolean.TRUE, "You have successfully selected the supervisor idea "
|
||||
+ idea.getTitle() + ", in the application period " + ap.getName());
|
||||
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import se.su.dsv.scipro.system.ProjectType;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -29,6 +30,10 @@ public abstract class GradingReport extends Report {
|
||||
@OneToMany(mappedBy = "gradingReport", cascade = {CascadeType.ALL})
|
||||
private List<GradingCriterion> gradingCriteria = new ArrayList<>();
|
||||
|
||||
@Basic
|
||||
@Column(name = "date_submitted_to_examiner")
|
||||
private Instant dateSubmittedToExaminer;
|
||||
|
||||
protected GradingReport() {
|
||||
// JPA
|
||||
}
|
||||
@ -37,6 +42,7 @@ public abstract class GradingReport extends Report {
|
||||
public void submit() {
|
||||
super.submit();
|
||||
setState(State.FINALIZED);
|
||||
setDateSubmittedToExaminer(Instant.now());
|
||||
}
|
||||
|
||||
public Project getProject() {
|
||||
@ -51,6 +57,14 @@ public abstract class GradingReport extends Report {
|
||||
gradingCriteria.add(criterion);
|
||||
}
|
||||
|
||||
public Instant getDateSubmittedToExaminer(){
|
||||
return this.dateSubmittedToExaminer;
|
||||
}
|
||||
|
||||
public void setDateSubmittedToExaminer(Instant dateSubmittedToExaminer) {
|
||||
this.dateSubmittedToExaminer = dateSubmittedToExaminer;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import se.su.dsv.scipro.system.GenericService;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.Either;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public interface GradingReportService extends GenericService<GradingReport, Long> {
|
||||
@ -24,4 +25,6 @@ public interface GradingReportService extends GenericService<GradingReport, Long
|
||||
GradingBasis getGradingBasis(Project project);
|
||||
|
||||
GradingBasis updateGradingBasis(Project project, GradingBasis gradingBasis);
|
||||
|
||||
Instant getDateSentToExaminer(Project project);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Provider;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
@Named
|
||||
@ -92,6 +93,16 @@ public class GradingReportServiceImpl extends AbstractServiceImpl<GradingReport,
|
||||
return getGradingBasis(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getDateSentToExaminer(Project project) {
|
||||
return getSupervisorGradingReports(project)
|
||||
.stream()
|
||||
.map(SupervisorGradingReport::getDateSubmittedToExaminer)
|
||||
.filter(Objects::nonNull)
|
||||
.max(Comparator.naturalOrder())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private GradingBasis.Assessment toAssessment(
|
||||
Language language,
|
||||
GradingCriterion gc) {
|
||||
|
@ -12,6 +12,9 @@
|
||||
transaction-type="RESOURCE_LOCAL">
|
||||
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
|
||||
<non-jta-data-source>java:/comp/env/jdbc/sciproDS</non-jta-data-source>
|
||||
<properties>
|
||||
<property name="hibernate.show_sql" value="false"/>
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
|
||||
<!-- A JPA Persistence Unit used for tests -->
|
||||
|
2
core/src/main/resources/db/migration/V385__grading_report_date_submitted_to_examiner.sql
Normal file
2
core/src/main/resources/db/migration/V385__grading_report_date_submitted_to_examiner.sql
Normal file
@ -0,0 +1,2 @@
|
||||
alter table GradingReport
|
||||
add date_submitted_to_examiner datetime null;
|
@ -11,8 +11,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
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.forummail.ForumMailSettingsService;
|
||||
import se.su.dsv.scipro.mail.MailEventService;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.test.ForumBuilder;
|
||||
import se.su.dsv.scipro.test.UserBuilder;
|
||||
@ -35,10 +33,6 @@ public class BasicForumServiceImplTest {
|
||||
@Mock
|
||||
private ForumPostRepository postRepository;
|
||||
@Mock
|
||||
private MailEventService mailEventService;
|
||||
@Mock
|
||||
private ForumMailSettingsService mailSettingsService;
|
||||
@Mock
|
||||
private EventBus eventBus;
|
||||
@InjectMocks
|
||||
private BasicForumServiceImpl basicForumService;
|
||||
@ -93,23 +87,20 @@ public class BasicForumServiceImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkThreadRead() {
|
||||
when(readStateRepository.find(any(User.class), any(ForumPost.class))).thenReturn(new ForumPostReadState());
|
||||
when(readStateRepository.save(isA(ForumPostReadState.class))).thenAnswer(AdditionalAnswers.returnsFirstArg());
|
||||
|
||||
public void testMarkThreadReadPostsEvent() {
|
||||
User user = new User();
|
||||
ForumPost post = new ForumPost();
|
||||
post.setContent("post 1");
|
||||
ForumPost post2 = new ForumPost();
|
||||
post2.setContent("post 2");
|
||||
ForumThread forumThread = new ForumThread();
|
||||
forumThread.addPost(post);
|
||||
forumThread.addPost(post2);
|
||||
|
||||
basicForumService.setThreadRead(user, forumThread, true);
|
||||
|
||||
ArgumentCaptor<ForumPostReadState> captor = ArgumentCaptor.forClass(ForumPostReadState.class);
|
||||
verify(readStateRepository, times(2)).save(captor.capture());
|
||||
|
||||
assertTrue(captor.getValue().isRead(), "Did not save correct read state");
|
||||
verify(eventBus).post(new ForumPostReadEvent(post, user));
|
||||
verify(eventBus).post(new ForumPostReadEvent(post2, user));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -181,8 +172,8 @@ public class BasicForumServiceImplTest {
|
||||
verify(eventBus).post(captor.capture());
|
||||
|
||||
ForumPostReadEvent event = captor.getValue();
|
||||
assertEquals(event.getPost(), post);
|
||||
assertEquals(event.getUser(), user);
|
||||
assertEquals(event.post(), post);
|
||||
assertEquals(event.user(), user);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -204,7 +204,12 @@ public class IdeaServiceImplTest {
|
||||
when(applicationPeriodService.getTypesForStudent(applicationPeriod, student))
|
||||
.thenReturn(List.of(bachelor));
|
||||
|
||||
assertPair(true, SELECTED_IDEA + idea.getTitle(), ideaService.validateStudentAcceptance(idea, student, coAuthor, applicationPeriod));
|
||||
Pair<Boolean, String> acceptance = ideaService.validateStudentAcceptance(
|
||||
idea,
|
||||
student,
|
||||
coAuthor,
|
||||
applicationPeriod);
|
||||
assertTrue(acceptance.getHead());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
14
owasp.xml
14
owasp.xml
@ -51,4 +51,18 @@
|
||||
</notes>
|
||||
<cve>CVE-2023-35116</cve>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes>
|
||||
This is a complete nonsense vulnerability. Some automated tool has
|
||||
gone completely bananas.
|
||||
</notes>
|
||||
<cve>CVE-2024-22949</cve>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes>
|
||||
This is a complete nonsense vulnerability. Some automated tool has
|
||||
gone completely bananas.
|
||||
</notes>
|
||||
<cve>CVE-2023-52070</cve>
|
||||
</suppress>
|
||||
</suppressions>
|
||||
|
@ -64,6 +64,11 @@
|
||||
<groupId>com.lowagie</groupId>
|
||||
<artifactId>itext</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<!-- until a new version containing https://github.com/wicketstuff/core/pull/873 is released -->
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -5,6 +5,7 @@ import org.apache.wicket.*;
|
||||
import org.apache.wicket.authorization.strategies.CompoundAuthorizationStrategy;
|
||||
import org.apache.wicket.csp.CSPDirective;
|
||||
import org.apache.wicket.csp.CSPDirectiveSrcValue;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.request.Request;
|
||||
import org.apache.wicket.request.Response;
|
||||
import org.apache.wicket.resource.JQueryResourceReference;
|
||||
@ -24,6 +25,7 @@ import se.su.dsv.scipro.checklists.AdminChecklistPage;
|
||||
import se.su.dsv.scipro.checklists.AdminEditChecklistTemplatePage;
|
||||
import se.su.dsv.scipro.checklists.ProjectViewChecklistPage;
|
||||
import se.su.dsv.scipro.checklists.SupervisorViewChecklistPage;
|
||||
import se.su.dsv.scipro.components.DisableSubmitButtonsOnSubmit;
|
||||
import se.su.dsv.scipro.examiner.pages.ExaminerStartPage;
|
||||
import se.su.dsv.scipro.finalseminar.*;
|
||||
import se.su.dsv.scipro.finalthesis.SupervisorFinalThesisListingPage;
|
||||
@ -80,6 +82,7 @@ import se.su.dsv.scipro.util.AdditionalExceptionLogger;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class SciProApplication extends LifecycleManagedWebApplication {
|
||||
@ -106,6 +109,12 @@ public class SciProApplication extends LifecycleManagedWebApplication {
|
||||
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
});
|
||||
converterLocator.set(ZonedDateTime.class, new LocalDateTimeConverter() {
|
||||
@Override
|
||||
protected DateTimeFormatter getDateTimeFormatter() {
|
||||
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
});
|
||||
return converterLocator;
|
||||
}
|
||||
|
||||
@ -150,6 +159,12 @@ public class SciProApplication extends LifecycleManagedWebApplication {
|
||||
.add(CSPDirective.IMG_SRC, "data:");
|
||||
|
||||
WicketWebjars.install(this);
|
||||
|
||||
getComponentInstantiationListeners().add(component -> {
|
||||
if (component instanceof Form) {
|
||||
component.add(new DisableSubmitButtonsOnSubmit());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void mountForumPage() {
|
||||
|
@ -22,8 +22,10 @@
|
||||
<dt>Research area</dt>
|
||||
<dd wicket:id="research_area"></dd>
|
||||
|
||||
<dt>Language</dt>
|
||||
<dd wicket:id="language"></dd>
|
||||
<wicket:enclosure>
|
||||
<dt>Language</dt>
|
||||
<dd wicket:id="language"></dd>
|
||||
</wicket:enclosure>
|
||||
|
||||
<wicket:enclosure>
|
||||
<dt>Reviewer requested at</dt>
|
||||
|
@ -84,7 +84,13 @@ public class AdminAssignReviewerPage extends AbstractAdminProjectPage {
|
||||
add(new Label("title", projectModel.map(Project::getTitle)));
|
||||
add(new Label("research_area", projectModel.map(Project::getResearchArea).map(ResearchArea::getTitle)));
|
||||
add(new UserLinkPanel("supervisor", projectModel.map(Project::getHeadSupervisor)));
|
||||
add(new EnumLabel<>("language", projectModel.map(Project::getLanguage)));
|
||||
add(new EnumLabel<>("language", projectModel.map(Project::getLanguage)){
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(getDefaultModelObject() != null);
|
||||
}
|
||||
});
|
||||
add(new ViewAttachmentPanel("rough_draft", roughDraftApproval.map(RoughDraftApproval::getCurrentThesis).map(FileReference::getFileDescription)));
|
||||
add(new DateLabel("requested_at", roughDraftApproval.map(RoughDraftApproval::getCurrentDecision).map(Decision::getRequested), DateStyle.DATETIME) {
|
||||
@Override
|
||||
|
@ -0,0 +1,30 @@
|
||||
package se.su.dsv.scipro.components;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.behavior.Behavior;
|
||||
import org.apache.wicket.markup.head.IHeaderResponse;
|
||||
import org.apache.wicket.markup.head.OnEventHeaderItem;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
|
||||
/**
|
||||
* Disables all elements with {@code [type=submit]}
|
||||
*/
|
||||
public class DisableSubmitButtonsOnSubmit extends Behavior {
|
||||
@Override
|
||||
public void bind(Component component) {
|
||||
super.bind(component);
|
||||
if (!(component instanceof Form<?>)) {
|
||||
throw new RuntimeException("Can only be used on Form components");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderHead(Component component, IHeaderResponse response) {
|
||||
super.renderHead(component, response);
|
||||
final String javaScript = "const submitButtons = event.target.querySelectorAll(\"[type=submit]\");\n" +
|
||||
"for (const button of submitButtons) {\n" +
|
||||
" button.disabled = true;\n" +
|
||||
"}\n";
|
||||
response.render(OnEventHeaderItem.forComponent(component, "submit", javaScript));
|
||||
}
|
||||
}
|
@ -4,7 +4,11 @@
|
||||
<wicket:panel>
|
||||
<strong>Status:</strong> <span wicket:id="status"></span>
|
||||
<br>
|
||||
<span wicket:id="approvedDate"></span><br>
|
||||
Approved by supervisor: <span wicket:id="approvedDate"></span><br>
|
||||
<wicket:enclosure>
|
||||
Submitted to examiner: <span wicket:id="submittedToExaminerTimestamp"></span><br>
|
||||
</wicket:enclosure>
|
||||
<br>
|
||||
</wicket:panel>
|
||||
</body>
|
||||
</html>
|
@ -3,6 +3,7 @@ package se.su.dsv.scipro.finalthesis;
|
||||
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.EnumLabel;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.DropDownChoice;
|
||||
import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
@ -19,6 +20,7 @@ import se.su.dsv.scipro.finalseminar.FinalSeminarService;
|
||||
import se.su.dsv.scipro.forum.pages.ProjectForumBasePage;
|
||||
import se.su.dsv.scipro.project.Project;
|
||||
import se.su.dsv.scipro.reflection.ReflectionService;
|
||||
import se.su.dsv.scipro.report.GradingReportService;
|
||||
import se.su.dsv.scipro.security.auth.ProjectModuleComponent;
|
||||
import se.su.dsv.scipro.session.SciProSession;
|
||||
import se.su.dsv.scipro.system.ProjectModule;
|
||||
@ -26,6 +28,8 @@ import se.su.dsv.scipro.util.PageParameterKeys;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static se.su.dsv.scipro.finalthesis.FinalThesis.Status;
|
||||
@ -40,6 +44,7 @@ public class FinalThesisPanel extends GenericPanel<Project> {
|
||||
public static final String APPROVED_PANEL = "approvedPanel";
|
||||
public static final String APPROVED_DATE = "approvedDate";
|
||||
public static final String NO_DECISION_PANEL = "noDecisionPanel";
|
||||
public static final String SUBMITTED_TO_EXAMINER_TIMESTAMP = "submittedToExaminerTimestamp";
|
||||
|
||||
@Inject
|
||||
private FinalThesisService finalThesisService;
|
||||
@ -49,6 +54,8 @@ public class FinalThesisPanel extends GenericPanel<Project> {
|
||||
private PublishingConsentService publishingConsentService;
|
||||
@Inject
|
||||
private ReflectionService reflectionService;
|
||||
@Inject
|
||||
private GradingReportService gradingReportService;
|
||||
|
||||
public FinalThesisPanel(String id, IModel<Project> project) {
|
||||
super(id, project);
|
||||
@ -96,9 +103,17 @@ public class FinalThesisPanel extends GenericPanel<Project> {
|
||||
private class ApprovedPanel extends Panel {
|
||||
public ApprovedPanel(String id) {
|
||||
super(id);
|
||||
|
||||
add(new EnumLabel<>("status", getModel().map(Project::getProjectStatus)));
|
||||
add(new DateLabel(APPROVED_DATE, getFinalThesis().map(FinalThesis::getDateApproved)));
|
||||
IModel<ZonedDateTime> submittedToExaminerTimestamp = LoadableDetachableModel.of(() -> gradingReportService.getDateSentToExaminer(getModelObject()))
|
||||
.map(instant -> instant.atZone(ZoneId.systemDefault()));
|
||||
add(new Label(SUBMITTED_TO_EXAMINER_TIMESTAMP, submittedToExaminerTimestamp) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(submittedToExaminerTimestamp.getObject() != null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -246,7 +246,9 @@ public class CriteriaPanel extends GenericPanel<SupervisorGradingReport> {
|
||||
|
||||
@Override
|
||||
public void setObject(GradingCriterionPoint object) {
|
||||
criterionIModel.getObject().setPoints(object.getPoint());
|
||||
if (object != null) {
|
||||
criterionIModel.getObject().setPoints(object.getPoint());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
save = Save
|
||||
overall_motivation = Overall motivation
|
||||
grading_basis_updated = Assessment saved at ${}
|
||||
rejection_comment_feedback.Required = Rejection commend feedback must be provided.
|
||||
rejection_comment_feedback.Required = Rejection comment feedback must be provided.
|
||||
|
@ -36,6 +36,7 @@
|
||||
<ul>
|
||||
<li wicket:id="status_final_thesis"></li>
|
||||
<li wicket:id="status_plagiarism"></li>
|
||||
<li wicket:id="status_publication_metadata"></li>
|
||||
<li>
|
||||
<div wicket:id="status_grading_basis">></div>
|
||||
<ul wicket:id="grading_basis_missing">
|
@ -33,8 +33,10 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class IndividualAuthorAssessment extends GenericPanel<User> {
|
||||
public class IndividualAuthorAssessmentPanel extends GenericPanel<User> {
|
||||
|
||||
@Inject
|
||||
private NationalSubjectCategoryService nationalSubjectCategoryService;
|
||||
@Inject
|
||||
private GradingReportService gradingReportService;
|
||||
@Inject
|
||||
@ -43,10 +45,12 @@ public class IndividualAuthorAssessment extends GenericPanel<User> {
|
||||
private FinalThesisService finalThesisService;
|
||||
@Inject
|
||||
private FinalSeminarService finalSeminarService;
|
||||
@Inject
|
||||
private PublicationMetadataService publicationMetadataService;
|
||||
|
||||
private final IModel<Project> projectModel;
|
||||
|
||||
public IndividualAuthorAssessment(String id, IModel<Project> projectModel, IModel<User> authorModel) {
|
||||
public IndividualAuthorAssessmentPanel(String id, IModel<Project> projectModel, IModel<User> authorModel) {
|
||||
super(id, authorModel);
|
||||
this.projectModel = projectModel;
|
||||
|
||||
@ -73,6 +77,10 @@ public class IndividualAuthorAssessment extends GenericPanel<User> {
|
||||
redGreen("status_plagiarism", hasSubmittedPlagiarismAnalysis,
|
||||
"must_perform_plagiarism_check",
|
||||
"plagiarism_check_performed");
|
||||
IModel<Boolean> hasSuppliedPublicationMetadata = Model.of(publicationMetadataService.hasSuppliedPublicationMetadata(projectModel.getObject(), nationalSubjectCategoryService.listCategories().isEmpty()));
|
||||
redGreen("status_publication_metadata", hasSuppliedPublicationMetadata,
|
||||
"must_supply_publication_metadata",
|
||||
"publication_metadata_supplied");
|
||||
IModel<Boolean> hasFilledInGradingBasis = gradingReport.map(this::gradingBasisDone);
|
||||
redGreen("status_grading_basis", hasFilledInGradingBasis,
|
||||
"grading_basis_must_meet_minimum_requirements",
|
||||
@ -169,6 +177,7 @@ public class IndividualAuthorAssessment extends GenericPanel<User> {
|
||||
super.onConfigure();
|
||||
setVisible(hasApprovedFinalThesis.getObject()
|
||||
&& hasSubmittedPlagiarismAnalysis.getObject()
|
||||
&& hasSuppliedPublicationMetadata.getObject()
|
||||
&& hasFilledInGradingBasis.getObject()
|
||||
&& hasFilledInIndividualAssessment.getObject());
|
||||
}
|
@ -4,6 +4,8 @@ must_approve_final_thesis = You must approve the final thesis.
|
||||
final_thesis_approved = Final thesis approved.
|
||||
must_perform_plagiarism_check = You have to check the text matching report and perform a plagiarism analysis.
|
||||
plagiarism_check_performed = Plagiarism analysis submitted.
|
||||
must_supply_publication_metadata = You must supply publication metadata.
|
||||
publication_metadata_supplied = Publication metadata supplied.
|
||||
grading_basis_must_meet_minimum_requirements = General criteria not met.
|
||||
grading_basis_minimum_requirements_met = General criteria met.
|
||||
individual_assessment_must_meet_minimum_requirements = Not all individual criteria are met.
|
@ -3,12 +3,12 @@
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="abstract_en">Abstract (English)</label>
|
||||
<label class="form-label" for="abstract_en">Abstract (English) (required)</label>
|
||||
<textarea class="form-control" id="abstract_en" wicket:id="abstract_en"></textarea>
|
||||
</div>
|
||||
<wicket:enclosure>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="abstract_sv">Abstract (Swedish)</label>
|
||||
<label class="form-label" for="abstract_sv">Abstract (Swedish) (required)</label>
|
||||
<textarea class="form-control" id="abstract_sv" wicket:id="abstract_sv"></textarea>
|
||||
</div>
|
||||
</wicket:enclosure>
|
||||
@ -22,11 +22,13 @@
|
||||
<input class="form-control" id="keywords_sv" wicket:id="keywords_sv">
|
||||
</div>
|
||||
</wicket:enclosure>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="national_subject_category">National subject category</label>
|
||||
<select class="form-select" id="national_subject_category" wicket:id="national_subject_category">
|
||||
</select>
|
||||
</div>
|
||||
<wicket:enclosure>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="national_subject_category">National subject category (required)</label>
|
||||
<select class="form-select" id="national_subject_category" wicket:id="national_subject_category">
|
||||
</select>
|
||||
</div>
|
||||
</wicket:enclosure>
|
||||
</wicket:panel>
|
||||
</body>
|
||||
</html>
|
@ -45,6 +45,7 @@ public class PublicationMetadataFormComponentPanel extends GenericPanel<Publicat
|
||||
.ifPresent(nationalSubjectCategoryChoice::setDefaultModelObject);
|
||||
}
|
||||
nationalSubjectCategoryChoice.setNullValid(true);
|
||||
nationalSubjectCategoryChoice.setVisible(!availableCategories.getObject().isEmpty());
|
||||
add(nationalSubjectCategoryChoice);
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,20 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org" lang="en">
|
||||
<body>
|
||||
<wicket:panel>
|
||||
<wicket:enclosure child="send">
|
||||
<wicket:enclosure child="form">
|
||||
<div wicket:id="feedback"></div>
|
||||
<button type="button" class="btn btn-success scrollSneak" wicket:id="send">Send thesis for examination</button>
|
||||
<form wicket:id="form">
|
||||
<div class="mb-3">
|
||||
<label for="examinationDate" class="form-label">Examination date</label>
|
||||
<input type="text" class="form-control" id="examinationDate" wicket:id="examinationDate" required>
|
||||
<small class="form-text">
|
||||
The examination date is suggested based on the last student activity (submitted final thesis,
|
||||
opposition, or active participation). You should only change this date if the last activity was at a
|
||||
different date.
|
||||
</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success scrollSneak" wicket:id="send">Send thesis for examination</button>
|
||||
</form>
|
||||
</wicket:enclosure>
|
||||
<p class="card-text" wicket:id="already_sent">
|
||||
<span class="fa fa-check text-success"></span>
|
||||
|
@ -2,10 +2,13 @@ package se.su.dsv.scipro.grading;
|
||||
|
||||
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.form.TextField;
|
||||
import org.apache.wicket.markup.html.panel.GenericPanel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import se.su.dsv.scipro.components.ConfirmationLink;
|
||||
import org.apache.wicket.model.Model;
|
||||
import se.su.dsv.scipro.components.BootstrapDatePicker;
|
||||
import se.su.dsv.scipro.daisyExternal.http.DaisyAPI;
|
||||
import se.su.dsv.scipro.file.FileDescription;
|
||||
import se.su.dsv.scipro.file.FileService;
|
||||
@ -33,6 +36,7 @@ import se.su.dsv.scipro.system.Language;
|
||||
import se.su.dsv.scipro.system.ResearchArea;
|
||||
import se.su.dsv.scipro.system.User;
|
||||
import se.su.dsv.scipro.util.Either;
|
||||
import se.su.dsv.scipro.util.JavascriptEventConfirmation;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.LocalDate;
|
||||
@ -75,10 +79,16 @@ public class SendToExaminer extends GenericPanel<Project> {
|
||||
super(id, projectModel);
|
||||
|
||||
needsSending = LoadableDetachableModel.of(() -> hasGradedExaminationWithoutSuggestion(authorModel.getObject()));
|
||||
add(new ConfirmationLink<>("send", authorModel, confirmationMessage) {
|
||||
|
||||
IModel<LocalDate> examinationDate = new Model<>();
|
||||
FinalThesis finalThesis = finalThesisService.findByProject(projectModel.getObject());
|
||||
examinationDate.setObject(getExaminationDate(authorModel.getObject(), projectModel.getObject(), finalThesis));
|
||||
|
||||
Form<Void> form = new Form<>("form") {
|
||||
@Override
|
||||
public void onClick() {
|
||||
sendToExaminer(getModelObject());
|
||||
protected void onSubmit() {
|
||||
super.onSubmit();
|
||||
sendToExaminer(authorModel.getObject(), examinationDate.getObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -86,7 +96,20 @@ public class SendToExaminer extends GenericPanel<Project> {
|
||||
super.onConfigure();
|
||||
setVisible(needsSending.getObject());
|
||||
}
|
||||
});
|
||||
};
|
||||
add(form);
|
||||
|
||||
WebMarkupContainer sendButton = new WebMarkupContainer("send");
|
||||
if (confirmationMessage.getObject() != null) {
|
||||
sendButton.add(new JavascriptEventConfirmation("click", confirmationMessage));
|
||||
}
|
||||
form.add(sendButton);
|
||||
|
||||
TextField<LocalDate> examinationDateField = new TextField<>("examinationDate", examinationDate, LocalDate.class);
|
||||
examinationDateField.setRequired(true);
|
||||
examinationDateField.add(new BootstrapDatePicker());
|
||||
form.add(examinationDateField);
|
||||
|
||||
add(new WebMarkupContainer("already_sent") {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
@ -101,6 +124,10 @@ public class SendToExaminer extends GenericPanel<Project> {
|
||||
String token = getSession().getMetaData(OAuth.TOKEN);
|
||||
Project project = getModelObject();
|
||||
List<Examination> examinations = gradingService.getExaminations(token, project.getIdentifier(), author.getIdentifier());
|
||||
if (examinations == null) {
|
||||
// if we can't tell assume it is not sent
|
||||
return true;
|
||||
}
|
||||
for (Examination examination : examinations) {
|
||||
if (examination.hasManyPassingGrades()) {
|
||||
Either<GetGradeError, Optional<Result>> result = gradingService.getResult(token, project.getIdentifier(), author.getIdentifier(), examination.id());
|
||||
@ -112,7 +139,7 @@ public class SendToExaminer extends GenericPanel<Project> {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void sendToExaminer(User author) {
|
||||
private void sendToExaminer(User author, LocalDate examinationDate) {
|
||||
checkStepsMissing();
|
||||
if (hasErrorMessage()) {
|
||||
// some steps have not been completed
|
||||
@ -125,6 +152,10 @@ public class SendToExaminer extends GenericPanel<Project> {
|
||||
return;
|
||||
}
|
||||
List<Examination> examinations = gradingService.getExaminations(token, project.getIdentifier(), author.getIdentifier());
|
||||
if (examinations == null) {
|
||||
getSession().error("Failed to get the examination setup for " + author.getFullName());
|
||||
return;
|
||||
}
|
||||
List<Examination> gradedExaminations = examinations
|
||||
.stream()
|
||||
.filter(Examination::hasManyPassingGrades)
|
||||
@ -138,7 +169,7 @@ public class SendToExaminer extends GenericPanel<Project> {
|
||||
} else if (gradedExaminations.isEmpty()) {
|
||||
getSession().info("Nothing to report on " + author.getFullName());
|
||||
} else {
|
||||
sendSuggestion(project, author, gradedExaminations.get(0));
|
||||
sendSuggestion(project, author, gradedExaminations.get(0), examinationDate);
|
||||
}
|
||||
needsSending.detach();
|
||||
}
|
||||
@ -176,7 +207,7 @@ public class SendToExaminer extends GenericPanel<Project> {
|
||||
return missing;
|
||||
}
|
||||
|
||||
private void sendSuggestion(Project project, User author, Examination examination) {
|
||||
private void sendSuggestion(Project project, User author, Examination examination, LocalDate examinationDate) {
|
||||
String token = getSession().getMetaData(OAuth.TOKEN);
|
||||
|
||||
Either<GetGradeError, Optional<Result>> currentResult
|
||||
@ -197,7 +228,6 @@ public class SendToExaminer extends GenericPanel<Project> {
|
||||
GradeCalculator gradeCalculator = gradeCalculatorService.getSupervisorCalculator(project);
|
||||
SupervisorGradingReport supervisorGradingReport = gradingReportService.getSupervisorGradingReport(getModelObject(), author);
|
||||
GradingReport.Grade grade = gradeCalculator.getGrade(supervisorGradingReport);
|
||||
LocalDate examinationDate = getExaminationDate(author, project, finalThesis);
|
||||
Either<ReportGradeError, Void> reported =
|
||||
gradingService.reportGrade(
|
||||
token,
|
||||
|
@ -56,7 +56,8 @@ public class SupervisorGradingPage extends AbstractSupervisorProjectDetailsPage
|
||||
@Override
|
||||
protected void populateItem(final ListItem<User> item) {
|
||||
item.add(new UserLabel("authorName", item.getModel()));
|
||||
final IModel<List<Examination>> examinations = SupervisorGradingPage.this.getExaminations(item.getModel());
|
||||
final IModel<List<Examination>> examinations = SupervisorGradingPage.this.getExaminations(item.getModel())
|
||||
.orElseGet(Collections::emptyList);
|
||||
|
||||
final IModel<List<Examination>> nonGradedExaminations = getSpecificExaminations(examinations, false);
|
||||
item.add(new NonGradedPanel(
|
||||
|
@ -46,6 +46,8 @@ import java.util.Set;
|
||||
|
||||
@ProjectModuleComponent(ProjectModule.GRADING)
|
||||
public class SupervisorGradingReportPage extends AbstractSupervisorProjectDetailsPage {
|
||||
@Inject
|
||||
private NationalSubjectCategoryService nationalSubjectCategoryService;
|
||||
@Inject
|
||||
private GeneralSystemSettingsService generalSystemSettingsService;
|
||||
@Inject
|
||||
@ -76,11 +78,7 @@ public class SupervisorGradingReportPage extends AbstractSupervisorProjectDetail
|
||||
.isPresent();
|
||||
add(newGreenHighlight("step_plagiarism", hasCheckedForPlagiarism, new ResourceModel("step_plagiarism")));
|
||||
|
||||
IModel<Boolean> hasProvidedPublicationMetadata =
|
||||
projectModel.map(publicationMetadataService::getByProject)
|
||||
.filter(metadata -> notBlank(metadata.getAbstractEnglish()) || notBlank(metadata.getAbstractSwedish()))
|
||||
.filter(metadata -> notBlank(metadata.getKeywordsEnglish()) || notBlank(metadata.getKeywordsSwedish()))
|
||||
.isPresent();
|
||||
IModel<Boolean> hasProvidedPublicationMetadata = Model.of(publicationMetadataService.hasSuppliedPublicationMetadata(projectModel.getObject(), nationalSubjectCategoryService.listCategories().isEmpty()));
|
||||
add(newGreenHighlight("step_publication_metadata", hasProvidedPublicationMetadata, new ResourceModel("step_publication_metadata")));
|
||||
|
||||
IModel<List<SupervisorGradingReport>> gradingReports = LoadableDetachableModel.of(() ->
|
||||
@ -131,7 +129,7 @@ public class SupervisorGradingReportPage extends AbstractSupervisorProjectDetail
|
||||
.map(author -> new DetachableServiceModel<>(userService, author))
|
||||
.map(authorModel -> createTab(
|
||||
authorModel.map(User::getFullName),
|
||||
panelId -> new IndividualAuthorAssessment(panelId, projectModel, authorModel)))
|
||||
panelId -> new IndividualAuthorAssessmentPanel(panelId, projectModel, authorModel)))
|
||||
.toList();
|
||||
tabs.addAll(authorTabs);
|
||||
|
||||
@ -151,10 +149,6 @@ public class SupervisorGradingReportPage extends AbstractSupervisorProjectDetail
|
||||
add(new ExaminerTimelinePanel("examiner_timeline", projectModel));
|
||||
}
|
||||
|
||||
private boolean notBlank(String s) {
|
||||
return s != null && !s.isBlank();
|
||||
}
|
||||
|
||||
private Component newGreenHighlight(String id, IModel<Boolean> completed, IModel<String> text) {
|
||||
return new RedGreenLabel(id, completed, text, text);
|
||||
}
|
||||
@ -176,6 +170,10 @@ public class SupervisorGradingReportPage extends AbstractSupervisorProjectDetail
|
||||
String token = getSession().getMetaData(OAuth.TOKEN);
|
||||
Project project = projectModel.getObject();
|
||||
List<Examination> examinations = gradingService.getExaminations(token, project.getIdentifier(), author.getIdentifier());
|
||||
if (examinations == null) {
|
||||
// if grading service is down, assume not sent
|
||||
return false;
|
||||
}
|
||||
for (Examination examination : examinations) {
|
||||
if (examination.hasManyPassingGrades()) {
|
||||
Either<GetGradeError, Optional<Result>> result = gradingService.getResult(token, project.getIdentifier(), author.getIdentifier(), examination.id());
|
||||
|
@ -1,9 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:wicket="http://wicket.apache.org">
|
||||
<body>
|
||||
<wicket:extend>
|
||||
<div class="row"></div>
|
||||
<div wicket:id="submissionPanel"></div>
|
||||
<wicket:extend>
|
||||
<div class="row">
|
||||
<div class="col-lg-8 col-xl-6" wicket:id="submissionPanel"></div>
|
||||
</div>
|
||||
</wicket:extend>
|
||||
</body>
|
||||
</html>
|
@ -4,6 +4,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
|
||||
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
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.link.ExternalLink;
|
||||
@ -116,7 +117,7 @@ public class ProjectIdeaSubmissionPanel extends GenericPanel<Idea> {
|
||||
addResearchAreaAndKeywordsSelection();
|
||||
addContract();
|
||||
addDeleteButton();
|
||||
add(new Button("save"));
|
||||
add(new Label("save", isNewIdea ? new ResourceModel("save") : new ResourceModel("update")));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -321,8 +322,13 @@ public class ProjectIdeaSubmissionPanel extends GenericPanel<Idea> {
|
||||
|
||||
@Override
|
||||
protected void onSubmit() {
|
||||
ideaService.saveStudentIdea(getModelObject(), creator, programDropDownChoice.getModelObject(), new HashSet<>(coAuthorChoice.getModelObject()),
|
||||
Idea idea = ideaService.saveStudentIdea(getModelObject(), creator, programDropDownChoice.getModelObject(), new HashSet<>(coAuthorChoice.getModelObject()),
|
||||
new ArrayList<>(keywords), isNewIdea);
|
||||
if (isNewIdea) {
|
||||
getSession().success(getString("ideaSubmitted", Model.of(idea)));
|
||||
} else {
|
||||
getSession().success(getString("ideaUpdated", Model.of(idea)));
|
||||
}
|
||||
setResponsePage(ProjectIdeaStartPage.class);
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,10 @@ too.many.authors= Too many authors for a ${name}-idea. May not be more than ${ma
|
||||
too.few.authors= Too few authors for a ${name}-idea. Must be at least ${minAuthors} including yourself.
|
||||
keywordError= You need to select between 1 and 5 keywords.
|
||||
submissionFailed= Idea could not be submitted.
|
||||
ideaSubmitted= You have successfully submitted your ${projectType.name} student idea, for the application period \
|
||||
${applicationPeriod.name}.
|
||||
ideaUpdated= You have successfully updated your ${projectType.name} student idea, in the application period \
|
||||
${applicationPeriod.name}.
|
||||
titleInfo= The idea title will become the project title once the idea has been matched to a supervisor and the course started. The title can then only be changed by the supervisor.
|
||||
researchAreaInfo= The idea should be connected to a research area.
|
||||
languageInfo= The language the thesis will be written in. This will affect many areas of the thesis writing process \
|
||||
@ -35,3 +39,5 @@ programInfo= Select the program within the context of which you are doing this i
|
||||
programDropDown.nullValid=Not within a program
|
||||
you.already.have.an.active.project.on.this.level= You already have an active project on this level.
|
||||
partner.already.has.an.active.project.on.this.level= ${fullName} already has an active project on this level.
|
||||
save= Submit idea
|
||||
update= Update idea
|
||||
|
@ -106,7 +106,25 @@ public class ProjectMyIdeasPanel extends Panel {
|
||||
columns.add(new AbstractColumn<>(Model.of("Status"), "match.status") {
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<Idea>> item, String id, IModel<Idea> model) {
|
||||
item.add(new StudentIdeaStatusColumnPanel(id, model));
|
||||
item.add(new Label(id, model.map(idea -> switch (idea.getMatchStatus()) {
|
||||
case UNMATCHED -> "Submitted, waiting for matching by administrator";
|
||||
case MATCHED -> {
|
||||
if (applicationPeriodService.courseStartHasPassed(idea.getApplicationPeriod())) {
|
||||
yield "Matched, project creation delayed. This is under investigation and handled manually. No action needed from you.";
|
||||
} else {
|
||||
yield "Matched, awaiting course start date";
|
||||
}
|
||||
}
|
||||
case COMPLETED -> "Matched, project started";
|
||||
case INACTIVE -> "Inactive";
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
columns.add(new AbstractColumn<>(Model.of("Course start date")) {
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<Idea>> item, String id, IModel<Idea> iModel) {
|
||||
item.add(new Label(id, iModel.map(Idea::getApplicationPeriod).map(ApplicationPeriod::getCourseStartDate)));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -9,6 +9,7 @@ import se.su.dsv.scipro.activityplan.SupervisorActivityPlanPage;
|
||||
import se.su.dsv.scipro.finalseminar.FinalSeminar;
|
||||
import se.su.dsv.scipro.finalseminar.ProjectFinalSeminarDetailsPage;
|
||||
import se.su.dsv.scipro.finalseminar.ProjectFinalSeminarPage;
|
||||
import se.su.dsv.scipro.finalseminar.ProjectOppositionPage;
|
||||
import se.su.dsv.scipro.finalseminar.SupervisorFinalSeminarPage;
|
||||
import se.su.dsv.scipro.forum.pages.ProjectForumBasePage;
|
||||
import se.su.dsv.scipro.forum.pages.SupervisorForumBasePage;
|
||||
@ -170,6 +171,9 @@ public class NotificationLandingPage extends WebPage {
|
||||
case FIRST_MEETING:
|
||||
defaultSplit.accept(ProjectFirstMeetingPage.class, SupervisorFirstMeetingPage.class);
|
||||
break;
|
||||
case OPPOSITION_FAILED:
|
||||
defaultSplit.accept(ProjectOppositionPage.class, SupervisorProjectDetailsPage.class);
|
||||
break;
|
||||
default:
|
||||
// no specific redirect, will default to start page
|
||||
}
|
||||
|
@ -15,6 +15,9 @@
|
||||
<p wicket:id="final_seminar_done_no_final_thesis_done_has_reflection">
|
||||
The final seminar has taken place, your final thesis has been rejected. You need to upload the new version of your final thesis to have your thesis assessed.
|
||||
</p>
|
||||
<p wicket:id="author_done_supervisor_not_done">
|
||||
Final thesis and reflection uploaded.
|
||||
</p>
|
||||
<p wicket:id="all_done">
|
||||
Your thesis project is completed.
|
||||
</p>
|
||||
|
@ -1,7 +1,5 @@
|
||||
package se.su.dsv.scipro.project.panels;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.behavior.Behavior;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.panel.GenericPanel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
@ -23,14 +21,18 @@ public class FinalThesisReflectionInstructionsPanel extends GenericPanel<Project
|
||||
@Inject
|
||||
private FinalSeminarService finalSeminarService;
|
||||
|
||||
private final IModel<Boolean> hasSubmittedReflection;
|
||||
private final IModel<Boolean> hasFinalThesis;
|
||||
private final IModel<Boolean> hasHadFinalSeminar;
|
||||
|
||||
public FinalThesisReflectionInstructionsPanel(String id, IModel<Project> projectModel) {
|
||||
super(id, projectModel);
|
||||
|
||||
IModel<Boolean> hasSubmittedReflection = LoadableDetachableModel.of(() ->
|
||||
hasSubmittedReflection = LoadableDetachableModel.of(() ->
|
||||
reflectionService.getSubmittedReflection(projectModel.getObject(), SciProSession.get().getUser()) != null);
|
||||
IModel<Boolean> hasFinalThesis = LoadableDetachableModel.of(() ->
|
||||
hasFinalThesis = LoadableDetachableModel.of(() ->
|
||||
!finalThesisService.isUploadAllowed(projectModel.getObject()));
|
||||
IModel<Boolean> hasHadFinalSeminar = LoadableDetachableModel.of(() ->
|
||||
hasHadFinalSeminar = LoadableDetachableModel.of(() ->
|
||||
finalSeminarService.hasHadFinalSeminar(projectModel.getObject()));
|
||||
add(new WebMarkupContainer("nothing_done") {
|
||||
@Override
|
||||
@ -60,23 +62,29 @@ public class FinalThesisReflectionInstructionsPanel extends GenericPanel<Project
|
||||
setVisible(hasHadFinalSeminar.getObject() && !hasFinalThesis.getObject() && hasSubmittedReflection.getObject());
|
||||
}
|
||||
});
|
||||
add(new WebMarkupContainer("author_done_supervisor_not_done") {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
boolean projectIsCompleted = projectModel.getObject().getProjectStatus() == ProjectStatus.COMPLETED;
|
||||
setVisible(hasHadFinalSeminar.getObject() && hasFinalThesis.getObject() && hasSubmittedReflection.getObject() && !projectIsCompleted);
|
||||
}
|
||||
});
|
||||
add(new WebMarkupContainer("all_done") {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(hasHadFinalSeminar.getObject() && hasFinalThesis.getObject() && hasSubmittedReflection.getObject());
|
||||
}
|
||||
});
|
||||
add(new Behavior() {
|
||||
@Override
|
||||
public void onConfigure(Component component) {
|
||||
super.onConfigure(component);
|
||||
component.setVisible(
|
||||
!hasHadFinalSeminar.getObject()
|
||||
|| !hasFinalThesis.getObject()
|
||||
|| !hasSubmittedReflection.getObject()
|
||||
|| projectModel.getObject().getProjectStatus() == ProjectStatus.COMPLETED);
|
||||
boolean projectIsCompleted = projectModel.getObject().getProjectStatus() == ProjectStatus.COMPLETED;
|
||||
setVisible(hasHadFinalSeminar.getObject() && hasFinalThesis.getObject() && hasSubmittedReflection.getObject() && projectIsCompleted);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach() {
|
||||
hasFinalThesis.detach();
|
||||
hasSubmittedReflection.detach();
|
||||
hasHadFinalSeminar.detach();
|
||||
super.onDetach();
|
||||
}
|
||||
}
|
||||
|
@ -228,8 +228,8 @@ public class RoughDraftApprovalDecisionPage extends ReviewerPage {
|
||||
for (User author : authors.getObject()) {
|
||||
try {
|
||||
List<Examination> examinations = getPassFailExaminations(author);
|
||||
if (examinations.isEmpty()) {
|
||||
// an empty list is returned if there's an error from the grading service
|
||||
if (examinations == null) {
|
||||
// null is returned if there's an error from the grading service
|
||||
return false;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package se.su.dsv.scipro.supervisor.pages;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.RestartResponseException;
|
||||
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
|
||||
@ -9,8 +8,6 @@ import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightSupervisorMyGroups;
|
||||
import se.su.dsv.scipro.file.FileService;
|
||||
import se.su.dsv.scipro.forum.BasicForumService;
|
||||
import se.su.dsv.scipro.forum.GroupForumService;
|
||||
import se.su.dsv.scipro.forum.dataobjects.GroupThread;
|
||||
import se.su.dsv.scipro.forum.dataobjects.ForumThread;
|
||||
@ -19,16 +16,11 @@ import se.su.dsv.scipro.group.GroupForumThread;
|
||||
import se.su.dsv.scipro.util.PageParameterKeys;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import java.sql.SQLIntegrityConstraintViolationException;
|
||||
|
||||
public class SupervisorViewGroupThreadPage extends AbstractSupervisorGroupPage implements MenuHighlightSupervisorMyGroups {
|
||||
|
||||
@Inject
|
||||
private GroupForumService groupForumService;
|
||||
@Inject
|
||||
private BasicForumService basicForumService;
|
||||
@Inject
|
||||
private FileService fileDescriptionService;
|
||||
|
||||
public SupervisorViewGroupThreadPage(final PageParameters parameters) {
|
||||
super(parameters);
|
||||
@ -48,21 +40,6 @@ public class SupervisorViewGroupThreadPage extends AbstractSupervisorGroupPage i
|
||||
|
||||
add(new FeedbackPanel("feedback"));
|
||||
|
||||
try {
|
||||
basicForumService.setThreadRead(loggedInUser(), groupThread.getForumThread(), true);
|
||||
} catch (RuntimeException e) {
|
||||
Throwable rootCause = Throwables.getRootCause(e);
|
||||
if (rootCause instanceof SQLIntegrityConstraintViolationException) {
|
||||
// One specific user keep getting weird constraint integrity violations.
|
||||
// All attempts at replication have failed.
|
||||
// To get rid of the error we catch it and ignore it. If we failed to
|
||||
// mark a thread as read because it is already read, it does not matter.
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
add(new ViewForumThreadPanel<>("thread", groupThreadModel, new GroupForumThread(groupForumService)) {
|
||||
@Override
|
||||
protected Component newBackLink(final String id) {
|
||||
|
@ -3,7 +3,6 @@ package se.su.dsv.scipro.finalseminar;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.jfree.data.time.Month;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -23,6 +22,7 @@ import se.su.dsv.scipro.test.ObjectMother;
|
||||
import se.su.dsv.scipro.test.UserBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
@ -79,7 +79,7 @@ public class SeminarThesisPanelTest extends SciProTest {
|
||||
@Test
|
||||
public void DeadlineInformationContainsDeadlineDate() {
|
||||
// given
|
||||
ZonedDateTime seminarDate = ZonedDateTime.of(2012, Month.APRIL, 21, 10, 10, 0, 0, ZoneId.systemDefault());
|
||||
ZonedDateTime seminarDate = ZonedDateTime.of(2012, Month.APRIL.getValue(), 21, 10, 10, 0, 0, ZoneId.systemDefault());
|
||||
int daysAhead = 10;
|
||||
|
||||
FinalSeminarSettings settings = new FinalSeminarSettings();
|
||||
|
@ -1,5 +1,6 @@
|
||||
package se.su.dsv.scipro.grading;
|
||||
|
||||
import org.apache.wicket.util.tester.FormTester;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import se.su.dsv.scipro.SciProTest;
|
||||
import se.su.dsv.scipro.file.FileDescription;
|
||||
@ -144,8 +145,9 @@ public class SendToExaminerTest extends SciProTest {
|
||||
.thenReturn(Either.right(null));
|
||||
|
||||
tester.startComponentInPage(new SendToExaminer("id", () -> project, () -> biden, () -> null));
|
||||
FormTester formTester = tester.newFormTester(path("id", "form"));
|
||||
|
||||
tester.clickLink(path("id", "send"));
|
||||
formTester.submit();
|
||||
|
||||
verify(gradingService).reportGrade(TOKEN, project.getIdentifier(), biden.getIdentifier(), gw.id(), grade.name(), finalThesis.getUploadDate());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user