From cbbd98b597cbb4de9bad77c01d83a54a65720dcd Mon Sep 17 00:00:00 2001
From: Andreas Svanberg <andreass@dsv.su.se>
Date: Thu, 6 Feb 2025 14:10:30 +0100
Subject: [PATCH 1/5] Upgrade Wicket version (#102)

Is a drop in replacement according to https://wicket.apache.org/news/2025/01/24/wicket-10.4.0-released.html#upgrading-from-earlier-versions

Fixes #100

Reviewed-on: https://gitea.dsv.su.se/DMC/scipro/pulls/102
Reviewed-by: Nico Athanassiadis <nico@dsv.su.se>
Co-authored-by: Andreas Svanberg <andreass@dsv.su.se>
Co-committed-by: Andreas Svanberg <andreass@dsv.su.se>
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 3e5972cbae..42018f99dc 100755
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
         <!-- Dependency versions -->
         <slf4j.version>2.0.7</slf4j.version>
         <log4j2.version>2.20.0</log4j2.version>
-        <wicket.version>10.1.0</wicket.version>
+        <wicket.version>10.4.0</wicket.version>
 
         <!-- See https://hibernate.org/orm/releases/ for which version Hibernate implements -->
         <jakarta.persistence-api.version>3.1.0</jakarta.persistence-api.version>

From 219c312441eeeba79df4b7295d9fd63542b65cd1 Mon Sep 17 00:00:00 2001
From: Andreas Svanberg <andreass@dsv.su.se>
Date: Fri, 7 Feb 2025 07:50:02 +0100
Subject: [PATCH 2/5] Fix localizer warning on the finishing up tab for each
 author (#101)

Fixes #48

The `getReflectionText` method was calling `getString` when no reflection has been submitted. This is not a relevant case for the editing form since it can not be accessed when there is no reflection. Inlined the method call and removed the non-submitted case, the default will be an empty string.

## How to test
1. Log in as a supervisor
2. Open a project that has a Daisy connection (`identifier`is non-null on the `Project`)
3. Go to the "Finishing up" tab
4. Go to the tab for the author with no reflection submitted
5. See that no warning is logged

Co-authored-by: Nico Athanassiadis <nico@dsv.su.se>
Reviewed-on: https://gitea.dsv.su.se/DMC/scipro/pulls/101
Reviewed-by: Nico Athanassiadis <nico@dsv.su.se>
Co-authored-by: Andreas Svanberg <andreass@dsv.su.se>
Co-committed-by: Andreas Svanberg <andreass@dsv.su.se>
---
 .../su/dsv/scipro/grading/ReflectionModalBodyPanel.java   | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/view/src/main/java/se/su/dsv/scipro/grading/ReflectionModalBodyPanel.java b/view/src/main/java/se/su/dsv/scipro/grading/ReflectionModalBodyPanel.java
index 986d509685..a7e48a1f1c 100644
--- a/view/src/main/java/se/su/dsv/scipro/grading/ReflectionModalBodyPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/grading/ReflectionModalBodyPanel.java
@@ -178,7 +178,13 @@ class ReflectionModalBodyPanel extends Panel {
 
         public SupervisorEditReflectionForm(String id, IModel<Reflection> reflectionModel) {
             super(id, reflectionModel);
-            IModel<String> reflectionTextModel = new Model<>(getReflectionText(reflectionModel.getObject()));
+            IModel<String> reflectionTextModel = new Model<>();
+            Reflection reflection = reflectionModel.getObject();
+            if (reflection instanceof Reflection.Submitted submitted) {
+                reflectionTextModel.setObject(submitted.reflection());
+            } else if (reflection instanceof Reflection.ImprovementsNeeded improvementsNeeded) {
+                reflectionTextModel.setObject(improvementsNeeded.oldReflection());
+            }
 
             TextArea<String> reflectionTextArea = new TextArea<>("reflection", reflectionTextModel);
             reflectionTextArea.setRequired(true);

From 304d0431c14f2b4808716122958c998cdb7dce68 Mon Sep 17 00:00:00 2001
From: Nico Athanassiadis <nico@dsv.su.se>
Date: Wed, 12 Feb 2025 11:07:53 +0100
Subject: [PATCH 3/5] Deadline visible in "Rough draft approval" page (#106)

Previously deadline was only visible at the Reviewer start page, tab 'Rough draft approvals'.

Now the deadline is also shown when you go to the detail page of a rough draft.

Fixes issue #99

Reviewed-on: https://gitea.dsv.su.se/DMC/scipro/pulls/106
Reviewed-by: Andreas Svanberg <andreass@dsv.su.se>
Co-authored-by: Nico Athanassiadis <nico@dsv.su.se>
Co-committed-by: Nico Athanassiadis <nico@dsv.su.se>
---
 compose-branch-deploy.yaml                    | 21 ++++++
 .../se/su/dsv/scipro/DataInitializer.java     | 67 ++++++++++++++++++-
 .../FinalSeminarApprovalProcessPanel.html     |  1 +
 .../FinalSeminarApprovalProcessPanel.java     |  1 +
 .../FinalSeminarApprovalProcessPanelTest.java |  7 ++
 .../resources/application-branch.properties   |  8 +--
 6 files changed, 99 insertions(+), 6 deletions(-)

diff --git a/compose-branch-deploy.yaml b/compose-branch-deploy.yaml
index aba04bbb51..05ba4bd181 100644
--- a/compose-branch-deploy.yaml
+++ b/compose-branch-deploy.yaml
@@ -21,6 +21,8 @@ services:
       - OAUTH2_RESOURCE_SERVER_ID=scipro_api_client
       - OAUTH2_RESOURCE_SERVER_SECRET=scipro_api_secret
       - OAUTH2_RESOURCE_SERVER_INTROSPECTION_URI=https://oauth2-${VHOST}/introspect
+      - OAUTH2_GS_AUTHORIZATION_URI=https://oauth2-gs-${VHOST}
+      - OAUTH2_GS_CLIENT_REDIRECT_URI=https://${VHOST}/oauth/callback
     networks:
       - traefik
       - internal
@@ -64,6 +66,25 @@ services:
       - "traefik.http.routers.oauth2-${COMPOSE_PROJECT_NAME}.rule=Host(`oauth2-${VHOST}`)"
       - "traefik.http.routers.oauth2-${COMPOSE_PROJECT_NAME}.tls.certresolver=letsencrypt"
 
+  oauth2-gs:
+      build:
+        context: https://github.com/dsv-su/toker.git
+        dockerfile: embedded.Dockerfile
+      restart: unless-stopped
+      environment:
+        - CLIENT_ID=scipro_client
+        - CLIENT_SECRET=scipro_secret
+        - CLIENT_REDIRECT_URI=https://${VHOST}/oauth/callback
+        - RESOURCE_SERVER_ID=scipro_api_client
+        - RESOURCE_SERVER_SECRET=scipro_api_secret
+        - CLIENT_SCOPES=grade:read grade:write
+      networks:
+        - traefik
+      labels:
+        - "traefik.enable=true"
+        - "traefik.http.routers.oauth2-gs-${COMPOSE_PROJECT_NAME}.rule=Host(`oauth2-gs-${VHOST}`)"
+        - "traefik.http.routers.oauth2-gs-${COMPOSE_PROJECT_NAME}.tls.certresolver=letsencrypt"
+
 networks:
   traefik:
     name: traefik
diff --git a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java
index 85a1f413a7..380176606b 100644
--- a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java
+++ b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java
@@ -4,11 +4,15 @@ import jakarta.inject.Inject;
 import jakarta.inject.Provider;
 import jakarta.persistence.EntityManager;
 import jakarta.transaction.Transactional;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
 import java.time.LocalDate;
 import java.time.LocalTime;
 import java.time.Month;
 import java.util.*;
+import java.util.function.Function;
 import se.su.dsv.scipro.checklist.ChecklistCategory;
+import se.su.dsv.scipro.file.FileUpload;
 import se.su.dsv.scipro.match.ApplicationPeriod;
 import se.su.dsv.scipro.match.Keyword;
 import se.su.dsv.scipro.milestones.dataobjects.MilestoneActivityTemplate;
@@ -20,6 +24,8 @@ import se.su.dsv.scipro.project.Project;
 import se.su.dsv.scipro.report.AbstractGradingCriterion;
 import se.su.dsv.scipro.report.GradingCriterionPointTemplate;
 import se.su.dsv.scipro.report.GradingReportTemplate;
+import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
+import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.system.*;
 
@@ -45,6 +51,12 @@ public class DataInitializer implements Lifecycle {
     @Inject
     private Provider<EntityManager> em;
 
+    @Inject
+    private RoughDraftApprovalService roughDraftApprovalService;
+
+    @Inject
+    private ReviewerAssignmentService reviewerAssignmentService;
+
     private static final String MAIL = "@example.com";
 
     private static final String ADMIN = "admin";
@@ -75,6 +87,7 @@ public class DataInitializer implements Lifecycle {
     private ResearchArea researchArea2;
     private ProjectType masterClass;
     private ProjectType magisterClass;
+    private Project project2;
 
     @Transactional
     @Override
@@ -89,12 +102,23 @@ public class DataInitializer implements Lifecycle {
             createMilestonesIfNotDone();
             createUsers();
             createProjects();
+            createRoughDraftApproval();
         }
         if (profile.getCurrentProfile() == Profiles.DEV && noAdminUser()) {
             createAdmin();
         }
     }
 
+    private void createRoughDraftApproval() {
+        roughDraftApprovalService.requestApproval(
+            project2,
+            new SimpleTextFile(project2.getHeadSupervisor(), "thesis.txt", "text/plain"),
+            "Please approve"
+        );
+
+        reviewerAssignmentService.assignReviewer(project2, eric_employee);
+    }
+
     @Override
     public void stop() {}
 
@@ -146,10 +170,10 @@ public class DataInitializer implements Lifecycle {
 
     private void createProjects() {
         createProject(PROJECT_1, eric_employee, sture_student, stina_student, eve_employee);
-        createProject(PROJECT_2, eve_employee, sid_student, simon_student, eric_employee);
+        project2 = createProject(PROJECT_2, eve_employee, sid_student, simon_student, eric_employee);
     }
 
-    private void createProject(String title, User headSupervisor, User student1, User student2, User reviewer) {
+    private Project createProject(String title, User headSupervisor, User student1, User student2, User reviewer) {
         Project project = Project.builder()
             .title(title)
             .projectType(bachelorClass)
@@ -160,6 +184,7 @@ public class DataInitializer implements Lifecycle {
         project.addProjectParticipant(student1);
         project.addReviewer(reviewer);
         save(project);
+        return project;
     }
 
     private void createUsers() {
@@ -1907,4 +1932,42 @@ public class DataInitializer implements Lifecycle {
         em.get().persist(entity);
         return entity;
     }
+
+    private static final class SimpleTextFile implements FileUpload {
+
+        private final User uploader;
+        private final String fileName;
+        private final String content;
+
+        private SimpleTextFile(User uploader, String fileName, String content) {
+            this.uploader = uploader;
+            this.fileName = fileName;
+            this.content = content;
+        }
+
+        @Override
+        public String getFileName() {
+            return fileName;
+        }
+
+        @Override
+        public String getContentType() {
+            return "text/plain";
+        }
+
+        @Override
+        public User getUploader() {
+            return uploader;
+        }
+
+        @Override
+        public long getSize() {
+            return content.length();
+        }
+
+        @Override
+        public <T> T handleData(Function<InputStream, T> handler) {
+            return handler.apply(new ByteArrayInputStream(content.getBytes()));
+        }
+    }
 }
diff --git a/view/src/main/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanel.html b/view/src/main/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanel.html
index dfbda44988..b67dc4bcbb 100644
--- a/view/src/main/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanel.html
+++ b/view/src/main/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanel.html
@@ -4,6 +4,7 @@
 <wicket:panel>
     <strong>Current thesis:</strong> <span wicket:id="currentThesis">[thesis.pdf (2014-08-08)]</span><br>
     <strong>Status:</strong> <span wicket:id="currentStatus">[Undecided]</span> <br>
+    <strong>Deadline:</strong> <span wicket:id="deadline">[Undecided]</span> <br>
     <strong>Supervisor comment:</strong> <span wicket:id="currentDecision.comment">[Undecided]</span> <br>
     <wicket:enclosure>
         <strong>Reason:</strong> <span wicket:id="currentReason">[I need more time]</span><br>
diff --git a/view/src/main/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanel.java b/view/src/main/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanel.java
index 00ba26aa74..6781c946d9 100644
--- a/view/src/main/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanel.java
@@ -28,6 +28,7 @@ public class FinalSeminarApprovalProcessPanel extends GenericPanel<ReviewerAppro
             )
         );
         add(new EnumLabel<>("currentStatus", process.map(ReviewerApproval::getCurrentStatus)));
+        add(new DateLabel("deadline", process.map(ReviewerApproval::getCurrentDeadline)));
         add(
             new MultiLineLabel(
                 "currentDecision.comment",
diff --git a/view/src/test/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanelTest.java b/view/src/test/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanelTest.java
index 35269ebf7a..753f2db147 100644
--- a/view/src/test/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanelTest.java
+++ b/view/src/test/java/se/su/dsv/scipro/supervisor/panels/FinalSeminarApprovalProcessPanelTest.java
@@ -10,6 +10,7 @@ import org.apache.wicket.model.LoadableDetachableModel;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import se.su.dsv.scipro.SciProTest;
+import se.su.dsv.scipro.components.DateLabel;
 import se.su.dsv.scipro.file.FileDescription;
 import se.su.dsv.scipro.file.FileReference;
 import se.su.dsv.scipro.project.Project;
@@ -44,6 +45,12 @@ public class FinalSeminarApprovalProcessPanelTest extends SciProTest {
         tester.assertModelValue(path(panel, "currentStatus"), finalSeminarApproval.getCurrentStatus());
     }
 
+    @Test
+    public void shows_deadline() {
+        tester.assertComponent(path(panel, "deadline"), DateLabel.class);
+        tester.assertModelValue(path(panel, "deadline"), finalSeminarApproval.getCurrentDeadline());
+    }
+
     @Test
     public void shows_current_reason_if_a_decision_has_been_made() {
         startPanelWithApprovedFinalSeminar();
diff --git a/war/src/main/resources/application-branch.properties b/war/src/main/resources/application-branch.properties
index 0d6cb9b213..47d660c18a 100644
--- a/war/src/main/resources/application-branch.properties
+++ b/war/src/main/resources/application-branch.properties
@@ -7,10 +7,10 @@ profile=DEV
 # No secrets available for branch deployment to branch.dsv.su.se
 # Will have to set up some mock API for this later
 service.grading.url=
-oauth.uri=
-oauth.clientId=
-oauth.clientSecret=
-oauth.redirectUri=
+oauth.uri=${OAUTH2_GS_AUTHORIZATION_URI:http://localhost:59734/authorize}
+oauth.clientId=${OAUTH2_CLIENT_ID:scipro}
+oauth.clientSecret=${OAUTH2_CLIENT_SECRET:s3cr3t}
+oauth.redirectUri=${OAUTH2_GS_CLIENT_REDIRECT_URI}
 
 # No secrets available for branch deployment to branch.dsv.su.se
 # Will have to set up some mock API for this later

From 08e1b785ca439a27a68eb925470debbdff41a06a Mon Sep 17 00:00:00 2001
From: Andreas Svanberg <andreass@dsv.su.se>
Date: Wed, 12 Feb 2025 13:28:14 +0100
Subject: [PATCH 4/5] Fix Docker build due to missing json-smart version (#107)
 Co-authored-by: Andreas Svanberg <andreass@dsv.su.se> Co-committed-by:
 Andreas Svanberg <andreass@dsv.su.se>

---
 Dockerfile  |  7 -------
 war/pom.xml | 41 -----------------------------------------
 2 files changed, 48 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 9212410a58..ac53fd07f3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,12 +13,6 @@ COPY view/pom.xml view/pom.xml
 COPY war/pom.xml war/pom.xml
 COPY daisy-integration/pom.xml daisy-integration/pom.xml
 
-# Download dependencies in a separate layer to allow caching for future builds
-RUN ./mvnw dependency:go-offline \
-    --batch-mode \
-    --define includeScope=compile  \
-    --activate-profiles docker-dependencies
-
 COPY api/src/ api/src/
 COPY core/src/ core/src/
 COPY view/src/ view/src/
@@ -26,7 +20,6 @@ COPY war/src/ war/src/
 COPY daisy-integration/src/ daisy-integration/src/
 
 RUN ./mvnw package \
-    --offline \
     --define skipTests \
     --activate-profiles branch,DEV \
     --define skip.npm \
diff --git a/war/pom.xml b/war/pom.xml
index 19e7317b23..c1dd889587 100644
--- a/war/pom.xml
+++ b/war/pom.xml
@@ -140,46 +140,5 @@
                 <spring.profile.active>branch</spring.profile.active>
             </properties>
         </profile>
-        <profile>
-            <id>docker-dependencies</id>
-            <!--
-                Some dependencies are not discovered by default when running dependency:go-offline.
-                They are added here manually to allow Docker build layers to be cached properly.
-            -->
-            <dependencies>
-                <dependency>
-                    <groupId>org.jboss.logging</groupId>
-                    <artifactId>jboss-logging</artifactId>
-                </dependency>
-                <dependency>
-                    <groupId>com.fasterxml</groupId>
-                    <artifactId>classmate</artifactId>
-                </dependency>
-                <dependency>
-                    <groupId>net.bytebuddy</groupId>
-                    <artifactId>byte-buddy-agent</artifactId>
-                </dependency>
-                <dependency>
-                    <groupId>com.querydsl</groupId>
-                    <artifactId>querydsl-apt</artifactId>
-                    <version>${querydsl.version}</version>
-                    <classifier>jakarta</classifier>
-                </dependency>
-                <dependency>
-                    <groupId>org.assertj</groupId>
-                    <artifactId>assertj-core</artifactId>
-                    <scope>test</scope>
-                </dependency>
-                <dependency>
-                    <groupId>net.minidev</groupId>
-                    <artifactId>json-smart</artifactId>
-                </dependency>
-                <dependency>
-                    <groupId>org.hamcrest</groupId>
-                    <artifactId>hamcrest-core</artifactId>
-                    <scope>test</scope>
-                </dependency>
-            </dependencies>
-        </profile>
     </profiles>
 </project>

From b9f7dd5a49aeebd64a5f44b66ac0775901550476 Mon Sep 17 00:00:00 2001
From: Andreas Svanberg <andreass@dsv.su.se>
Date: Thu, 13 Feb 2025 09:59:33 +0100
Subject: [PATCH 5/5] Update supervisor's idea table immediately upon
 scheduling a first meeting (#105)

Before, after scheduling a first meeting, they had to refresh the entire page to show the information in the table.

Fixes #82

Reviewed-on: https://gitea.dsv.su.se/DMC/scipro/pulls/105
Reviewed-by: Nico Athanassiadis <nico@dsv.su.se>
---
 .../se/su/dsv/scipro/DataInitializer.java     | 31 +++++++++++++++++--
 .../firstmeeting/FirstMeetingPanel.java       |  6 +++-
 .../scipro/match/SupervisorMyIdeasPanel.java  | 25 +++++++--------
 3 files changed, 44 insertions(+), 18 deletions(-)

diff --git a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java
index 380176606b..7807205f64 100644
--- a/core/src/main/java/se/su/dsv/scipro/DataInitializer.java
+++ b/core/src/main/java/se/su/dsv/scipro/DataInitializer.java
@@ -14,6 +14,8 @@ import java.util.function.Function;
 import se.su.dsv.scipro.checklist.ChecklistCategory;
 import se.su.dsv.scipro.file.FileUpload;
 import se.su.dsv.scipro.match.ApplicationPeriod;
+import se.su.dsv.scipro.match.Idea;
+import se.su.dsv.scipro.match.IdeaService;
 import se.su.dsv.scipro.match.Keyword;
 import se.su.dsv.scipro.milestones.dataobjects.MilestoneActivityTemplate;
 import se.su.dsv.scipro.milestones.dataobjects.MilestonePhaseTemplate;
@@ -28,6 +30,7 @@ import se.su.dsv.scipro.reviewing.ReviewerAssignmentService;
 import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
 import se.su.dsv.scipro.security.auth.roles.Roles;
 import se.su.dsv.scipro.system.*;
+import se.su.dsv.scipro.util.Pair;
 
 public class DataInitializer implements Lifecycle {
 
@@ -42,6 +45,9 @@ public class DataInitializer implements Lifecycle {
     @Inject
     private PasswordService passwordService;
 
+    @Inject
+    private IdeaService ideaService;
+
     @Inject
     private MilestoneActivityTemplateService milestoneActivityTemplateService;
 
@@ -83,6 +89,8 @@ public class DataInitializer implements Lifecycle {
     private Set<ResearchArea> researchAreas;
     private Long researchAreaId = RESEARCH_AREA_ID;
     private Set<Language> languages;
+    private ApplicationPeriod applicationPeriod;
+    private Keyword keyword1;
     private ResearchArea researchArea1;
     private ResearchArea researchArea2;
     private ProjectType masterClass;
@@ -101,6 +109,7 @@ public class DataInitializer implements Lifecycle {
             createKeywordsIfNotDone();
             createMilestonesIfNotDone();
             createUsers();
+            createMatchedIdea();
             createProjects();
             createRoughDraftApproval();
         }
@@ -127,18 +136,18 @@ public class DataInitializer implements Lifecycle {
     }
 
     private void createApplicationPeriodIfNotDone() {
-        ApplicationPeriod applicationPeriod = new ApplicationPeriod("HT 2014");
+        applicationPeriod = new ApplicationPeriod("HT 2014");
         applicationPeriod.setStartDate(LocalDate.now().minusDays(APPLICATION_PERIOD_START_MINUS_DAYS));
         applicationPeriod.setEndDate(LocalDate.now().plusDays(APPLICATION_PERIOD_END_PLUS_DAYS));
         applicationPeriod.setCourseStartDate(LocalDate.now().plusDays(APPLICATION_PERIOD_COURSE_START_PLUS_DAYS));
         applicationPeriod.setCourseStartTime(LocalTime.of(8, 0));
         applicationPeriod = save(applicationPeriod);
-        applicationPeriod.setProjectTypes(new HashSet<>(Collections.singletonList(bachelorClass)));
+        applicationPeriod.setProjectTypes(new HashSet<>(Set.of(bachelorClass, masterClass)));
         save(applicationPeriod);
     }
 
     private void createKeywordsIfNotDone() {
-        Keyword keyword1 = new Keyword("IT");
+        keyword1 = new Keyword("IT");
         keyword1.addResearchArea(researchArea1);
         keyword1.addResearchArea(researchArea2);
         save(keyword1);
@@ -273,6 +282,22 @@ public class DataInitializer implements Lifecycle {
         return u;
     }
 
+    private void createMatchedIdea() {
+        Idea idea = new Idea();
+        idea.setApplicationPeriod(applicationPeriod);
+        idea.setType(Idea.Type.SUPERVISOR);
+        idea.setProjectType(masterClass);
+        idea.setTitle("Idea without first meeting");
+        idea.setDescription("Explore the deep sea");
+        idea.setPrerequisites("Diving experience");
+        idea.setResearchArea(researchArea1);
+        idea.setPublished(true);
+        Idea saved = ideaService.saveSupervisorIdea(idea, eve_employee, new ArrayList<>(Set.of(keyword1)), true);
+        Pair<Boolean, String> validated = ideaService.validateAdminAddAuthors(saved, Set.of(sid_student));
+        assert validated.getHead();
+        ideaService.setAuthors(saved, Set.of(sid_student), eve_employee);
+    }
+
     private void createGradingCriterionTemplateIfNotDone() {
         save(getBachelorTemplate());
         save(getMasterTemplate());
diff --git a/view/src/main/java/se/su/dsv/scipro/firstmeeting/FirstMeetingPanel.java b/view/src/main/java/se/su/dsv/scipro/firstmeeting/FirstMeetingPanel.java
index c7b62f64cd..3158294d42 100644
--- a/view/src/main/java/se/su/dsv/scipro/firstmeeting/FirstMeetingPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/firstmeeting/FirstMeetingPanel.java
@@ -169,7 +169,11 @@ public class FirstMeetingPanel extends GenericPanel<Idea> {
         }
 
         private void saveAndNotify() {
-            firstMeetingRepository.save(getModelObject());
+            FirstMeeting saved = firstMeetingRepository.save(getModelObject());
+            // After saving the first meeting we have to populate it on the already loaded idea to
+            // make sure that other places that want to render the first meeting get the correct data.
+            // An alternative would be to detach the idea model to force a database refresh.
+            FirstMeetingPanel.this.getModelObject().setFirstMeeting(saved);
             NotificationSource source = new NotificationSource();
             String date = dateService.format(getModelObject().getFirstMeetingDate(), DateStyle.DATETIME);
             String room = getModelObject().getRoom();
diff --git a/view/src/main/java/se/su/dsv/scipro/match/SupervisorMyIdeasPanel.java b/view/src/main/java/se/su/dsv/scipro/match/SupervisorMyIdeasPanel.java
index cd59b023bf..f59bd4ac49 100755
--- a/view/src/main/java/se/su/dsv/scipro/match/SupervisorMyIdeasPanel.java
+++ b/view/src/main/java/se/su/dsv/scipro/match/SupervisorMyIdeasPanel.java
@@ -337,29 +337,26 @@ public class SupervisorMyIdeasPanel extends Panel {
                 if (idea.getMatch() == null) {
                     return "-";
                 }
-                switch (idea.getMatchStatus()) {
-                    case UNMATCHED:
-                        return getString("status.unmatched");
-                    case COMPLETED:
-                        return getString("status.completed");
-                    case INACTIVE:
-                        return getString("status.inactive");
-                    case MATCHED:
+                return switch (idea.getMatchStatus()) {
+                    case UNMATCHED -> getString("status.unmatched");
+                    case COMPLETED -> getString("status.completed");
+                    case INACTIVE -> getString("status.inactive");
+                    case MATCHED -> {
                         if (applicationPeriodService.courseStartHasPassed(idea.getApplicationPeriod())) {
                             if (idea.isExported()) {
                                 if (idea.wasExportSuccessful()) {
-                                    return getString("status.project.created");
+                                    yield getString("status.project.created");
                                 } else {
-                                    return getString("status.export.failed");
+                                    yield getString("status.export.failed");
                                 }
                             } else {
-                                return getString("status.awaiting.project.creation");
+                                yield getString("status.awaiting.project.creation");
                             }
                         } else {
-                            return getString("status.awaiting.course.start", Model.of(idea.getApplicationPeriod()));
+                            yield getString("status.awaiting.course.start", Model.of(idea.getApplicationPeriod()));
                         }
-                }
-                return "-"; // can't happen
+                    }
+                };
             }
         };
     }