diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b1f4811132f46eb429eea5c4e4f1387629c284d4..08f9c0dcbe414bd245fb2ec5fd5b691f72da04f8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -34,9 +34,6 @@ variables:
   DOCKER_DRIVER: overlay2
   DOCKER_TLS_CERTDIR: "/certs"
 
-  SAST_DISABLE_DIND: "false"
-  SAST_DEFAULT_ANALYZERS: "spotbugs"
-
   # Configure mysql environment variables
   MYSQL_DATABASE: "$DB_NAME"
   MYSQL_USER: "$DB_NAME"
@@ -116,7 +113,9 @@ gradle_test:
       - codecov/
     reports:
       junit: build/test-results/test/TEST-*.xml
-      cobertura: build/reports/jacoco/test/jacocoTestReport.xml
+      coverage_report:
+        coverage_format: cobertura
+        path: build/reports/jacoco/test/jacocoTestReport.xml
   before_script:
     - mv src/test/resources/application-h2.properties src/test/resources/application-test.properties
   script:
@@ -406,23 +405,6 @@ spotbugs-sast:
   dependencies:
     - gradle_build
 
-eslint-sast:
-  allow_failure: true
-  variables:
-    SAST_EXCLUDED_PATHS: tmp, build, target, out, .gradle, gradle, docs, codecov
-  rules:
-    - if: $CI_PIPELINE_SOURCE == "trigger" ||
-        $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"
-      when: never
-    - if: $CI_COMMIT_BRANCH == "master" ||
-        $CI_COMMIT_BRANCH == "development" ||
-        $CI_MERGE_REQUEST_ID
-  stage: gitlab reports
-  needs:
-    - gradle_build
-  dependencies:
-    - gradle_build
-
 # Run the DAST security checks and reporter.
 # Currently set to manual as it requires a test environment to be up and running.
 dast:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6a837d317ce16a4e5835f5963e0dfb85234ceca..d4e0e7cb134823105cd2ee205b420f6474ec33ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 ### Added
+
+### Changed
+
+### Fixed
+
+### Deprecated
+
+### Removed
+
+## [2.1.0]
+### Added
 - Redirect users to enrol page when they are not correctly enrolled for a lab. [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
 - A Student Group gets created automatically when a student attempts to enqueue without one. [@lemaire](https://gitlab.ewi.tudelft.nl/Lemaire)
 - Allow managers and up to pick any request [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
@@ -30,12 +41,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Add indicator for active filters [@cedricwillekens](https://gitlab.ewi.tudelft.nl/cedricwilleken)
 - Allow users to view and create new shared editions. [@cedricwillekens](https://gitlab.ewi.tudelft.nl/cedricwilleken)
 - Add room images for student requests [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
+- Hybrid Labs are now supported regardless of what the direction of the lab is. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
+- Added buttons to edit sessions for a shared lab [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
 
 ### Changed
 - Redirect students to their request when their are being processed when accessing the lab page. [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
 - Provide a clearer error message to users when username cannot be found [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
 - Also allow for constraints checking to be done on individual students. [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
 - Finished course editions are now primarily ordered by the end date (DESC) and secondarily ordered by the role of the user. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
+- Lab export now includes TA comments. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx)
 
 ### Fixed
 - In exam lab, fix all requests getting rejected [@rbackx](https://gitlab.ewi.tudelft.nl/rbackx)
@@ -46,6 +60,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fix language of calendar [@cchen9](https://gitlab.ewi.tudelft.nl/cchen9)
 - Prevent error on returning to catalog or enrol page after enrolling in a course edition [@cchen9](https://gitlab.ewi.tudelft.nl/cchen9)
 - Form submission buttons on requests are disabled after they are pressed, preventing 2 requests to be sent. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
+- Feedback is now deleted when the associated request is deleted [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
+- Assistants can no longer see requests belonging to other editions in a shared session [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
 
 ## [1.2.0] - [2020-06-06]
 ### Added
@@ -97,6 +113,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Get Next button will only show up if the lab is open. [@tnulle](https://gitlab.ewi.tudelft.nl/tnulle)
 - Improve performance of counting the number of requests current in the queue for a specific assistant and specific lab by [@lemaire](https://gitlab.ewi.tudelft.nl/lemaire)
 - Fix wrong submission link by remove email suffix [@tnulle](https://gitlab.ewi.tudelft.nl/tnulle)
+- Requests for shared sessions are now only sent to the appropriate assistants. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
 
 ## [1.1.0] - (2019-12-16)
 ### Deprecated
diff --git a/build.gradle.kts b/build.gradle.kts
index 4c479e9ecad7ab912cb27f79bb2d47c60427ae4c..07a19081be111bc28d63f187d14d7855ae814bf4 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,12 +4,12 @@ import com.diffplug.gradle.spotless.SpotlessExtension
 import org.springframework.boot.gradle.tasks.run.BootRun
 
 group = "nl.tudelft.ewi.queue"
-version = "2.0.0"
+version = "2.1.0"
 
 val javaVersion = JavaVersion.VERSION_17
 
 val libradorVersion = "1.0.3-SNAPSHOT6"
-val labradoorVersion = "1.3.1-SNAPSHOT"
+val labradoorVersion = "1.3.5"
 val queryDslVersion = "4.4.0"
 
 // A definition of all dependencies and repositories where to find them that need to
@@ -55,7 +55,7 @@ plugins {
 
 	// Spring plugins for managing dependencies and creating
 	// a nice Spring Boot application.
-	id("org.springframework.boot").version("2.5.12")
+	id("org.springframework.boot").version("2.5.14")
 	id("io.spring.dependency-management").version("1.0.11.RELEASE")
 
 	// Plugin to provide task to check the current versions of
diff --git a/src/main/java/nl/tudelft/queue/QueueMvcConfig.java b/src/main/java/nl/tudelft/queue/QueueMvcConfig.java
index 24804baf7581ab502c1d87e5cd1d7ce02ec5d462..6c7587bb0ca5bfb9173ff29a93919ef912c62e20 100644
--- a/src/main/java/nl/tudelft/queue/QueueMvcConfig.java
+++ b/src/main/java/nl/tudelft/queue/QueueMvcConfig.java
@@ -42,7 +42,7 @@ public class QueueMvcConfig implements WebMvcConfigurer {
 		registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/");
 		registry.addResourceHandler("/img/**").addResourceLocations("classpath:/static/img/");
 		registry.addResourceHandler("/webjars/**").addResourceLocations("/webjars/").resourceChain(false);
-		registry.addResourceHandler("/**").addResourceLocations("/static/");
+		registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
 
 	}
 }
diff --git a/src/main/java/nl/tudelft/queue/controller/AdminController.java b/src/main/java/nl/tudelft/queue/controller/AdminController.java
index 01dd038f8f30deea5ebb04f26b15696fa2c338a8..ae26344f00e4d0ad1cc30838894d790963027463 100644
--- a/src/main/java/nl/tudelft/queue/controller/AdminController.java
+++ b/src/main/java/nl/tudelft/queue/controller/AdminController.java
@@ -19,7 +19,10 @@ package nl.tudelft.queue.controller;
 
 import java.io.IOException;
 import java.time.LocalDateTime;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 import javax.validation.Valid;
 
@@ -30,13 +33,16 @@ import nl.tudelft.labracore.api.dto.*;
 import nl.tudelft.labracore.api.dto.CourseCreateDTO;
 import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
 import nl.tudelft.labracore.lib.security.user.Person;
+import nl.tudelft.librador.dto.view.View;
 import nl.tudelft.queue.cache.EditionCacheManager;
 import nl.tudelft.queue.cache.RoomCacheManager;
 import nl.tudelft.queue.cache.SessionCacheManager;
 import nl.tudelft.queue.dto.create.QueueCourseCreateDTO;
+import nl.tudelft.queue.dto.view.QueueSessionSummaryDTO;
 import nl.tudelft.queue.model.labs.Lab;
 import nl.tudelft.queue.repository.LabRepository;
 import nl.tudelft.queue.service.AdminService;
+import nl.tudelft.queue.service.EditionService;
 import nl.tudelft.queue.service.LabService;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -66,9 +72,10 @@ public class AdminController {
 
 	@Autowired
 	private EditionCacheManager eCache;
-
 	@Autowired
-	private LabRepository labRepository;
+	private EditionService es;
+	@Autowired
+	private LabRepository lr;
 
 	@Autowired
 	@Lazy
@@ -112,20 +119,47 @@ public class AdminController {
 	 */
 	@GetMapping("/calendar")
 	public String getAdminCoursesPage(Model model) {
-		Period currPeriod = new Period().start(LocalDateTime.now().minusDays(1))
+		Period currPeriod = new Period().start(LocalDateTime.now().minusWeeks(1))
 				.end(LocalDateTime.now().plusMonths(1));
 		List<Lab> labs = ls.getAllLabsWithinPeriod(currPeriod);
 		model.addAttribute("sessions", ls.convertToCalendarEntries(labs));
-		long count;
-		if ((count = ls.countOngoingLabs(labs)) > 0) {
-			model.addAttribute("labsOngoing", String.format("Currently %d labs " +
-					"ongoing!", count));
+		return "admin/view/calendar";
+	}
+
+	@GetMapping("/running")
+	public String getRunningLabs(Model model) {
+		LocalDateTime now = LocalDateTime.now();
+		var runningEditions = eCache.get(Objects.requireNonNull(
+				eApi.getAllEditionsActiveAtDate(LocalDateTime.now())
+						.map(EditionSummaryDTO::getId).collectList().block()));
+
+		var runningSessions = sCache.get(runningEditions.stream()
+				.flatMap(e -> e.getSessions().stream())
+				.filter(s -> s.getEnd().isAfter(now))
+				.map(SessionSummaryDTO::getId));
+		var runningLabs = es.sortLabs(lr
+				.findAllBySessions(runningSessions.stream().map(SessionDetailsDTO::getId)
+						.collect(Collectors.toList()))
+				.stream().map(l -> View.convert(l, QueueSessionSummaryDTO.class))
+				.filter(qs -> qs.getSlot().today())
+				.sorted(Comparator.comparing(qs -> qs.getSlot().getClosesAt()))
+				.collect(Collectors.toList()));
+		model.addAttribute("runningLabs", runningLabs);
+
+		Period today = new Period().start(LocalDateTime.now().minusDays(1))
+				.end(LocalDateTime.now().plusDays(1));
+		List<Lab> labs = ls.getAllLabsWithinPeriod(today);
+		long labCount = ls.countOngoingLabs(labs);
+		if (labCount > 0) {
+			model.addAttribute("labsOngoing", String.format("Currently %d lab(s) " +
+					"ongoing", labCount));
 		}
-		if ((count = ls.countLabsWithOpenSlotSelection(labs)) > 0) {
-			model.addAttribute("slotSelectionOpen", String.format("Currently %d labs with slot " +
-					"selection open", count));
+		long slotCount = ls.countLabsWithOpenSlotSelection(labs);
+		if (slotCount > 0) {
+			model.addAttribute("slotSelectionOpen", String.format("Currently %d lab(s) with slot " +
+					"selection open", slotCount));
 		}
-		return "admin/view/calendar";
+		return "admin/view/running";
 	}
 
 	/**
diff --git a/src/main/java/nl/tudelft/queue/controller/EditionController.java b/src/main/java/nl/tudelft/queue/controller/EditionController.java
index 27e431c762304cc02c38c0867fb4707c6accdb63..215c8011024668c2498bbdc6b98961949562cc12 100644
--- a/src/main/java/nl/tudelft/queue/controller/EditionController.java
+++ b/src/main/java/nl/tudelft/queue/controller/EditionController.java
@@ -18,12 +18,16 @@
 package nl.tudelft.queue.controller;
 
 import static java.time.LocalDateTime.now;
+import static nl.tudelft.labracore.api.dto.PersonDetailsDTO.DefaultRoleEnum.ADMIN;
+import static nl.tudelft.labracore.api.dto.PersonDetailsDTO.DefaultRoleEnum.TEACHER;
 import static nl.tudelft.labracore.lib.LabracoreApiUtil.fromPageable;
 import static nl.tudelft.queue.PageUtil.toPage;
 
 import java.io.IOException;
+import java.time.LocalDateTime;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -153,6 +157,9 @@ public class EditionController {
 	public String getEditionList(@AuthenticatedPerson Person person,
 			@PageableDefault(sort = "id", direction = Sort.Direction.DESC) Pageable pageable,
 			Model model) {
+
+		PersonDetailsDTO pd = Objects.requireNonNull(pApi.getPersonById(person.getId()).block());
+
 		var filter = es.getFilter("/editions");
 		var editions = eApi
 				.getEditionsPageActiveOrTaughtBy(person.getId(), fromPageable(pageable), filter.getPrograms(),
@@ -171,6 +178,17 @@ public class EditionController {
 
 		model.addAttribute("page", "catalog");
 
+		if (pd.getDefaultRole() == ADMIN || pd.getDefaultRole() == TEACHER) {
+			model.addAttribute("allEditions", eCache.get(
+					Objects.requireNonNullElse(
+							eApi.getAllEditionsActiveDuringPeriod(
+									new Period().start(LocalDateTime.now())
+											.end(LocalDateTime.now().plusYears(1)))
+									.map(EditionSummaryDTO::getId)
+									.collectList().block(),
+							List.of())));
+		}
+
 		return "edition/index";
 	}
 
diff --git a/src/main/java/nl/tudelft/queue/controller/HomeController.java b/src/main/java/nl/tudelft/queue/controller/HomeController.java
index 207c71a074e565b4b9bf4715d175a7f6427057d3..75fbbea2289192d5699cd94b7fc159a8cb09a386 100644
--- a/src/main/java/nl/tudelft/queue/controller/HomeController.java
+++ b/src/main/java/nl/tudelft/queue/controller/HomeController.java
@@ -17,8 +17,6 @@
  */
 package nl.tudelft.queue.controller;
 
-import static nl.tudelft.labracore.api.dto.PersonDetailsDTO.DefaultRoleEnum.ADMIN;
-import static nl.tudelft.labracore.api.dto.PersonDetailsDTO.DefaultRoleEnum.TEACHER;
 import static nl.tudelft.labracore.api.dto.RoleEditionDetailsDTO.TypeEnum.TEACHER_RO;
 
 import java.time.LocalDateTime;
@@ -199,36 +197,6 @@ public class HomeController {
 							.filter(qs -> ps.canEnqueueSelf(qs.getId()) || qs.getSlot().open())
 							.collect(Collectors.toList())));
 
-			if (pd.getDefaultRole() == ADMIN) {
-				var runningEditions = eCache.get(Objects.requireNonNull(
-						eApi.getAllEditionsActiveAtDate(LocalDateTime.now())
-								.map(EditionSummaryDTO::getId).collectList().block()));
-
-				var runningSessions = sCache.get(runningEditions.stream()
-						.flatMap(e -> e.getSessions().stream())
-						.filter(s -> s.getEnd().isAfter(now))
-						.map(SessionSummaryDTO::getId));
-				var runningLabs = es.sortLabs(lr
-						.findAllBySessions(runningSessions.stream().map(SessionDetailsDTO::getId)
-								.collect(Collectors.toList()))
-						.stream().map(l -> View.convert(l, QueueSessionSummaryDTO.class))
-						.filter(qs -> qs.getSlot().today())
-						.sorted(Comparator.comparing(qs -> qs.getSlot().getClosesAt()))
-						.collect(Collectors.toList()));
-				model.addAttribute("runningLabs", runningLabs);
-			}
-
-			if (pd.getDefaultRole() == ADMIN || pd.getDefaultRole() == TEACHER) {
-				model.addAttribute("allEditions", eCache.get(
-						Objects.requireNonNullElse(
-								eApi.getAllEditionsActiveDuringPeriod(
-										new Period().start(LocalDateTime.now())
-												.end(LocalDateTime.now().plusYears(1)))
-										.map(EditionSummaryDTO::getId)
-										.collectList().block(),
-								List.of())));
-			}
-
 			model.addAttribute("editions", editions);
 			model.addAttribute("sharedEditions", sharedEditions);
 			model.addAttribute("sharedLabs", sharedLabs);
diff --git a/src/main/java/nl/tudelft/queue/controller/LabController.java b/src/main/java/nl/tudelft/queue/controller/LabController.java
index a8dc526c63fb64c96f46fbea4df8132b99271f4b..083a3e3257c1bfc254b82ec8041b7064a1b08b43 100644
--- a/src/main/java/nl/tudelft/queue/controller/LabController.java
+++ b/src/main/java/nl/tudelft/queue/controller/LabController.java
@@ -528,7 +528,9 @@ public class LabController {
 		ls.deleteSession(qSession);
 		var session = sCache.getOrThrow(qSession.getSession());
 
-		// TODO: Redirect to an edition collection specific page for shared labs
+		if (session.getEditionCollection() != null) {
+			return "redirect:/shared-edition/" + session.getEditionCollection().getId();
+		}
 		return "redirect:/edition/" + session.getEditions().get(0).getId() + "/labs";
 	}
 
diff --git a/src/main/java/nl/tudelft/queue/controller/RequestController.java b/src/main/java/nl/tudelft/queue/controller/RequestController.java
index 38e8139c8a436f192232db7189b145762b854f45..61489cb9530c2054563d63d8e65a9259355a29e1 100644
--- a/src/main/java/nl/tudelft/queue/controller/RequestController.java
+++ b/src/main/java/nl/tudelft/queue/controller/RequestController.java
@@ -46,6 +46,7 @@ import nl.tudelft.queue.repository.LabRequestRepository;
 import nl.tudelft.queue.service.*;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.domain.Sort;
 import org.springframework.data.web.PageableDefault;
@@ -144,10 +145,16 @@ public class RequestController {
 				.map(qs -> (Lab) qs)
 				.collect(Collectors.toList());
 
+		List<LabRequest> filteredRequests = rs
+				.filterRequestsSharedEditionCheck(lrr.findAllByFilter(labs, filter, pageable).getContent(),
+						assistant);
+
+		var requestsViews = rts.convertRequestsToView(
+				new PageImpl<>(filteredRequests, pageable, filteredRequests.size()), filteredRequests.size());
+
 		model.addAttribute("page", "requests");
 		model.addAttribute("filter", filter);
-		model.addAttribute("requests", rts.convertRequestsToView(
-				lrr.findAllByFilter(labs, filter, pageable), lrr.countByFilter(labs, filter)));
+		model.addAttribute("requests", requestsViews);
 		model.addAttribute("requestCounts", rts.labRequestCounts(
 				labs, assistant, filter));
 
diff --git a/src/main/java/nl/tudelft/queue/dto/create/QueueSessionCreateDTO.java b/src/main/java/nl/tudelft/queue/dto/create/QueueSessionCreateDTO.java
index 2ac52e1a5efd9ef78d0c80b4ddbeaaa5af6beaef..480c00ca34b92c714e550127577d66deb9bad5b2 100644
--- a/src/main/java/nl/tudelft/queue/dto/create/QueueSessionCreateDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/create/QueueSessionCreateDTO.java
@@ -44,7 +44,7 @@ public abstract class QueueSessionCreateDTO<D extends QueueSession<?>> extends C
 	private Set<Long> modules = new HashSet<>();
 
 	@Builder.Default
-	private Set<Long> rooms = new HashSet<>();
+	protected Set<Long> rooms = new HashSet<>();
 
 	@Builder.Default
 	private LabRequestConstraintsCreateDTO constraints = new LabRequestConstraintsCreateDTO();
@@ -74,7 +74,6 @@ public abstract class QueueSessionCreateDTO<D extends QueueSession<?>> extends C
 
 		nonEmpty("modules", modules);
 
-		nonEmpty("rooms", rooms);
 	}
 
 	@Override
diff --git a/src/main/java/nl/tudelft/queue/dto/create/labs/CapacitySessionCreateDTO.java b/src/main/java/nl/tudelft/queue/dto/create/labs/CapacitySessionCreateDTO.java
index 87d9c921e7785034fa9f3bc2c454f194501eed9e..62e7df05b95b0224cab86147a31e98bf2997ec89 100644
--- a/src/main/java/nl/tudelft/queue/dto/create/labs/CapacitySessionCreateDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/create/labs/CapacitySessionCreateDTO.java
@@ -55,6 +55,8 @@ public class CapacitySessionCreateDTO extends QueueSessionCreateDTO<CapacitySess
 	public void validate() {
 		super.validate();
 
+		nonEmpty("rooms", rooms);
+
 		if (capacitySessionConfig.getEnrolmentClosesAt()
 				.isBefore(capacitySessionConfig.getEnrolmentOpensAt())) {
 			errors.rejectValue("capacitySessionConfig.enrolmentClosesAt",
diff --git a/src/main/java/nl/tudelft/queue/dto/create/labs/LabCreateDTO.java b/src/main/java/nl/tudelft/queue/dto/create/labs/LabCreateDTO.java
index 1845586e73a64f611464be03d3439474d0cecb26..8ddd9ea4af5bea4e65a2d09390ca3d84bac73ff4 100644
--- a/src/main/java/nl/tudelft/queue/dto/create/labs/LabCreateDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/create/labs/LabCreateDTO.java
@@ -18,6 +18,7 @@
 package nl.tudelft.queue.dto.create.labs;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -33,9 +34,12 @@ import nl.tudelft.labracore.api.dto.SessionDetailsDTO;
 import nl.tudelft.queue.dto.create.QueueSessionCreateDTO;
 import nl.tudelft.queue.model.embeddables.AllowedRequest;
 import nl.tudelft.queue.model.enums.CommunicationMethod;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestType;
 import nl.tudelft.queue.model.labs.Lab;
 
+import org.springframework.util.CollectionUtils;
+
 @Data
 @SuperBuilder
 @NoArgsConstructor
@@ -51,6 +55,9 @@ public abstract class LabCreateDTO<D extends Lab> extends QueueSessionCreateDTO<
 	@Builder.Default
 	private Map<Long, Set<RequestType>> requestTypes = new HashMap<>();
 
+	@Builder.Default
+	private Set<OnlineMode> onlineModes = new HashSet<>();
+
 	@Builder.Default
 	private Boolean enableExperimental = false;
 
@@ -65,6 +72,8 @@ public abstract class LabCreateDTO<D extends Lab> extends QueueSessionCreateDTO<
 				.collect(Collectors.groupingBy(AllowedRequest::getAssignment,
 						Collectors.mapping(AllowedRequest::getType, Collectors.toSet())));
 
+		this.onlineModes = new HashSet<>(lab.getOnlineModes());
+
 		this.enableExperimental = lab.getEnableExperimental();
 	}
 
@@ -77,6 +86,12 @@ public abstract class LabCreateDTO<D extends Lab> extends QueueSessionCreateDTO<
 		nonEmpty("requestTypes", requestTypes);
 
 		nonNull("enableExperimental", enableExperimental);
+
+		if (CollectionUtils.isEmpty(rooms) && CollectionUtils.isEmpty(onlineModes)) {
+			errors.rejectValue("rooms", "Select at least 1 room or online mode");
+			errors.rejectValue("onlineModes", "Select at least 1 room or online mode");
+		}
+
 	}
 
 	@Override
diff --git a/src/main/java/nl/tudelft/queue/dto/create/requests/LabRequestCreateDTO.java b/src/main/java/nl/tudelft/queue/dto/create/requests/LabRequestCreateDTO.java
index f09d6ff1c1d6e92e09b66be4ded7cf3d84eafd16..8b535e49adc7ccb51a75fe5bbc6194b54752fb59 100644
--- a/src/main/java/nl/tudelft/queue/dto/create/requests/LabRequestCreateDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/create/requests/LabRequestCreateDTO.java
@@ -35,6 +35,7 @@ import nl.tudelft.queue.dto.create.RequestCreateDTO;
 import nl.tudelft.queue.dto.id.TimeSlotIdDTO;
 import nl.tudelft.queue.model.LabRequest;
 import nl.tudelft.queue.model.embeddables.AllowedRequest;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestType;
 import nl.tudelft.queue.model.labs.Lab;
 import nl.tudelft.queue.model.labs.SlottedLab;
@@ -60,9 +61,10 @@ public class LabRequestCreateDTO extends RequestCreateDTO<LabRequest, Lab> {
 	@NotNull
 	private Long assignment;
 
-	@NotNull
 	private Long room;
 
+	private OnlineMode onlineMode;
+
 	private transient Lab session;
 
 	@Override
@@ -73,8 +75,13 @@ public class LabRequestCreateDTO extends RequestCreateDTO<LabRequest, Lab> {
 			nonEmpty("question", question);
 		}
 
+		if ((room == null) == (onlineMode == null)) {
+			errors.rejectValue("room", "Room or online mode not selected");
+			errors.rejectValue("onlineMode", "Room or online mode not selected");
+		}
+
 		var session = getBean(SessionCacheManager.class).getOrThrow(this.getSession().getSession());
-		if (session.getRooms().stream().noneMatch(r -> Objects.equals(r.getId(), room))) {
+		if (room != null && session.getRooms().stream().noneMatch(r -> Objects.equals(r.getId(), room))) {
 			errors.rejectValue("room", "A room with id " + room + " is not available in the lab.");
 		}
 
diff --git a/src/main/java/nl/tudelft/queue/dto/patch/LabPatchDTO.java b/src/main/java/nl/tudelft/queue/dto/patch/LabPatchDTO.java
index cf70084da0f345957cee494442ae386e700a635b..abcb220d9ea369ebd5dda648b7bc5ec759546db3 100644
--- a/src/main/java/nl/tudelft/queue/dto/patch/LabPatchDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/patch/LabPatchDTO.java
@@ -17,9 +17,7 @@
  */
 package nl.tudelft.queue.dto.patch;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import javax.validation.constraints.Max;
@@ -28,7 +26,9 @@ import javax.validation.constraints.Min;
 import lombok.*;
 import lombok.experimental.SuperBuilder;
 import nl.tudelft.labracore.api.dto.AssignmentIdDTO;
+import nl.tudelft.labracore.api.dto.RoomIdDTO;
 import nl.tudelft.queue.model.enums.CommunicationMethod;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestType;
 import nl.tudelft.queue.model.labs.Lab;
 
@@ -47,12 +47,19 @@ public abstract class LabPatchDTO<D extends Lab> extends QueueSessionPatchDTO<D>
 	@Builder.Default
 	private Map<Long, Set<RequestType>> requestTypes = null;
 
+	@Builder.Default
+	private Set<OnlineMode> onlineModes = null;
+
 	private Boolean enableExperimental;
 
 	@Override
 	protected void applyOneToOne() {
 		updateNonNull(communicationMethod, data::setCommunicationMethod);
 		updateNonNull(eolGracePeriod, data::setEolGracePeriod);
+		if (onlineModes == null && rooms != null) {
+			onlineModes = new HashSet<>();
+		}
+		updateNonNull(onlineModes, data::setOnlineModes);
 		data.setEnableExperimental(Boolean.TRUE.equals(enableExperimental)); // null is false as well
 	}
 
@@ -65,7 +72,18 @@ public abstract class LabPatchDTO<D extends Lab> extends QueueSessionPatchDTO<D>
 
 	@Override
 	protected void validate() {
+		super.validate();
+
 		nonEmpty("requestTypes", requestTypes);
+
+		// A null value does not update labracore or queue, therefore we set it to an empty list.
+		// No verification needed to check
+		if (rooms == null && onlineModes != null) {
+			rooms = new HashSet<>();
+		} else if (onlineModes == null && rooms != null) {
+			onlineModes = new HashSet<>();
+		}
+
 	}
 
 	@Override
@@ -80,6 +98,24 @@ public abstract class LabPatchDTO<D extends Lab> extends QueueSessionPatchDTO<D>
 				.collect(Collectors.toList());
 	}
 
+	/**
+	 * Overriden method, since if it returns null, it usually signifies "no change" in labracore. From now it
+	 * will signify that this is a sesion with online modes only.
+	 *
+	 * @return A list of roomIdDTOs
+	 */
+	@Override
+	public List<RoomIdDTO> roomIdDTOs() {
+		if (rooms == null) {
+			if (onlineModes != null) {
+				return new ArrayList<>();
+			}
+			return null;
+		}
+
+		return rooms.stream().map(al -> new RoomIdDTO().id(al)).collect(Collectors.toList());
+	}
+
 	private void nonEmpty(String field, Map<?, ?> set) {
 		if (set != null && set.isEmpty()) {
 			errors.rejectValue(field, "Field should not be empty");
diff --git a/src/main/java/nl/tudelft/queue/dto/patch/QueueSessionPatchDTO.java b/src/main/java/nl/tudelft/queue/dto/patch/QueueSessionPatchDTO.java
index 631d5945cbec87bf6f7b240b8d7f895082dc5c39..72d862c244b4755ececd69e2a1ce9f85294c781c 100644
--- a/src/main/java/nl/tudelft/queue/dto/patch/QueueSessionPatchDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/patch/QueueSessionPatchDTO.java
@@ -44,7 +44,7 @@ public abstract class QueueSessionPatchDTO<D extends QueueSession<?>> extends Pa
 	private Set<Long> modules = null;
 
 	@Builder.Default
-	private Set<Long> rooms = null;
+	protected Set<Long> rooms = null;
 
 	@Builder.Default
 	private LabRequestConstraintsPatchDTO constraints = new LabRequestConstraintsPatchDTO();
@@ -64,7 +64,6 @@ public abstract class QueueSessionPatchDTO<D extends QueueSession<?>> extends Pa
 	@Override
 	protected void validate() {
 		nonEmpty("modules", modules);
-		nonEmpty("rooms", rooms);
 	}
 
 	/**
@@ -83,7 +82,7 @@ public abstract class QueueSessionPatchDTO<D extends QueueSession<?>> extends Pa
 		return rooms.stream().map(al -> new RoomIdDTO().id(al)).collect(Collectors.toList());
 	}
 
-	private void nonEmpty(String field, Collection<?> set) {
+	protected void nonEmpty(String field, Collection<?> set) {
 		if (set != null && set.isEmpty()) {
 			errors.rejectValue(field, "Field should not be empty");
 		}
diff --git a/src/main/java/nl/tudelft/queue/dto/patch/RequestPatchDTO.java b/src/main/java/nl/tudelft/queue/dto/patch/RequestPatchDTO.java
index 25294984fcfc48b73e22c4faf92e534074aad135..c6f18b3bf902a343f78c1dbfc1c4c47534cc2d54 100644
--- a/src/main/java/nl/tudelft/queue/dto/patch/RequestPatchDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/patch/RequestPatchDTO.java
@@ -48,7 +48,7 @@ public class RequestPatchDTO extends Patch<LabRequest> {
 	protected void validate() {
 		var session = SpringContext.getBean(SessionCacheManager.class)
 				.getOrThrow(data.getSession().getSession());
-		if (session.getRooms().stream().noneMatch(r -> Objects.equals(r.getId(), room))) {
+		if (room != null && session.getRooms().stream().noneMatch(r -> Objects.equals(r.getId(), room))) {
 			errors.rejectValue("room", "Room is not found in the surrounding lab");
 		}
 
diff --git a/src/main/java/nl/tudelft/queue/dto/patch/labs/CapacitySessionPatchDTO.java b/src/main/java/nl/tudelft/queue/dto/patch/labs/CapacitySessionPatchDTO.java
index cae2edb250867e4c5fcedd103abc74fa348f2776..4d8b8ec28c5ed51d6b5d2a5a6a6e097120c54245 100644
--- a/src/main/java/nl/tudelft/queue/dto/patch/labs/CapacitySessionPatchDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/patch/labs/CapacitySessionPatchDTO.java
@@ -40,6 +40,12 @@ public class CapacitySessionPatchDTO extends QueueSessionPatchDTO<CapacitySessio
 	@Builder.Default
 	private CapacitySessionConfigPatchDTO capacitySessionConfig = new CapacitySessionConfigPatchDTO();
 
+	@Override
+	public void validate() {
+		super.validate();
+		nonEmpty("rooms", rooms);
+	}
+
 	@Override
 	protected void postApply() {
 		super.postApply();
diff --git a/src/main/java/nl/tudelft/queue/dto/util/RequestTableFilterDTO.java b/src/main/java/nl/tudelft/queue/dto/util/RequestTableFilterDTO.java
index 86ef532e4ccc81cfb7ba17eec0c75d60af72fb23..77fc3722a3ed8be2424757f9d6da5f46c76d6847 100644
--- a/src/main/java/nl/tudelft/queue/dto/util/RequestTableFilterDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/util/RequestTableFilterDTO.java
@@ -29,6 +29,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 import nl.tudelft.librador.dto.Validated;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestStatus;
 import nl.tudelft.queue.model.enums.RequestType;
 
@@ -43,6 +44,9 @@ public class RequestTableFilterDTO extends Validated implements Serializable {
 	private Set<Long> assignments = new HashSet<>();
 	@NotNull
 	private Set<Long> rooms = new HashSet<>();
+
+	@NotNull
+	private Set<OnlineMode> onlineModes = new HashSet<>();
 	@NotNull
 	private Set<Long> assigned = new HashSet<>();
 	@NotNull
@@ -60,7 +64,7 @@ public class RequestTableFilterDTO extends Validated implements Serializable {
 	 * @return The number of lists which contain at least 1 element to filter on.
 	 */
 	public long countActiveFilters() {
-		return Stream.of(labs, assigned, rooms, assigned, assignments, requestStatuses,
+		return Stream.of(labs, assigned, rooms, onlineModes, assigned, assignments, requestStatuses,
 				requestTypes).filter(s -> !s.isEmpty()).count();
 	}
 
@@ -70,7 +74,7 @@ public class RequestTableFilterDTO extends Validated implements Serializable {
 	 * @return A stream of Sets.
 	 */
 	private Stream<Set<?>> getAllFiltersAsStream() {
-		return Stream.of(labs, assigned, rooms, assigned, assignments, requestStatuses,
+		return Stream.of(labs, assigned, rooms, onlineModes, assigned, assignments, requestStatuses,
 				requestTypes);
 	}
 
diff --git a/src/main/java/nl/tudelft/queue/dto/view/LabViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/LabViewDTO.java
index e7799ea679224a2de128a21f86d69bf3e439156a..60ea91065092e043bf36c000ae2dcadf5af01d9d 100644
--- a/src/main/java/nl/tudelft/queue/dto/view/LabViewDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/view/LabViewDTO.java
@@ -22,6 +22,7 @@ import java.util.Set;
 import lombok.*;
 import nl.tudelft.queue.model.embeddables.AllowedRequest;
 import nl.tudelft.queue.model.enums.CommunicationMethod;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.labs.Lab;
 
 @Data
@@ -34,4 +35,7 @@ public abstract class LabViewDTO<D extends Lab> extends QueueSessionViewDTO<D> {
 	private Set<AllowedRequest> allowedRequests;
 
 	private Boolean enableExperimental;
+
+	private Set<OnlineMode> onlineModes;
+
 }
diff --git a/src/main/java/nl/tudelft/queue/dto/view/QueueSessionSummaryDTO.java b/src/main/java/nl/tudelft/queue/dto/view/QueueSessionSummaryDTO.java
index 16cad7b0f5aab595ee730635a90cb6af3fd6d672..fa7fa48c3cf78d5cec835f680a4f55aa1602a19b 100644
--- a/src/main/java/nl/tudelft/queue/dto/view/QueueSessionSummaryDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/view/QueueSessionSummaryDTO.java
@@ -21,12 +21,16 @@ import static java.time.LocalDateTime.now;
 import static nl.tudelft.librador.SpringContext.getBean;
 
 import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Objects;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
+import nl.tudelft.labracore.api.dto.EditionSummaryDTO;
 import nl.tudelft.librador.dto.view.View;
+import nl.tudelft.queue.cache.EditionCollectionCacheManager;
 import nl.tudelft.queue.cache.SessionCacheManager;
 import nl.tudelft.queue.misc.QueueSessionStatus;
 import nl.tudelft.queue.model.QueueSession;
@@ -46,6 +50,8 @@ public class QueueSessionSummaryDTO extends View<QueueSession<?>> {
 
 	private QueueSessionType type;
 
+	private List<EditionSummaryDTO> associatedEditions;
+
 	private String name;
 	private String readableName;
 	private Slot slot;
@@ -84,6 +90,14 @@ public class QueueSessionSummaryDTO extends View<QueueSession<?>> {
 					.isBefore(now());
 		}
 		isShared = session.getEditionCollection() != null;
+		if (isShared) {
+			var editionCollection = getBean(EditionCollectionCacheManager.class)
+					.getOrThrow(session.getEditionCollection().getId());
+			associatedEditions = editionCollection.getEditions();
+		} else {
+			associatedEditions = List.of(Objects.requireNonNull(session.getEdition()));
+		}
+
 	}
 
 	/**
diff --git a/src/main/java/nl/tudelft/queue/dto/view/events/StudentNotFoundEventViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/events/StudentNotFoundEventViewDTO.java
index cae29aee2f293b9fa4f7164c5c8f3fa44a6123bc..935d4197bf49a005c6ea4b98cb4255be510e046a 100644
--- a/src/main/java/nl/tudelft/queue/dto/view/events/StudentNotFoundEventViewDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/view/events/StudentNotFoundEventViewDTO.java
@@ -39,11 +39,12 @@ public class StudentNotFoundEventViewDTO extends RequestEventViewDTO<StudentNotF
 	 */
 	@Override
 	public String getDescription() {
+		if (request.getOnlineMode() != null) {
+			return "Student did not connect on time";
+		}
 		switch (getRequest().getSession().getCommunicationMethod()) {
-			case JITSI_MEET:
-				return "Student did not connect on time";
 			case STUDENT_VISIT_TA:
-				return "Student did not show";
+				return "Student did not show up";
 			case TA_VISIT_STUDENT:
 				return "Could not find student to handle the request";
 			default:
diff --git a/src/main/java/nl/tudelft/queue/dto/view/requests/LabRequestViewDTO.java b/src/main/java/nl/tudelft/queue/dto/view/requests/LabRequestViewDTO.java
index 67072fb3a5e71543b59b758ed1dbdc8f73aaab2b..a861a969294985f9d9277ad73eba64ab17916740 100644
--- a/src/main/java/nl/tudelft/queue/dto/view/requests/LabRequestViewDTO.java
+++ b/src/main/java/nl/tudelft/queue/dto/view/requests/LabRequestViewDTO.java
@@ -31,9 +31,11 @@ import nl.tudelft.queue.cache.AssignmentCacheManager;
 import nl.tudelft.queue.cache.EditionCacheManager;
 import nl.tudelft.queue.cache.ModuleCacheManager;
 import nl.tudelft.queue.dto.view.RequestViewDTO;
+import nl.tudelft.queue.dto.view.events.RequestHandledEventViewDTO;
 import nl.tudelft.queue.model.Feedback;
 import nl.tudelft.queue.model.LabRequest;
 import nl.tudelft.queue.model.TimeSlot;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestType;
 
 import org.apache.commons.lang3.ArrayUtils;
@@ -50,6 +52,7 @@ public class LabRequestViewDTO extends RequestViewDTO<LabRequest> {
 
 	private String jitsiRoom;
 	private TimeSlot timeSlot;
+	private OnlineMode onlineMode;
 
 	private RequestType requestType;
 
@@ -57,10 +60,18 @@ public class LabRequestViewDTO extends RequestViewDTO<LabRequest> {
 
 	private List<Feedback> feedbacks;
 
+	private String commentForStudent;
+
+	private String commentForAssistant;
+
 	@Override
 	public void postApply() {
 		super.postApply();
 
+		if (onlineMode != null) {
+			setRoom(null);
+		}
+
 		assignment = getBean(AssignmentCacheManager.class).getOrThrow(data.getAssignment());
 		timeSlot = data.getTimeSlot();
 
@@ -69,6 +80,17 @@ public class LabRequestViewDTO extends RequestViewDTO<LabRequest> {
 					.getEdition().getId();
 			super.setEdition(getBean(EditionCacheManager.class).getOrThrow(editionId));
 		}
+
+		var handledEvent = getEvents().stream()
+				.filter(e -> e instanceof RequestHandledEventViewDTO<?>)
+				.findFirst()
+				.map(e -> (RequestHandledEventViewDTO<?>) e);
+		if (handledEvent.isPresent()) {
+			commentForStudent = handledEvent.get().getReasonForStudent();
+			commentForAssistant = handledEvent.get().getReasonForAssistant();
+		} else {
+			commentForStudent = commentForAssistant = "";
+		}
 	}
 
 	@Override
@@ -93,8 +115,11 @@ public class LabRequestViewDTO extends RequestViewDTO<LabRequest> {
 				"Comment",
 				"Question",
 				"Time Slot",
+				"Online Mode",
 				"Request Type",
-				"Assignment");
+				"Assignment",
+				"Reason for Student",
+				"Reason for Assistant");
 	}
 
 	@Override
@@ -103,7 +128,10 @@ public class LabRequestViewDTO extends RequestViewDTO<LabRequest> {
 				(comment != null) ? comment : "",
 				(question != null) ? question : "",
 				(timeSlot != null) ? timeSlot.toString() : "",
+				(onlineMode != null) ? onlineMode.getDisplayName() : "",
 				requestType.displayName(),
-				assignment.getName());
+				assignment.getName(),
+				commentForStudent,
+				commentForAssistant);
 	}
 }
diff --git a/src/main/java/nl/tudelft/queue/model/Feedback.java b/src/main/java/nl/tudelft/queue/model/Feedback.java
index bce2fc2516520e68d2a94b22bb938bf669e90109..670767ed565f6d0218da0dadeabaed3a9d90f27f 100644
--- a/src/main/java/nl/tudelft/queue/model/Feedback.java
+++ b/src/main/java/nl/tudelft/queue/model/Feedback.java
@@ -32,6 +32,9 @@ import lombok.Data;
 import lombok.NoArgsConstructor;
 import nl.tudelft.queue.model.labs.Lab;
 
+import org.hibernate.annotations.SQLDelete;
+import org.hibernate.annotations.Where;
+
 /**
  * Entity class representing feedback given to a TA about one request they contributed to handling. Feedback
  * may be given multiple times per request, but can only given once per TA per request. In other words, one
@@ -42,6 +45,8 @@ import nl.tudelft.queue.model.labs.Lab;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
+@Where(clause = "deleted_at IS NULL")
+@SQLDelete(sql = "UPDATE feedback SET deleted_at = NOW() WHERE request_id = ? AND assistant_id = ?")
 public class Feedback {
 	/**
 	 * The embeddable id of a Feedback object.
@@ -108,4 +113,7 @@ public class Feedback {
 	@NotNull
 	@Builder.Default
 	private LocalDateTime lastUpdatedAt = LocalDateTime.now();
+
+	@Builder.Default
+	private LocalDateTime deletedAt = null;
 }
diff --git a/src/main/java/nl/tudelft/queue/model/LabRequest.java b/src/main/java/nl/tudelft/queue/model/LabRequest.java
index d520298e9d53e5cbca493c465305c34306dfea9f..16631a57c4818899992fd58fcb63772d38a67a96 100644
--- a/src/main/java/nl/tudelft/queue/model/LabRequest.java
+++ b/src/main/java/nl/tudelft/queue/model/LabRequest.java
@@ -33,10 +33,13 @@ import lombok.*;
 import lombok.experimental.SuperBuilder;
 import nl.tudelft.librador.dto.view.View;
 import nl.tudelft.queue.dto.view.requests.LabRequestViewDTO;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestType;
 import nl.tudelft.queue.model.labs.ExamLab;
 import nl.tudelft.queue.model.labs.Lab;
 
+import org.hibernate.annotations.Cascade;
+
 /**
  * A request that has been made by a StudentGroup, that the database representation of someone asking for
  * help. Part of this class stores it's data directly in the Request table. The lifecycle of a Request, that
@@ -72,6 +75,11 @@ public class LabRequest extends Request<Lab> {
 	 */
 	private String jitsiRoom;
 
+	/**
+	 * The OnlineMode this request will take place in (Jitsi, MS teams, etc)
+	 */
+	private OnlineMode onlineMode;
+
 	/**
 	 * The timeslot this request occupies. Timeslots can be used as opposed to direct requests to allow for
 	 * reserving a TA for a specific time ahead of the lab starting.
@@ -94,6 +102,7 @@ public class LabRequest extends Request<Lab> {
 	@ToString.Exclude
 	@EqualsAndHashCode.Exclude
 	@OneToMany(mappedBy = "request")
+	@Cascade(org.hibernate.annotations.CascadeType.DELETE)
 	private List<Feedback> feedbacks = new ArrayList<>();
 
 	/**
diff --git a/src/main/java/nl/tudelft/queue/model/LabRequestConstraint.java b/src/main/java/nl/tudelft/queue/model/LabRequestConstraint.java
index 5754b107f1b36c4c5f61532fd499068842d4cd6c..03b7dbafc7389c9b9118f40a2c7d45d4b4a03c74 100644
--- a/src/main/java/nl/tudelft/queue/model/LabRequestConstraint.java
+++ b/src/main/java/nl/tudelft/queue/model/LabRequestConstraint.java
@@ -18,6 +18,7 @@
 package nl.tudelft.queue.model;
 
 import javax.persistence.*;
+import javax.validation.constraints.NotNull;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -94,5 +95,5 @@ public abstract class LabRequestConstraint {
 	 *
 	 * @param session The lab to set.
 	 */
-	public abstract void setSession(QueueSession<?> session);
+	public abstract void setSession(@NotNull QueueSession<?> session);
 }
diff --git a/src/main/java/nl/tudelft/queue/model/enums/CommunicationMethod.java b/src/main/java/nl/tudelft/queue/model/enums/CommunicationMethod.java
index a96e9575c76533d491eae97ad1d71fff16eab5b7..5a808afa535004eb91e37d8e06dc066be7c9af45 100644
--- a/src/main/java/nl/tudelft/queue/model/enums/CommunicationMethod.java
+++ b/src/main/java/nl/tudelft/queue/model/enums/CommunicationMethod.java
@@ -31,17 +31,8 @@ public enum CommunicationMethod {
 	),
 	STUDENT_VISIT_TA(
 		"Student visits TA"
-	),
-	JITSI_MEET(
-		"Jitsi Meeting"
 	);
 
 	private final String displayName;
 
-	/**
-	 * @return Whether the communication method is an online method.
-	 */
-	public boolean isOnline() {
-		return JITSI_MEET == this;
-	}
 }
diff --git a/src/main/java/nl/tudelft/queue/model/enums/OnlineMode.java b/src/main/java/nl/tudelft/queue/model/enums/OnlineMode.java
new file mode 100644
index 0000000000000000000000000000000000000000..318871b2e271d7383d1415170c21267d8ec32595
--- /dev/null
+++ b/src/main/java/nl/tudelft/queue/model/enums/OnlineMode.java
@@ -0,0 +1,48 @@
+/*
+ * Queue - A Queueing system that can be used to handle labs in higher education
+ * Copyright (C) 2016-2021  Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.queue.model.enums;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum OnlineMode {
+
+	JITSI(
+		"Jitsi"
+	);
+
+	private final String displayName;
+
+	/**
+	 * Maps the online mode(s) to the display name of the respective mode(s) and concatenates them. Used
+	 * mainly for displaying the available online modes to end users.
+	 *
+	 * @param  onlineModes A set of online modes
+	 * @return             A concatenated string with all the display names
+	 */
+	public static String displayNames(Set<OnlineMode> onlineModes) {
+		return onlineModes.stream().map(OnlineMode::getDisplayName)
+				.sorted(String::compareToIgnoreCase)
+				.collect(Collectors.joining(", "));
+	}
+}
diff --git a/src/main/java/nl/tudelft/queue/model/labs/Lab.java b/src/main/java/nl/tudelft/queue/model/labs/Lab.java
index 5d88cf2213aa1e7ad40bef1dc2c8ecdbe372c9bf..0ced510627ef11c7247a541816426bf0c1df6f48 100644
--- a/src/main/java/nl/tudelft/queue/model/labs/Lab.java
+++ b/src/main/java/nl/tudelft/queue/model/labs/Lab.java
@@ -41,6 +41,7 @@ import nl.tudelft.queue.model.QueueSession;
 import nl.tudelft.queue.model.Request;
 import nl.tudelft.queue.model.embeddables.AllowedRequest;
 import nl.tudelft.queue.model.enums.CommunicationMethod;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestType;
 
 import org.hibernate.validator.constraints.UniqueElements;
@@ -54,8 +55,7 @@ import org.hibernate.validator.constraints.UniqueElements;
 public abstract class Lab extends QueueSession<LabRequest> {
 
 	/**
-	 * The communication method that will be used for the lab. This could range from physical contact to Jitsi
-	 * meetings.
+	 * The communication method that will be used for the lab. Online meetings disregard this.
 	 */
 	@NotNull
 	@Enumerated(EnumType.STRING)
@@ -80,6 +80,15 @@ public abstract class Lab extends QueueSession<LabRequest> {
 	@ElementCollection
 	private Set<AllowedRequest> allowedRequests = new HashSet<>();
 
+	/**
+	 * The online options available for the lab. Currently only Jitsi
+	 */
+	@NotNull
+	@UniqueElements
+	@Builder.Default
+	@ElementCollection
+	private Set<OnlineMode> onlineModes = new HashSet<>();
+
 	@NotNull
 	@Builder.Default
 	private Boolean enableExperimental = false;
@@ -129,8 +138,9 @@ public abstract class Lab extends QueueSession<LabRequest> {
 
 		return request instanceof LabRequest && super.allowsRequest(request)
 				&& allowsRequest((LabRequest) request)
-				&& request.getRoom() != null && session.getRooms().stream()
-						.anyMatch(room -> Objects.equals(room.getId(), request.getRoom()));
+				&& (request.getRoom() == null) ? ((LabRequest) request).getOnlineMode() != null
+						: session.getRooms().stream()
+								.anyMatch(room -> Objects.equals(room.getId(), request.getRoom()));
 	}
 
 	/**
diff --git a/src/main/java/nl/tudelft/queue/realtime/messages/RequestCreatedMessage.java b/src/main/java/nl/tudelft/queue/realtime/messages/RequestCreatedMessage.java
index c0736c9bf9881553c33a3d161a0725368b889f4b..3ef20d8449cb8ccddd0fe2b5fa8bb20b3e829d8e 100644
--- a/src/main/java/nl/tudelft/queue/realtime/messages/RequestCreatedMessage.java
+++ b/src/main/java/nl/tudelft/queue/realtime/messages/RequestCreatedMessage.java
@@ -24,6 +24,7 @@ import nl.tudelft.librador.dto.view.View;
 import nl.tudelft.queue.cache.ModuleCacheManager;
 import nl.tudelft.queue.dto.view.requests.LabRequestViewDTO;
 import nl.tudelft.queue.model.LabRequest;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestStatus;
 import nl.tudelft.queue.model.enums.RequestType;
 
@@ -50,6 +51,10 @@ public class RequestCreatedMessage extends View<LabRequest> implements Message {
 	private Long buildingId;
 	private String buildingName;
 
+	private OnlineMode onlineMode;
+
+	private String onlineModeDisplayName;
+
 	private Long assignmentId;
 	private String assignmentName;
 	private Long moduleId;
@@ -84,10 +89,15 @@ public class RequestCreatedMessage extends View<LabRequest> implements Message {
 
 		requestedBy = view.requesterEntityName();
 
-		roomId = view.getRoom().getId();
-		roomName = view.getRoom().getName();
-		buildingId = view.getRoom().getBuilding().getId();
-		buildingName = view.getRoom().getBuilding().getName();
+		if (view.getRoom() != null) {
+			roomId = view.getRoom().getId();
+			roomName = view.getRoom().getName();
+			buildingId = view.getRoom().getBuilding().getId();
+			buildingName = view.getRoom().getBuilding().getName();
+		} else if (view.getOnlineMode() != null) {
+			onlineMode = view.getOnlineMode();
+			onlineModeDisplayName = view.getOnlineMode().getDisplayName();
+		}
 
 		assignmentId = view.getAssignment().getId();
 		assignmentName = view.getAssignment().getName();
diff --git a/src/main/java/nl/tudelft/queue/repository/FeedbackRepository.java b/src/main/java/nl/tudelft/queue/repository/FeedbackRepository.java
index dc91e820bb5f00804a18f34a810c8cc4cbb5bab0..9fb6e629554baa23120710c0a791c163f38677ed 100644
--- a/src/main/java/nl/tudelft/queue/repository/FeedbackRepository.java
+++ b/src/main/java/nl/tudelft/queue/repository/FeedbackRepository.java
@@ -88,11 +88,12 @@ public interface FeedbackRepository
 	 */
 	default Page<Feedback> findByAssistantAnonymised(Long assistantId, Pageable pageable) {
 		var feedback = StreamSupport.stream(findAll(qf.id.assistantId.eq(assistantId)
-				.and(qf.feedback.isNotNull()).and(qf.feedback.isNotEmpty())
-				.and(qf.createdAt.before(LocalDateTime.now().minusMonths(1))), qf.createdAt.desc())
+				.and(qf.createdAt.before(LocalDateTime.now().minusWeeks(2))), qf.createdAt.desc())
 						.spliterator(),
 				false)
-				.skip(10).collect(Collectors.toList());
+				.skip(5)
+				.filter(f -> f.getFeedback() != null && !f.getFeedback().isEmpty())
+				.collect(Collectors.toList());
 		var page = feedback.subList(pageable.getPageNumber() * pageable.getPageSize(),
 				Math.min(feedback.size(), (pageable.getPageNumber() + 1) * pageable.getPageSize()));
 		return new PageImpl<>(page, pageable, feedback.size());
diff --git a/src/main/java/nl/tudelft/queue/repository/LabRequestRepository.java b/src/main/java/nl/tudelft/queue/repository/LabRequestRepository.java
index 658c90981b4ea0f34eae60521f6ce2acf0853746..35b9e3943acdfd3f90cad1e1d8d482c2fa38ea52 100644
--- a/src/main/java/nl/tudelft/queue/repository/LabRequestRepository.java
+++ b/src/main/java/nl/tudelft/queue/repository/LabRequestRepository.java
@@ -35,10 +35,7 @@ import nl.tudelft.queue.model.enums.RequestStatus;
 import nl.tudelft.queue.model.labs.AbstractSlottedLab;
 import nl.tudelft.queue.model.labs.Lab;
 
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.*;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.querydsl.QuerydslPredicateExecutor;
 import org.springframework.lang.NonNull;
@@ -364,6 +361,7 @@ public interface LabRequestRepository
 		e = and(e, filter.getAssigned(), qlr.eventInfo.assignedTo::in);
 		e = and(e, filter.getLabs(), qlr.session.id::in);
 		e = and(e, filter.getRooms(), qlr.room::in);
+		e = and(e, filter.getOnlineModes(), qlr.onlineMode::in);
 		e = and(e, filter.getAssignments(), qlr.assignment::in);
 		e = and(e, filter.getRequestStatuses(), qlr.eventInfo.status::in);
 		e = and(e, filter.getRequestTypes(), qlr.requestType::in);
diff --git a/src/main/java/nl/tudelft/queue/service/EditionService.java b/src/main/java/nl/tudelft/queue/service/EditionService.java
index 3e00f24752d6179158fd4e53116e2a660e85a6f9..0fe80542f26985df1d7315e11d052588b54acbc9 100644
--- a/src/main/java/nl/tudelft/queue/service/EditionService.java
+++ b/src/main/java/nl/tudelft/queue/service/EditionService.java
@@ -23,6 +23,7 @@ import static java.util.stream.Collectors.groupingBy;
 
 import java.io.IOException;
 import java.util.*;
+import java.util.List;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
diff --git a/src/main/java/nl/tudelft/queue/service/LabService.java b/src/main/java/nl/tudelft/queue/service/LabService.java
index a9d5d601d2906c5a4e5157650f353a5c8170b613..efae7c7c1481bba5508bea30e44919361186524a 100644
--- a/src/main/java/nl/tudelft/queue/service/LabService.java
+++ b/src/main/java/nl/tudelft/queue/service/LabService.java
@@ -22,10 +22,7 @@ import static nl.tudelft.queue.misc.QueueSessionStatus.*;
 
 import java.io.IOException;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import javax.transaction.Transactional;
@@ -50,6 +47,7 @@ import nl.tudelft.queue.misc.QueueSessionStatus;
 import nl.tudelft.queue.model.QueueSession;
 import nl.tudelft.queue.model.TimeSlot;
 import nl.tudelft.queue.model.embeddables.AllowedRequest;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestType;
 import nl.tudelft.queue.model.enums.SelectionProcedure;
 import nl.tudelft.queue.model.labs.AbstractSlottedLab;
@@ -414,7 +412,7 @@ public class LabService {
 	 * Get all the labs which are taking place within a certain period.
 	 *
 	 * @param  period The period for which to retrieve all the labs.
-	 * @return        A list of labs which are all occuring within the specified period.
+	 * @return        A list of labs which are all occurring within the specified period.
 	 */
 	public List<Lab> getAllLabsWithinPeriod(Period period) {
 		var editions = eCache.get(Objects.requireNonNull(eApi.getAllEditionsActiveDuringPeriod(period)
@@ -434,11 +432,9 @@ public class LabService {
 	 */
 	public long countOngoingLabs(List<Lab> labs) {
 		LocalDateTime now = LocalDateTime.now();
-		return labs.stream().filter(l -> {
-			SessionDetailsDTO session = sCache.getOrThrow(l.getSession());
-			return session.getStart().isBefore(now) &&
-					session.getEnd().isAfter(now);
-		}).count();
+		List<SessionDetailsDTO> sessions = sCache.get(labs.stream().map(QueueSession::getSession));
+		return sessions.stream().filter(s -> s.getStart().isBefore(now) &&
+				s.getEnd().isAfter(now)).count();
 	}
 
 	/**
@@ -455,7 +451,7 @@ public class LabService {
 				case EXAM:
 					AbstractSlottedLab<TimeSlot> lab = (AbstractSlottedLab<TimeSlot>) l;
 					return lab.getSlottedLabConfig().getSelectionOpensAt().isBefore(now)
-							|| sCache.getOrThrow(lab.getSession()).getStart().isAfter(now);
+							&& sCache.getOrThrow(lab.getSession()).getEnd().isAfter(now);
 				default:
 					return false;
 			}
@@ -486,4 +482,15 @@ public class LabService {
 		return allowedAssignments;
 	}
 
+	/**
+	 * Gets the online modes in a lab session.
+	 *
+	 * @param  labSessionId The ID of the lab session
+	 * @return              The online modes associated to the lab.
+	 */
+	public Set<OnlineMode> getOnlineModesInLabSession(long labSessionId) {
+		var labs = lr.findAllBySessions(List.of(labSessionId));
+		return labs.stream().map(Lab::getOnlineModes).flatMap(Set::stream).collect(Collectors.toSet());
+	}
+
 }
diff --git a/src/main/java/nl/tudelft/queue/service/PermissionService.java b/src/main/java/nl/tudelft/queue/service/PermissionService.java
index 6067bd32d6d119d9442d5b82e14ed751284061b0..85c2a68a6f1b6537c165a7b959a1481814a0c32e 100644
--- a/src/main/java/nl/tudelft/queue/service/PermissionService.java
+++ b/src/main/java/nl/tudelft/queue/service/PermissionService.java
@@ -330,7 +330,7 @@ public class PermissionService {
 	 * @return            Whether the currently authenticated person has a staff role in any of the given
 	 *                    course editions.
 	 */
-	private boolean hasStaffRole(List<Long> editionIds) {
+	public boolean hasStaffRole(List<Long> editionIds) {
 		return withAnyRole(editionIds, (person, role) -> STAFF_ROLES.contains(role));
 	}
 
@@ -400,6 +400,16 @@ public class PermissionService {
 		return isAdmin() || withRole(editionId, (person, role) -> role != BLOCKED);
 	}
 
+	/**
+	 * Determine if a user has manage perms for a edition/shared edition.
+	 *
+	 * @param  editionList The editions to check for
+	 * @return             true iff the person is allowed to modify said editions, false otherwise
+	 */
+	public boolean canManageInAnyEdition(List<EditionSummaryDTO> editionList) {
+		return editionList.stream().anyMatch(edition -> canManageSessions(edition.getId()));
+	}
+
 	/**
 	 * @param  edition The edition the authenticated user wants to see.
 	 * @return         Whether the authenticated user can see the edition.
diff --git a/src/main/java/nl/tudelft/queue/service/RequestService.java b/src/main/java/nl/tudelft/queue/service/RequestService.java
index f73c23c6f6dffad50ec882b6bfb59c41d185720e..b71df2242b7c2952ac932b879d248ebb38efa65f 100644
--- a/src/main/java/nl/tudelft/queue/service/RequestService.java
+++ b/src/main/java/nl/tudelft/queue/service/RequestService.java
@@ -24,6 +24,7 @@ import java.util.stream.Collectors;
 
 import javax.transaction.Transactional;
 
+import nl.tudelft.labracore.api.AssignmentControllerApi;
 import nl.tudelft.labracore.api.ModuleControllerApi;
 import nl.tudelft.labracore.api.QuestionControllerApi;
 import nl.tudelft.labracore.api.StudentGroupControllerApi;
@@ -36,6 +37,7 @@ import nl.tudelft.queue.model.ClosableTimeSlot;
 import nl.tudelft.queue.model.LabRequest;
 import nl.tudelft.queue.model.Request;
 import nl.tudelft.queue.model.SelectionRequest;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.SelectionProcedure;
 import nl.tudelft.queue.model.events.*;
 import nl.tudelft.queue.model.labs.AbstractSlottedLab;
@@ -79,6 +81,12 @@ public class RequestService {
 	@Autowired
 	private QuestionControllerApi qApi;
 
+	@Autowired
+	private AssignmentControllerApi asApi;
+
+	@Autowired
+	private RoleDTOService roleDTOService;
+
 	@Autowired
 	private PersonCacheManager pCache;
 
@@ -98,6 +106,9 @@ public class RequestService {
 	@Lazy
 	private LabService lService;
 
+	@Autowired
+	private PermissionService permissionService;
+
 	private static final ReentrantLock lock = new ReentrantLock();
 
 	/**
@@ -123,7 +134,12 @@ public class RequestService {
 
 		if (request instanceof LabRequest) {
 			var labRequest = (LabRequest) request;
-			if (((LabRequest) request).getSession().getCommunicationMethod().isOnline()) {
+			/*
+			 * TODO in the future, delegate to another service layer component,
+			 *  and let it generate a link for the respective online mode.
+			 * This is for the far future, when more online modes need to be supported.
+			 */
+			if (((LabRequest) request).getOnlineMode() == OnlineMode.JITSI) {
 				labRequest.setJitsiRoom(js.createJitsiRoomName(labRequest));
 			}
 			if (((LabRequest) request).getSession().getEnableExperimental()) {
@@ -395,7 +411,6 @@ public class RequestService {
 			Optional<LabRequest> oldRequests = lrr.findCurrentlyProcessingRequest(assistant,
 					request.getSession());
 			if (oldRequests.isPresent()) {
-				lock.unlock();
 				return oldRequests.get();
 			}
 			rer.applyAndSave(new RequestPickedEvent(request, assistant.getId()));
@@ -500,6 +515,34 @@ public class RequestService {
 		});
 	}
 
+	/**
+	 * This is meant to act as a filter for a list of lab requests that originates from a shared session. This
+	 * is so that assistants from other editions don't see requests from other editions they are not an
+	 * assistant of.
+	 *
+	 * @param  requests A list of requests to filter
+	 * @param  person   The assistant that will see the lab requests
+	 * @return          A new list of lab requests that the assistant will see only if they are an assistant
+	 *                  of the edition the request belongs.
+	 */
+	public List<LabRequest> filterRequestsSharedEditionCheck(List<LabRequest> requests, Person person) {
+		// Check to make sure requests are not empty, otherwise API call fails.
+		if (requests.isEmpty()) {
+			return requests;
+		}
+
+		var assignmentIds = requests.stream().map(LabRequest::getAssignment).distinct().toList();
+
+		var allowedAssignments = Objects
+				.requireNonNull(asApi.getAssignmentsWithModules(assignmentIds).collectList().block()).stream()
+				.filter(assignment -> permissionService
+						.hasStaffRole(List.of(assignment.getModule().getEdition().getId())))
+				.map(AssignmentModuleDetailsDTO::getId)
+				.collect(Collectors.toSet());
+
+		return requests.stream().filter(r -> allowedAssignments.contains(r.getAssignment())).toList();
+	}
+
 	/**
 	 * Creates an individual student group for one student without a current group so that they may place a
 	 * request with that student group.
diff --git a/src/main/java/nl/tudelft/queue/service/WebSocketService.java b/src/main/java/nl/tudelft/queue/service/WebSocketService.java
index 65684a1a89176b3f5c2412b5b58cf56b4282fb3c..7d6f840f821de638736dac99d9d0fc33f81f58ee 100644
--- a/src/main/java/nl/tudelft/queue/service/WebSocketService.java
+++ b/src/main/java/nl/tudelft/queue/service/WebSocketService.java
@@ -23,10 +23,12 @@ import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import nl.tudelft.labracore.api.AssignmentControllerApi;
 import nl.tudelft.labracore.api.EditionControllerApi;
 import nl.tudelft.labracore.api.SessionControllerApi;
 import nl.tudelft.labracore.api.StudentGroupControllerApi;
@@ -44,6 +46,8 @@ import org.springframework.messaging.simp.SimpMessageSendingOperations;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import reactor.core.publisher.Flux;
+
 @Service
 public class WebSocketService {
 
@@ -53,6 +57,9 @@ public class WebSocketService {
 	@Autowired
 	private SessionControllerApi sApi;
 
+	@Autowired
+	private AssignmentControllerApi aApi;
+
 	@Autowired
 	private StudentGroupControllerApi sgApi;
 
@@ -92,18 +99,26 @@ public class WebSocketService {
 	 * @param message The message containing information on the event that occurred for the request.
 	 */
 	@Async
-	public void sendRequestTableMessage(Request<?> request, Message message) {
-		var sMono = sApi.getSessionsById(List.of(request.getSession().getSession())).collectList();
-		var rMono = sMono
-				.flatMapIterable(ss -> ss.stream()
-						.flatMap(s -> s.getEditions().stream().map(EditionSummaryDTO::getId))
-						.collect(Collectors.toSet()))
-				.flatMap(id -> eApi.getEditionParticipants(id));
+	public CompletableFuture<Void> sendRequestTableMessage(Request<?> request, Message message) {
+		Flux<RolePersonDetailsDTO> rMono;
+		if (request instanceof LabRequest labRequest) {
+			rMono = aApi.getAssignmentById(labRequest.getAssignment())
+					.flatMapMany(dto -> eApi.getEditionParticipants(dto.getModule().getEdition().getId()));
+		} else {
+			var sMono = sApi.getSessionsById(List.of(request.getSession().getSession())).collectList();
+			rMono = sMono
+					.flatMapIterable(ss -> ss.stream()
+							.flatMap(s -> s.getEditions().stream().map(EditionSummaryDTO::getId))
+							.collect(Collectors.toSet()))
+					.flatMap(id -> eApi.getEditionParticipants(id));
+		}
 
 		sendMessage(Objects.requireNonNull(rMono
 				.filter(r -> r != null && Set.of(TEACHER, TEACHER_RO, HEAD_TA, TA).contains(r.getType()))
 				.map(RolePersonDetailsDTO::getPerson)
 				.distinct().collectList().block()), "/topic/request-table", message);
+
+		return CompletableFuture.completedFuture(null);
 	}
 
 	/**
diff --git a/src/main/resources/migrations.yml b/src/main/resources/migrations.yml
index b229e6f9f62cf2879a04c90d2422a08543c62eb3..79450ef225ed16ff8510a6db110f178761cc4839 100644
--- a/src/main/resources/migrations.yml
+++ b/src/main/resources/migrations.yml
@@ -795,3 +795,111 @@ databaseChangeLog:
                   name: deleted_at
                   type: TIMESTAMP
             tableName: request
+  # Hybrid lab schema changes
+  - changeSet:
+      id: 1665319404891-1
+      author: hpage (generated)
+      changes:
+        - createTable:
+            columns:
+              - column:
+                  constraints:
+                    nullable: false
+                  name: lab_id
+                  type: BIGINT
+              - column:
+                  name: online_modes
+                  type: INT
+            tableName: lab_online_modes
+  - changeSet:
+      id: 1665319404891-2
+      author: hpage (generated)
+      changes:
+        - addColumn:
+            columns:
+              - column:
+                  name: online_mode
+                  type: INTEGER
+            tableName: lab_request
+  - changeSet:
+      id: 1665319404891-3
+      author: hpage (generated)
+      changes:
+        - createIndex:
+            columns:
+              - column:
+                  name: lab_id
+            indexName: FK5rmsu88a1ow9m60756pckpo4_INDEX_8
+            tableName: lab_online_modes
+  - changeSet:
+      id: 1665319404891-4
+      author: hpage (generated)
+      changes:
+        - addForeignKeyConstraint:
+            baseColumnNames: lab_id
+            baseTableName: lab_online_modes
+            constraintName: FK5rmsu88a1ow9m60756pckpo4
+            deferrable: false
+            initiallyDeferred: false
+            onDelete: RESTRICT
+            onUpdate: RESTRICT
+            referencedColumnNames: id
+            referencedTableName: lab
+            validate: true
+  - changeSet:
+      id: update-jitsi-direction-modes
+      author: Ruben Backx
+      changes:
+        - sql:
+            comment: Set all existing online labs to JITSI online mode
+            sql: insert into lab_online_modes (lab_id, online_modes) select id, 0 from lab where communication_method = 'JITSI_MEET';
+  - changeSet:
+      id: update-jitsi-direction-request-modes-1
+      author: Ruben Backx
+      changes:
+        - sql:
+            comment: (MariaDB) Set all existing online requests to JITSI online mode
+            dbms: mariadb
+            sql: update lab_request as lr inner join lab as l inner join request as r on r.session_id = l.id and r.id = lr.id set lr.online_mode = 0 where l.communication_method = 'JITSI_MEET';
+  - changeSet:
+      id: update-jitsi-direction-request-modes-2
+      author: Ruben Backx
+      changes:
+        - sql:
+            comment: (PGSQL) Set all existing online requests to JITSI online mode
+            dbms: postgresql
+            sql: update lab_request as lr set online_mode = 0 from lab as l, request as r where r.session_id = l.id and r.id = lr.id and l.communication_method = 'JITSI_MEET';
+  - changeSet:
+      id: 1665319404891-M1
+      author: Henry Page
+      changes:
+        - sql:
+            comment: Change communication method Jitsi to TA Visit Student
+            sql: update lab set communication_method = 'TA_VISIT_STUDENT' WHERE communication_method = 'JITSI_MEET';
+  # Soft delete feedback and set deletion time
+  - changeSet:
+      id: 1668721308765-1
+      author: henry (generated)
+      changes:
+        - addColumn:
+            columns:
+              - column:
+                  name: deleted_at
+                  type: TIMESTAMP
+            tableName: feedback
+  - changeSet:
+      id: 1668721308765-M1
+      author: Henry Page
+      changes:
+        - sql:
+            comment: (MariaDB) Update feedbacks that have deleted requests to have same deletion time
+            dbms: mariadb
+            sql: UPDATE feedback AS f INNER JOIN request AS r ON r.id=f.request_id SET f.deleted_at = r.deleted_at WHERE r.deleted_at IS NOT NULL;
+  - changeSet:
+      id: 1668721308765-M2
+      author: Henry Page
+      changes:
+        - sql:
+            comment: (PGSQL) Update feedbacks that have deleted requests to have same deletion time
+            dbms: postgresql
+            sql: UPDATE feedback AS f SET deleted_at = r.deleted_at FROM request AS r WHERE f.request_id = r.id AND r.deleted_at IS NOT NULL;
diff --git a/src/main/resources/static/js/request_table.js b/src/main/resources/static/js/request_table.js
index df87bf80b77b1b01a714f1cdecdd6aefb14f4a5a..74ff96666accf63edbf90ae253c8e35bb39509cd 100644
--- a/src/main/resources/static/js/request_table.js
+++ b/src/main/resources/static/js/request_table.js
@@ -41,6 +41,7 @@ const inFilter = (() => {
     let selectedLabs;
     let selectedAssignments;
     let selectedRooms;
+    let selectedOnlineModes;
     let selectedStatuses;
     let selectedTypes;
 
@@ -49,17 +50,22 @@ const inFilter = (() => {
         selectedLabs = findSelected($("#lab-select"));
         selectedAssignments = findSelected($("#assignment-select"));
         selectedRooms = findSelected($("#room-select"));
+        selectedOnlineModes = findSelected($("#onlineMode-select"));
         selectedStatuses = findSelected($("#status-select"));
         selectedTypes = findSelected($("#request-type-select"));
     });
 
     // Return a function checking whether an incoming request passes the filters
     return event => {
+        const locationConstraintMet =
+            (event["roomId"] && !event["onlineMode"]) ? selectedRooms.includes(event["roomId"].toString()) :
+            (!event["roomId"] && event["onlineMode"]) ? selectedOnlineModes.includes(event["onlineMode"]) : false;
+
         return selectedLabs.includes(event["labId"].toString()) &&
             selectedAssignments.includes(event["assignmentId"].toString()) &&
-            selectedRooms.includes(event["roomId"].toString()) &&
             selectedStatuses.includes(event["status"]) &&
-            selectedTypes.includes(event["requestType"]);
+            selectedTypes.includes(event["requestType"]) &&
+            locationConstraintMet;
     }
 }).apply()
 
diff --git a/src/main/resources/templates/admin/view.html b/src/main/resources/templates/admin/view.html
index 869371b05ef44a58cadd70df49524a3d425607df..578b67c34aa5aa421f5e6e112a8a3c4a34ce3201 100644
--- a/src/main/resources/templates/admin/view.html
+++ b/src/main/resources/templates/admin/view.html
@@ -36,10 +36,16 @@
     </section>
 
     <ul class="nav nav-tabs">
+        <li class="nav-item">
+            <a th:href="@{/admin/running}" class="nav-link"
+               th:classappend="${#request.requestURI.matches('.*/running.*') ? 'active' : ''}">
+                <i class="fa fa-th-large" aria-hidden="true"></i>Running Labs
+            </a>
+        </li>
         <li class="nav-item">
             <a th:href="@{/admin/courses}" class="nav-link"
                th:classappend="${#request.requestURI.matches('.*/course.*') ? 'active' : ''}">
-                <i class="fa fa-th-large" aria-hidden="true"></i>Courses
+                <i class="fa fa-th-large" aria-hidden="true"></i>All courses
             </a>
         </li>
         <li class="nav-item">
@@ -51,13 +57,13 @@
         <li class="nav-item">
             <a th:href="@{/admin/rooms}" class="nav-link"
                th:classappend="${#request.requestURI.matches('.*/room.*') ? 'active' : ''}">
-                <i class="fa fa-flask" aria-hidden="true"></i> Rooms
+                <i class="fa fa-flask" aria-hidden="true"></i>Rooms
             </a>
         </li>
         <li class="nav-item">
             <a th:href="@{/admin/announcements}" class="nav-link"
                th:classappend="${#request.requestURI.matches('.*/announcement.*') ? 'active' : ''}">
-                <i class="fa fa-bullhorn" aria-hidden="true"></i> Announcements
+                <i class="fa fa-bullhorn" aria-hidden="true"></i>Announcements
             </a>
         </li>
     </ul>
diff --git a/src/main/resources/templates/admin/view/calendar.html b/src/main/resources/templates/admin/view/calendar.html
index c88d11d5ab77f56bd3f4b1f501ae74d032d827af..3ef86ba86a3015b9d5871f796ff55355f642053b 100644
--- a/src/main/resources/templates/admin/view/calendar.html
+++ b/src/main/resources/templates/admin/view/calendar.html
@@ -12,20 +12,6 @@
 
 <section layout:fragment="subcontent">
     <div class="container">
-        <div class="alert alert-warning mt-md-3" role="alert"
-             th:unless="${#strings.isEmpty(labsOngoing)}">
-            <th:block th:text="${labsOngoing}"/>
-            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
-                <span aria-hidden="true">&times;</span>
-            </button>
-        </div>
-        <div class="alert alert-warning mt-md-3" role="alert"
-             th:unless="${#strings.isEmpty(slotSelectionOpen)}">
-            <th:block th:text="${slotSelectionOpen}"/>
-            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
-                <span aria-hidden="true">&times;</span>
-            </button>
-        </div>
         <div id="calendar"/>
         <script th:inline="javascript">
             document.addEventListener('DOMContentLoaded', function () {
diff --git a/src/main/resources/templates/admin/view/running.html b/src/main/resources/templates/admin/view/running.html
new file mode 100644
index 0000000000000000000000000000000000000000..aa5b60554b530896195399696d02c05e5dd8c583
--- /dev/null
+++ b/src/main/resources/templates/admin/view/running.html
@@ -0,0 +1,77 @@
+<!--
+
+    Queue - A Queueing system that can be used to handle labs in higher education
+    Copyright (C) 2016-2023  Delft University of Technology
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+-->
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{admin/view}">
+
+<body>
+<section layout:fragment="subcontent">
+    <div class="page-sub-header">
+        <h3>Admin overview - Labs running today:</h3>
+    </div>
+    <div class="boxed-group" >
+        <div class="boxed-group-inner" th:if="${#lists.isEmpty(runningLabs)}">
+            None.
+        </div>
+
+        <div class="alert alert-warning mt-md-3" role="alert"
+             th:unless="${#strings.isEmpty(labsOngoing)}">
+            <th:block th:text="${labsOngoing}"/>
+            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+                <span aria-hidden="true">&times;</span>
+            </button>
+        </div>
+        <div class="alert alert-warning mt-md-3" role="alert"
+             th:unless="${#strings.isEmpty(slotSelectionOpen)}">
+            <th:block th:text="${slotSelectionOpen}"/>
+            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+                <span aria-hidden="true">&times;</span>
+            </button>
+        </div>
+
+        <ul class="list-group">
+            <li th:each="lab : ${runningLabs}" th:classappend="${lab.slot.open()} ? 'lab-open' : 'lab-closed'" class="list-group-item">
+                <a href="#" th:href="@{/lab/{id}(id=${lab.id})}"
+                   th:text="|${lab.name} ${#temporals.format(lab.slot.opensAt, 'dd MMMM yyyy HH:mm')} - ${#temporals.format(lab.slot.closesAt, 'dd MMMM yyyy HH:mm')} ${lab.slotOccupationString}|">
+                </a>
+
+                <span class="badge badge-pill badge-info text-white"
+                      th:if="${lab.slot.open()}">Active</span>
+                <span class="badge badge-pill badge-info text-white"
+                      th:if="${lab.slot.closed()}">Completed</span>
+
+                <span class="badge badge-pill badge-danger text-white"
+                      th:if="${lab.type == T(nl.tudelft.queue.model.enums.QueueSessionType).EXAM}">Exam</span>
+                <span class="badge badge-pill badge-info text-white"
+                      th:if="${lab.type == T(nl.tudelft.queue.model.enums.QueueSessionType).SLOTTED}">Slotted</span>
+                <span class="badge badge-pill badge-warning text-white"
+                      th:if="${lab.type == T(nl.tudelft.queue.model.enums.QueueSessionType).CAPACITY}">Limited Capacity</span>
+
+                <span class="badge badge-pill badge-warning text-muted"
+                      th:if="${!lab.slot.closed() && lab.status.isOpenToEnqueueing()}">Open for enqueueing</span>
+            </li>
+        </ul>
+    </div>
+
+    <!-- Invisible column for spacing at the bottom of the page -->
+    <div class="row mt-3"></div>
+</section>
+</body>
+</html>
diff --git a/src/main/resources/templates/edition/index.html b/src/main/resources/templates/edition/index.html
index 7f2200039562cd3bd7e8d8571c91695e941a0261..f0a2ccf1b6d82ced6afc2b70ed2b24e58b4539a6 100644
--- a/src/main/resources/templates/edition/index.html
+++ b/src/main/resources/templates/edition/index.html
@@ -63,6 +63,17 @@
                th:disabled="${!@permissionService.canCreateEdition()}">
                 Create new edition
             </a>
+            <button class="btn btn-outline-primary" onclick="showDialog('create-shared-edition-dialog')">
+                Create course collection
+            </button>
+            <div tabindex="0" class="help-tip">
+                <div class="help-icon">?</div>
+                <p>A course collection is a collection of courses that acts in the queue as one
+                    course. This can be used for sessions in which multiple courses take place at
+                    the same time. This avoids having to create a session for every
+                    course, as sessions can be shared.</p>
+            </div>
+            <div th:replace="~{shared-edition/create/create-shared-edition :: overlay}"></div>
         </th:block>
         <h1>Course Editions</h1>
     </div>
diff --git a/src/main/resources/templates/edition/view/status.html b/src/main/resources/templates/edition/view/status.html
index 916368e57a4007b550ec8f9437066ffafc933d31..77053ed6745577fc6e63b67e063d0502c8decf8d 100644
--- a/src/main/resources/templates/edition/view/status.html
+++ b/src/main/resources/templates/edition/view/status.html
@@ -72,7 +72,7 @@
 
     <div>
         <div id="labs" class="tab-pane">
-            <div th:unless="${#lists.isEmpty(activeLabs) || #lists.isEmpty(inactiveLabs)}">
+            <div th:unless="${#lists.isEmpty(activeLabs) && #lists.isEmpty(inactiveLabs)}">
                 <div class="row">
                     <form id="lab-form" method="GET" class="form-inline">
                         <div class="row">
@@ -219,7 +219,7 @@
                 </div>
             </div>
 
-            <div th:unless="${!#lists.isEmpty(activeLabs) && !#lists.isEmpty(inactiveLabs)}">
+            <div th:if="${#lists.isEmpty(activeLabs) && #lists.isEmpty(inactiveLabs)}">
                 <h5>No labs</h5>
             </div>
         </div>
diff --git a/src/main/resources/templates/home/dashboard.html b/src/main/resources/templates/home/dashboard.html
index fec4e7393198e9c6fbbc81db07debc3b246d0793..d09211c3b153b4a6999772297c93c5f5d1d4df5b 100644
--- a/src/main/resources/templates/home/dashboard.html
+++ b/src/main/resources/templates/home/dashboard.html
@@ -45,19 +45,6 @@
         <div class="page-header">
             <h1>Home</h1>
         </div>
-        <div style="margin-bottom: 1rem" th:if="${@permissionService.isAdminOrTeacher()}">
-            <button class="btn btn-outline-primary" onclick="showDialog('create-shared-edition-dialog')">
-                Create course collection
-            </button>
-            <div tabindex="0" class="help-tip">
-                <div class="help-icon">?</div>
-                <p>A course collection is a collection of courses that acts in the queue as one
-                    course. This can be used for sessions in which multiple courses take place at
-                    the same time. This avoids having to create a session for every
-                    course, as sessions can be shared.</p>
-            </div>
-            <div th:replace="~{shared-edition/create/create-shared-edition :: overlay}"></div>
-        </div>
 
         <ul class="nav nav-tabs" id="overview-tabs" role="tablist">
             <li class="nav-item">
@@ -68,10 +55,6 @@
                 <a class="nav-link" id="calendar-tab" data-toggle="tab" href="#calendar" role="tab"
                    aria-controls="calendar" aria-selected="false">Calendar</a>
             </li>
-            <li class="nav-item" th:if="${@permissionService.isAdmin()}">
-                <a class="nav-link" id="admin-tab" data-toggle="tab" href="#admin" role="tab"
-                   aria-controls="admin" aria-selected="false">Admin</a>
-            </li>
         </ul>
 
         <div class="tab-content mt-2">
@@ -237,38 +220,6 @@
                 </div>
             </div>
 
-            <div class="tab-pane fade" id="admin" role="tabpanel" aria-labelledby="admin-tab"
-                 th:if="${@permissionService.isAdmin()}">
-                <div class="boxed-group" >
-                    <h3>Admin overview - Labs running today:</h3>
-                    <div class="boxed-group-inner" th:if="${#lists.isEmpty(runningLabs)}">
-                        None.
-                    </div>
-
-                    <ul class="list-group">
-                        <li th:each="lab : ${runningLabs}" th:classappend="${lab.slot.open()} ? 'lab-open' : 'lab-closed'" class="list-group-item">
-                            <a href="#" th:href="@{/lab/{id}(id=${lab.id})}"
-                               th:text="|${lab.name} ${#temporals.format(lab.slot.opensAt, 'dd MMMM yyyy HH:mm')} - ${#temporals.format(lab.slot.closesAt, 'dd MMMM yyyy HH:mm')} ${lab.slotOccupationString}|">
-                            </a>
-
-                            <span class="badge badge-pill badge-info text-white"
-                                  th:if="${lab.slot.open()}">Active</span>
-                            <span class="badge badge-pill badge-info text-white"
-                                  th:if="${lab.slot.closed()}">Completed</span>
-
-                            <span class="badge badge-pill badge-danger text-white"
-                                  th:if="${lab.type == T(nl.tudelft.queue.model.enums.QueueSessionType).EXAM}">Exam</span>
-                            <span class="badge badge-pill badge-info text-white"
-                                  th:if="${lab.type == T(nl.tudelft.queue.model.enums.QueueSessionType).SLOTTED}">Slotted</span>
-                            <span class="badge badge-pill badge-warning text-white"
-                                  th:if="${lab.type == T(nl.tudelft.queue.model.enums.QueueSessionType).CAPACITY}">Limited Capacity</span>
-
-                            <span class="badge badge-pill badge-warning text-muted"
-                                  th:if="${!lab.slot.closed() && lab.status.isOpenToEnqueueing()}">Open for enqueueing</span>
-                        </li>
-                    </ul>
-                </div>
-            </div>
         </div>
     </section>
 </body>
diff --git a/src/main/resources/templates/lab/create/components/lab-general.html b/src/main/resources/templates/lab/create/components/lab-general.html
index 4131e10f3e61dc8927ca2e74d5757d91f8221866..0f78eaccc0ebaea3c411fa76f36efc051e3631e4 100644
--- a/src/main/resources/templates/lab/create/components/lab-general.html
+++ b/src/main/resources/templates/lab/create/components/lab-general.html
@@ -44,7 +44,7 @@
             <div class="col-sm-10">
                 <select class="selectpicker form-control" id="direction-select" required name="communicationMethod"
                         th:classappend="${#fields.hasErrors('communicationMethod')} ? 'is-invalid'"
-                        data-title="Pick a direction" onchange="updateJitsi()">
+                        data-title="Pick a direction">
                     <option th:each="cm : ${T(nl.tudelft.queue.model.enums.CommunicationMethod).values()}"
                             th:value="${cm}" th:text="${cm.displayName}"
                             th:selected="${dto.communicationMethod == cm}"></option>
@@ -124,8 +124,8 @@
                 <select multiple class="selectpicker form-control"
                         th:classappend="${#fields.hasErrors('rooms')} ? 'is-invalid'"
                         id="room-select" th:field="*{rooms}" size="10"
-                        data-live-search="true" required
-                        data-title="Pick at least one room"
+                        data-live-search="true"
+                        data-title="Pick a room"
                         data-actions-box="true">
                     <th:block th:each="room : ${rooms}">
                         <option th:value="${room.id}"
@@ -139,6 +139,25 @@
             </div>
         </div>
 
+        <div id="online-mode-select-div" class="form-group form-row mb-4">
+            <label class="col-sm-2 col-form-label" for="online-mode-select">Online Modes</label>
+            <div class="col-sm-10">
+                <select multiple class="selectpicker form-control"
+                        th:classappend="${#fields.hasErrors('onlineModes')} ? 'is-invalid'"
+                        id="online-mode-select" th:field="*{onlineModes}" size="10"
+                        data-live-search="true"
+                        data-title="Pick online mode(s)"
+                        data-actions-box="true">
+                    <th:block th:each="onlineMode : ${T(nl.tudelft.queue.model.enums.OnlineMode).values()}">
+                        <option th:value="${onlineMode}"
+                                th:selected="${dto.onlineModes.contains(onlineMode)}"
+                                th:text="|${onlineMode.getDisplayName()}|">
+                        </option>
+                    </th:block>
+                </select>
+            </div>
+        </div>
+
         <div class="form-group form-row">
             <label class="col-sm-2 col-form-label" for="module-select">Modules</label>
             <div class="col-sm-10">
@@ -255,28 +274,6 @@
                 $("#room-select").selectpicker("refresh");
             }
 
-            function updateJitsi() {
-                const direction = $("#direction-select").val();
-
-                const buildingSelectDiv = $("#building-select-div");
-                const roomSelectDiv = $("#room-select-div");
-
-                if (direction === "JITSI_MEET") {
-                    buildingSelectDiv.addClass("d-none")
-                    roomSelectDiv.addClass("d-none");
-
-                    $("#room-select > option").each(function () {
-                        $(this).attr("selected", $(this).val() === "94");
-                    });
-
-                    $("#room-select").selectpicker("refresh");
-                } else {
-                    buildingSelectDiv.removeClass("d-none")
-                    roomSelectDiv.removeClass("d-none");
-
-                    updateRooms();
-                }
-            }
             //]]>
         </script>
     </th:block>
@@ -284,3 +281,4 @@
 </body>
 </html>
 
+
diff --git a/src/main/resources/templates/lab/create/slotted.html b/src/main/resources/templates/lab/create/slotted.html
index a32e02a55d1dd7a829854cceea9346bdb4b9379c..27a121a12106187cebb77993c731be1340cecaaf 100644
--- a/src/main/resources/templates/lab/create/slotted.html
+++ b/src/main/resources/templates/lab/create/slotted.html
@@ -46,7 +46,7 @@ layout:decorate="~{lab/create/regular}">
     <section layout:fragment="slot-config">
         <h4>Time slots</h4>
         <hr/>
-
+<!--slotted labs only-->
         <div class="form-group form-row">
             <label for="slot-duration-input" class="col-sm-4 col-form-label">Intervals of x minutes:</label>
             <div class="col-sm-4">
diff --git a/src/main/resources/templates/lab/edit/components/lab-general.html b/src/main/resources/templates/lab/edit/components/lab-general.html
index 8ad1e4824c44d191b5de13634767e8e1479a6bc3..cec71e4beb02da428c98b27e3bb4471e6ae6b03e 100644
--- a/src/main/resources/templates/lab/edit/components/lab-general.html
+++ b/src/main/resources/templates/lab/edit/components/lab-general.html
@@ -46,7 +46,7 @@
             <label for="direction-select" class="col-sm-2 col-form-label">Direction:</label>
             <div class="col-sm-10">
                 <select class="selectpicker form-control" id="direction-select"
-                        name="communicationMethod" onchange="updateJitsi()">
+                        name="communicationMethod">
                     <option th:each="cm : ${T(nl.tudelft.queue.model.enums.CommunicationMethod).values()}"
                             th:value="${cm}" th:text="${cm.displayName}"
                             th:selected="${lab.communicationMethod == cm}"></option>
@@ -104,8 +104,7 @@
         <h4>General</h4>
         <hr/>
 
-        <div id="building-select-div" class="form-group form-row mb-4"
-             th:classappend="${lab.communicationMethod.isOnline()} ? 'd-none'">
+        <div id="building-select-div" class="form-group form-row mb-4">
             <label class="col-sm-2 col-form-label" for="building-select">Buildings</label>
             <div class="col-sm-10">
                 <select multiple class="selectpicker form-control" size="10"
@@ -123,14 +122,13 @@
             </div>
         </div>
 
-        <div id="room-select-div" class="form-group form-row mb-4"
-                th:classappend="${lab.communicationMethod.isOnline()} ? 'd-none'">
+        <div id="room-select-div" class="form-group form-row mb-4">
             <label class="col-sm-2 col-form-label" for="room-select">Rooms</label>
             <div class="col-sm-10">
                 <select multiple class="selectpicker form-control"
                         th:classappend="${#fields.hasErrors('rooms')} ? 'is-invalid'"
                         id="room-select" name="rooms" size="10"
-                        data-live-search="true" required>
+                        data-live-search="true">
                     <th:block th:each="r : ${rooms}">
                         <option th:value="${r.id}"
                                 th:classappend="${#lists.isEmpty(lSession.rooms.?[building.id == __${r.building.id}__])} ? 'd-none'"
@@ -142,6 +140,22 @@
                 </select>
             </div>
         </div>
+        <div id="online-mode-select-div" class="form-group form-row mb-4">
+            <label class="col-sm-2 col-form-label" for="online-mode-select">Online Modes</label>
+            <div class="col-sm-10">
+                <select multiple class="selectpicker form-control"
+                        th:classappend="${#fields.hasErrors('onlineModes')} ? 'is-invalid'"
+                        id="online-mode-select" name="onlineModes" size="10"
+                        data-live-search="true">
+                    <th:block th:each="onlineMode : ${T(nl.tudelft.queue.model.enums.OnlineMode).values()}">
+                        <option th:value="${onlineMode}"
+                                th:selected="${@labService.getOnlineModesInLabSession(lSession.id).contains(onlineMode)}"
+                                th:text="|${onlineMode.getDisplayName()}|">
+                        </option>
+                    </th:block>
+                </select>
+            </div>
+        </div>
 
         <div class="form-group form-row">
             <label class="col-sm-2 col-form-label" for="module-select">Modules</label>
@@ -262,29 +276,6 @@
 
                 $("#room-select").selectpicker("refresh");
             }
-
-            function updateJitsi() {
-                const direction = $("#direction-select").val();
-
-                const buildingSelectDiv = $("#building-select-div");
-                const roomSelectDiv = $("#room-select-div");
-
-                if (direction === "JITSI_MEET") {
-                    buildingSelectDiv.addClass("d-none")
-                    roomSelectDiv.addClass("d-none");
-
-                    $("#room-select > option").each(function () {
-                        $(this).attr("selected", $(this).val() === "94");
-                    });
-
-                    $("#room-select").selectpicker("refresh");
-                } else {
-                    buildingSelectDiv.removeClass("d-none")
-                    roomSelectDiv.removeClass("d-none");
-
-                    updateRooms();
-                }
-            }
             //]]>
         </script>
     </th:block>
diff --git a/src/main/resources/templates/lab/enqueue/lab.html b/src/main/resources/templates/lab/enqueue/lab.html
index a6641c3c9d9fbfb7857dbe686b40ec7a744f386d..5a6ba4a4fa378fcfc836206ca1e8a07b3410d277 100644
--- a/src/main/resources/templates/lab/enqueue/lab.html
+++ b/src/main/resources/templates/lab/enqueue/lab.html
@@ -111,7 +111,7 @@
             </div>
         </div>
 
-        <div class="form-group" id="room-div" th:classappend="${qSession.enableExperimental ? 'd-none' : ''}">
+        <div class="form-group" id="room-div" th:unless="${#lists.isEmpty(rooms)}" th:classappend="${qSession.enableExperimental ? 'd-none' : ''}">
             <label for="input-room" class="col-sm-2 control-label">Room</label>
 
             <div class="col-sm-8">
@@ -126,6 +126,26 @@
                     Room error
                 </div>
             </div>
+        </div>
+
+        <div class="form-group" id="onlineMode-div" th:unless="${#sets.isEmpty(qSession.onlineModes)}" th:classappend="${qSession.enableExperimental ? 'd-none' : ''}">
+            <label for="input-onlineMode" class="col-sm-2 control-label">Online Mode</label>
+
+            <div class="col-sm-8">
+                <select class="selectpicker form-control" id="input-onlineMode" th:field="*{onlineMode}"
+                        data-title="Pick an online mode"
+                        th:classappend="${#fields.hasErrors('onlineMode')} ? 'is-invalid'" required>
+                    <option th:each="onlineMode : ${qSession.onlineModes}"
+                            th:id="|input-onlineMode-${onlineMode.name()}|"
+                            th:value="${onlineMode}"
+                            th:text="|${onlineMode.getDisplayName()}|"></option>
+                </select>
+                <div class="invalid-feedback" th:if="${#fields.hasErrors('onlineMode')}" th:errors="*{onlineMode}">
+                    Online Mode error
+                </div>
+            </div>
+
+
         </div>
 
             <div class="form-group" id="question-div" th:classappend="${qSession.enableExperimental ? 'd-none' : ''}">
@@ -182,6 +202,10 @@
             const typeSelect = $("#input-type");
 
             const questionInput = $("#input-question");
+            const commentInput = $("#input-comment");
+
+            const roomSelect = $("#input-room");
+            const onlineModeSelect = $("#input-onlineMode");
 
             assignmentSelect.on("changed.bs.select", () => {
                 typeSelect.val("");
@@ -220,6 +244,23 @@
                     }
                 }
             })
+
+            roomSelect.on("changed.bs.select", () => {
+                onlineModeSelect.prop("selectedIndex",-1);
+                roomSelect.prop("required",true);
+                onlineModeSelect.prop("required",false);
+                commentInput.prop("disabled",false);
+                onlineModeSelect.selectpicker("refresh");
+            });
+            onlineModeSelect.on("changed.bs.select", () => {
+                roomSelect.prop("selectedIndex",-1);
+                onlineModeSelect.prop("required",true);
+                roomSelect.prop("required",false);
+                commentInput.val('');
+                commentInput.prop("disabled",true);
+                roomSelect.selectpicker("refresh");
+            });
+
             //]]>
         </script>
     </th:block>
diff --git a/src/main/resources/templates/lab/view/components/current-request-edit.html b/src/main/resources/templates/lab/view/components/current-request-edit.html
index abcd8bfeb08dc3f1f3756a5650719c46c6f05c03..042615a3f081be48037a20e6bd32657e287d5c18 100644
--- a/src/main/resources/templates/lab/view/components/current-request-edit.html
+++ b/src/main/resources/templates/lab/view/components/current-request-edit.html
@@ -33,31 +33,40 @@
     <div class="col-12 mt-3" th:if="${current.eventInfo.status.isPending()}">
         <div class="card location-info text-white">
             <th:block>
-                <h3 class="card-header">Update your location and question</h3>
+                <h3 class="card-header" th:if="${current.room != null}">Update your location and question</h3>
+                <h3 class="card-header" th:if="${current.onlineMode != null}">Update your question</h3>
                 <div class="card-body col-12 col-md-12">
-                    <span th:text="|Current room: ${current.room.building.name} - ${current.room.name}|"></span>
                     <form class="form" method="post"
                           th:action="@{/request/{id}/update-request-info/(id=${current.id})}"
                           th:object="${currentDto}">
-                        <div class="form-group">
-                            <select class="custom-select custom-select-md"
-                                    th:field="*{room}">
-                                <option disabled value="">Choose your new room</option>
-                                <option th:each="room : ${rooms}"
-                                        th:value="${room.id}"
-                                        th:selected="${current.room == room.id}"
-                                        th:text="|${room.building.name} - ${room.name}|">
-                                </option>
-                            </select>
+                        <div th:if="${current.room != null}" id="physical-location-info">
+                            <span th:text="|Current location: ${current.room?.building?.name} - ${current.room?.name}|"></span>
+                            <div class="form-group">
+                                <select class="custom-select custom-select-md"
+                                        th:field="*{room}">
+                                    <option disabled value="">Choose your new room</option>
+                                    <option th:each="room : ${rooms}"
+                                            th:value="${room.id}"
+                                            th:selected="${current.room == room.id}"
+                                            th:text="|${room.building.name} - ${room.name}|">
+                                    </option>
+                                </select>
+                            </div>
+
+                            <div class="form-group" id="comment">
+                                <label for="inputComment">Where are you located?</label>
+                                <input type="text" class="form-control" id="inputComment"
+                                       placeholder="Cubicle 1..."
+                                       th:field="*{comment}"/>
+                            </div>
                         </div>
 
-                        <div class="form-group" id="comment">
-                            <label for="inputComment">Where are you located?</label>
-                            <input type="text" class="form-control" id="inputComment"
-                                   placeholder="Cubicle 1..."
-                                   th:field="*{comment}"/>
+                        <div th:if="${!#sets.isEmpty(qSession.onlineModes)}" id="online-mode-info">
+
+
                         </div>
 
+
                         <div th:if="${current.requestType == T(nl.tudelft.queue.model.enums.RequestType).QUESTION}"
                              class="form-group" id="question">
                             <label for="inputQuestion">What is your question?</label>
diff --git a/src/main/resources/templates/lab/view/components/lab-info.html b/src/main/resources/templates/lab/view/components/lab-info.html
index f9f9424d5cc61a4e0c4f9a930271041dc500f1bd..6b91618b6565ffa686a3a39f40617e2c747f0f7a 100644
--- a/src/main/resources/templates/lab/view/components/lab-info.html
+++ b/src/main/resources/templates/lab/view/components/lab-info.html
@@ -40,7 +40,22 @@
                 <dd th:text="|${#temporals.format(qSession.session.start, 'dd MMMM yyyy HH:mm')} - ${#temporals.format(qSession.session.end, 'dd MMMM yyyy HH:mm')}|"></dd>
 
                 <dt>Rooms</dt>
-                <dd th:text="${#strings.listJoin(@roomDTOService.names(rooms), ', ')}"></dd>
+                <dd th:if="${#lists.isEmpty(rooms)}"
+                    class="text-danger">
+                    This lab does not have any rooms
+                </dd>
+                <dd th:unless="${#lists.isEmpty(rooms)}"
+                    th:text="${#strings.listJoin(@roomDTOService.names(rooms), ', ')}">
+                </dd>
+
+                <dt>Online Modes</dt>
+                <dd th:if="${#sets.isEmpty(qSession.onlineModes)}"
+                    class="text-danger">
+                    This lab does not have an online alternative
+                </dd>
+                <dd th:unless="${#sets.isEmpty(qSession.onlineModes)}"
+                    th:text="${T(nl.tudelft.queue.model.enums.OnlineMode).displayNames(qSession.onlineModes)}">
+                </dd>
 
                 <dt>Modules</dt>
                 <dd th:if="${#lists.isEmpty(modules)}"
diff --git a/src/main/resources/templates/request/list/filters.html b/src/main/resources/templates/request/list/filters.html
index c1fb374657da598501ae748bdf019afd8fea2363..6a16cb88320760d9dd3afd8811a00c6c36e57ba3 100644
--- a/src/main/resources/templates/request/list/filters.html
+++ b/src/main/resources/templates/request/list/filters.html
@@ -102,6 +102,18 @@
                     </select>
                 </div>
 
+                <div class="form-group">
+                    <label class="form-control-label" for="onlineMode-select">Online Mode</label>
+                    <select multiple class="form-control selectpicker" id="onlineMode-select" th:field="*{onlineModes}">
+                        <th:block th:each="onlineMode : ${T(nl.tudelft.queue.model.enums.OnlineMode).values()}">
+                            <option th:value="${onlineMode}"
+                                    th:selected="${filter.onlineModes.contains(onlineMode)}"
+                                    th:text="|${onlineMode.displayName}|">
+                            </option>
+                        </th:block>
+                    </select>
+                </div>
+
                 <div class="form-group">
                     <label class="form-control-label" for="assigned-select">Assigned</label>
                     <select multiple class="form-control selectpicker" id="assigned-select"
diff --git a/src/main/resources/templates/request/list/request-table.html b/src/main/resources/templates/request/list/request-table.html
index 03d362f4a254cc7ba2d570031bc124c2112ef430..09b08492383bedfb68db2539c4cd8a4daf1a3246 100644
--- a/src/main/resources/templates/request/list/request-table.html
+++ b/src/main/resources/templates/request/list/request-table.html
@@ -66,10 +66,14 @@
                            th:text="${request.requesterEntityName()}">
                         </a>
                     </td>
-                    <td th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'">
+                    <td th:if="${request.room != null && request.room.building != null}" th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'">
                         <a class="text-white" th:href="@{/request/{id}(id=${request.id})}"
                            th:text="|${request.room.building.name} - ${request.room.name}|"></a>
                     </td>
+                    <td th:if="${request.onlineMode != null}" th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'">
+                        <a class="text-white" th:href="@{/request/{id}(id=${request.id})}"
+                           th:text="|${'@Online'} - ${request.onlineMode.displayName}|"></a>
+                    </td>
                     <td th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'">
                         <a class="text-white" th:href="@{/request/{id}(id=${request.id})}"
                            th:text="|${request.assignment.name} (${request.assignment.module.name})|">
@@ -105,7 +109,13 @@
             <td th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'"><span class="badge badge-pill bg-danger" id="status-{{id}}">NEW</span></td>
             <td th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'"><a th:href="${base + '/{{id}}'}" class="text-white">{{requestTypeDisplayName}}</a></td>
             <td th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'"><a th:href="${base + '/{{id}}'}" class="text-white">{{requestedBy}}</a></td>
-            <td th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'"><a th:href="${base + '/{{id}}'}" class="text-white">{{buildingName}} - {{roomName}}</a></td>
+            {{#if roomName}}
+                {{#if buildingName}}
+                    <td th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'"><a th:href="${base + '/{{id}}'}" class="text-white">{{buildingName}} - {{roomName}}</a></td>
+                {{/if}}
+            {{else if onlineMode}}
+                <td th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'"><a th:href="${base + '/{{id}}'}" class="text-white">@Online - {{onlineModeDisplayName}}</a></td>
+            {{/if}}
             <td th:classappend="${@thymeleafConfig.isTheDay()} ? 'ctrl-alt-right'"><a th:href="${base + '/{{id}}'}" class="text-white">{{assignmentName}} ({{moduleName}})</a><br/>
                 <small>Right now</small>
             </td>
diff --git a/src/main/resources/templates/request/view.html b/src/main/resources/templates/request/view.html
index 17d7c98fb21b121cb31d8bebc4a88a2b0b543a48..e07c580e79b5f7c0eb6cf42d408e84132f1e9851 100644
--- a/src/main/resources/templates/request/view.html
+++ b/src/main/resources/templates/request/view.html
@@ -65,7 +65,7 @@
 
         <script src="/js/map_loader.js"></script>
         <script type="text/javascript" th:inline="javascript">
-            const roomId = /*[[${request.room.id}]]*/ 0;
+            const roomId = /*[[${request.room != null} ? ${request.room.id}]]*/ 0;
             updateRequestInfo(roomId);
         </script>
 
diff --git a/src/main/resources/templates/request/view/components/lab-request-info.html b/src/main/resources/templates/request/view/components/lab-request-info.html
index 4229f7f7f9f9931c5ba63fde544a552438b024b7..fe5d9419c7cb8d28730ce5e75f2bef7cd587eba5 100644
--- a/src/main/resources/templates/request/view/components/lab-request-info.html
+++ b/src/main/resources/templates/request/view/components/lab-request-info.html
@@ -49,8 +49,16 @@
                 <dt>Assignment</dt>
                 <dd th:text="${request.assignment.name}"></dd>
 
-                <dt>Room</dt>
-                <dd th:text="|${request.room.building.name} - ${request.room.name}|"></dd>
+                <th:block th:if="${request.room != null}">
+                    <dt>Room</dt>
+                    <dd th:text="|${request.room.building.name} - ${request.room.name}|"></dd>
+                </th:block>
+
+                <th:block th:if="${request.onlineMode != null}">
+                    <dt>Online Mode</dt>
+                    <dd th:text="|${'@Online'} - ${request.onlineMode.displayName}|"></dd>
+                </th:block>
+
 
                 <dt>Type</dt>
                 <dd th:text="${request.requestType.displayName()}"></dd>
@@ -65,7 +73,8 @@
                     <dd th:text="${request.comment}"></dd>
                 </th:block>
 
-                <th:block th:if="${request.getJitsiRoom() != null && @permissionService.canViewRequestJitsiRoom(request.id)}">
+                <!--TODO This block needs to be changed to support other online modes -->
+                <th:block th:if="${request.getOnlineMode() == T(nl.tudelft.queue.model.enums.OnlineMode).JITSI && request.getJitsiRoom() != null && @permissionService.canViewRequestJitsiRoom(request.id)}">
                     <dt>Link to Jitsi Room</dt>
                     <dd>
                         <a th:href="@{${@jitsiService.getJitsiRoomUrl(request.data)}}" th:target="_blank"
diff --git a/src/main/resources/templates/shared-edition/view/session-list.html b/src/main/resources/templates/shared-edition/view/session-list.html
index f5f31fbd8d5bb92c2bb93e7088682a9249e25674..ec18c69fcb07d58a6e293e993236877963895f36 100644
--- a/src/main/resources/templates/shared-edition/view/session-list.html
+++ b/src/main/resources/templates/shared-edition/view/session-list.html
@@ -53,9 +53,33 @@
         <td>
           <a th:text="${lab.name}" th:href="@{/lab/{id}(id=${lab.id})}"></a>
           <span th:if="${lab.isShared}" class="badge badge-pill badge-info text-white">Shared lab</span>
+
         </td>
         <td th:text="${#temporals.format(lab.slot.opensAt, 'dd MMMM yyyy HH:mm')}"></td>
-        <td th:text="${#temporals.format(lab.slot.closesAt, 'dd MMMM yyyy HH:mm')}"></td>
+        <td>
+          <span th:text="${#temporals.format(lab.slot.closesAt, 'dd MMMM yyyy HH:mm')}"></span>
+          <div class="btn-group float-right" th:if="${@permissionService.canManageInAnyEdition(lab.associatedEditions)}">
+
+            <th:block>
+              <a th:href="@{/lab/{id}/edit(id=${lab.id})}" class="btn btn-sm btn-secondary">
+                <i class="fa fa-pencil" aria-hidden="true"></i>
+              </a>
+            </th:block>
+            <th:block>
+              <a th:href="@{/lab/{id}/remove(id=${lab.id})}"
+                 class="btn btn-sm btn-danger">
+                <i class="fa fa-trash-o" aria-hidden="true"></i>
+              </a>
+            </th:block>
+            <th:block>
+              <a href="#"
+                 th:href="@{/lab/{labId}/copy(labId=${lab.id})}"
+                 class="btn btn-sm btn-primary">
+                <i class="fa fa-copy" aria-hidden="true"></i>
+              </a>
+            </th:block>
+          </div>
+        </td>
       </tr>
     </th:block>
   </div>
diff --git a/src/test/java/nl/tudelft/queue/controller/AdminControllerTest.java b/src/test/java/nl/tudelft/queue/controller/AdminControllerTest.java
index 5c35c2ee265350820afd5c9d2ca12a4fd0af5e00..b6d780e667521ae4cbd39e7513eaa5fd7d22e664 100644
--- a/src/test/java/nl/tudelft/queue/controller/AdminControllerTest.java
+++ b/src/test/java/nl/tudelft/queue/controller/AdminControllerTest.java
@@ -17,24 +17,18 @@
  */
 package nl.tudelft.queue.controller;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.Mockito.when;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
 import javax.transaction.Transactional;
 
 import nl.tudelft.labracore.api.EditionControllerApi;
 import nl.tudelft.labracore.api.dto.EditionDetailsDTO;
-import nl.tudelft.labracore.api.dto.EditionSummaryDTO;
 import nl.tudelft.queue.service.LabService;
 import nl.tudelft.queue.service.PermissionService;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -42,7 +36,6 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.web.servlet.MockMvc;
 
-import reactor.core.publisher.Flux;
 import test.TestDatabaseLoader;
 import test.labracore.EditionApiMocker;
 import test.test.TestQueueApplication;
@@ -82,12 +75,7 @@ public class AdminControllerTest {
 	@Test
 	@WithUserDetails("admin")
 	void getAdminPanel() throws Exception {
-		when(ls.countOngoingLabs(anyList())).thenReturn(1L);
-
-		mockMvc.perform(get("/admin/calendar")).andExpect(status().isOk()).andExpect(model().attribute(
-				"labsOngoing", "Currently 1 labs ongoing!")).andReturn();
-		when(eApi.getAllEditionsActiveDuringPeriod(any()))
-				.thenReturn(Flux.just(new ModelMapper().map(oopNow, EditionSummaryDTO.class)));
+		mockMvc.perform(get("/admin/running")).andExpect(status().isOk());
 	}
 
 }
diff --git a/src/test/java/nl/tudelft/queue/controller/EditionControllerTest.java b/src/test/java/nl/tudelft/queue/controller/EditionControllerTest.java
index 89034a3c02bbc47a959e3dff49b2debdc3e82f19..ef4a3b56b8acc29ce41d945faef092d2ff934174 100644
--- a/src/test/java/nl/tudelft/queue/controller/EditionControllerTest.java
+++ b/src/test/java/nl/tudelft/queue/controller/EditionControllerTest.java
@@ -56,6 +56,7 @@ import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import test.TestDatabaseLoader;
 import test.labracore.*;
@@ -83,6 +84,12 @@ public class EditionControllerTest {
 	@Autowired
 	private ModuleApiMocker mApiMocker;
 
+	@Autowired
+	private EditionCollectionApiMocker ecApiMocker;
+
+	@Autowired
+	private PersonApiMocker pApiMocker;
+
 	@Autowired
 	private RoleApiMocker rApiMocker;
 
@@ -107,6 +114,8 @@ public class EditionControllerTest {
 		rApiMocker.mock();
 		sApiMocker.mock();
 		sgApiMocker.mock();
+		ecApiMocker.mock();
+		pApiMocker.mock();
 
 		student100 = db.getStudents()[100];
 		student155 = db.getStudents()[155];
@@ -122,15 +131,20 @@ public class EditionControllerTest {
 		when(eApi.getEditionsPageActiveOrTaughtBy(any(), any(), any(), any()))
 				.thenReturn(Mono.just(new PageEditionDetailsDTO().content(List.of()).totalElements(0L)));
 
+		when(eApi.getAllEditionsActiveDuringPeriod(any())).thenReturn(Flux.just());
+
 		mvc.perform(get("/editions"))
 				.andExpect(status().isOk())
 				.andExpect(model().attribute("page", "catalog"))
-				.andExpect(model().attributeExists("editions"));
+				.andExpect(model().attributeExists("editions"))
+				.andExpect(model().attributeExists("allEditions"));
 	}
 
 	@Test
 	@WithUserDetails("teacher0")
 	void submitFiltersRedirectsBackToEditions() throws Exception {
+		when(eApi.getAllEditionsActiveDuringPeriod(any())).thenReturn(Flux.just());
+
 		MockHttpSession session = new MockHttpSession();
 
 		mvc.perform(post("/editions/filter").with(csrf())
@@ -146,6 +160,8 @@ public class EditionControllerTest {
 		when(eApi.getEditionsPageActiveOrTaughtBy(any(), any(), any(), any()))
 				.thenReturn(Mono.just(new PageEditionDetailsDTO().content(List.of()).totalElements(0L)));
 
+		when(eApi.getAllEditionsActiveDuringPeriod(any())).thenReturn(Flux.just());
+
 		MockHttpSession session = new MockHttpSession();
 
 		mvc.perform(post("/editions/filter").with(csrf())
@@ -167,6 +183,21 @@ public class EditionControllerTest {
 				.andExpect(view().name("course/request"));
 	}
 
+	@Test
+	@WithUserDetails("student155")
+	void allEditionsNotExposedToStudent() throws Exception {
+		when(eApi.getEditionsPageActiveOrTaughtBy(any(), any(), any(), any()))
+				.thenReturn(Mono.just(new PageEditionDetailsDTO().content(List.of()).totalElements(0L)));
+
+		when(eApi.getAllEditionsActiveDuringPeriod(any())).thenReturn(Flux.just());
+
+		mvc.perform(get("/editions"))
+				.andExpect(status().isOk())
+				.andExpect(model().attribute("page", "catalog"))
+				.andExpect(model().attributeExists("editions"))
+				.andExpect(model().attributeDoesNotExist("allEditions"));
+	}
+
 	@Test
 	@WithUserDetails("student155")
 	void getEditionEnrolViewContainsEdition() throws Exception {
diff --git a/src/test/java/nl/tudelft/queue/controller/HomeControllerTest.java b/src/test/java/nl/tudelft/queue/controller/HomeControllerTest.java
index d5be0a02360615bd0c432d1396de8a1bac7a14d1..c97a354986a3c257f8d0e8eadd00ded55a242dab 100644
--- a/src/test/java/nl/tudelft/queue/controller/HomeControllerTest.java
+++ b/src/test/java/nl/tudelft/queue/controller/HomeControllerTest.java
@@ -126,9 +126,8 @@ class HomeControllerTest {
 		mvc.perform(get("/"))
 				.andExpect(status().isOk())
 				.andExpect(model().attribute("user", matchPersonId(student50.getId())))
-				.andExpect(model().attributeExists("activeRoles", "editions"))
-				.andExpect(model().attributeDoesNotExist("runningEditions"))
-				.andExpect(model().attributeDoesNotExist("sharedEdition"))
+				.andExpect(model().attributeExists("activeRoles", "editions", "labs"))
+				.andExpect(model().attributeExists("sharedEditions", "sharedLabs"))
 				.andExpect(view().name("home/dashboard"));
 
 		verify(personApi).getPersonById(student50.getId());
@@ -148,8 +147,6 @@ class HomeControllerTest {
 				.andExpect(model().attribute("user", matchPersonId(admin.getId())))
 				.andExpect(model().attribute("activeRoles", List.of()))
 				.andExpect(model().attribute("editions", Map.of()))
-				.andExpect(model().attribute("runningLabs", List.of()))
-				.andExpect(model().attributeExists("allEditions"))
 				.andExpect(view().name("home/dashboard"));
 
 		verify(personApi).getPersonById(admin.getId());
@@ -163,7 +160,6 @@ class HomeControllerTest {
 
 		mvc.perform(get("/"))
 				.andExpect(status().isOk())
-				.andExpect(model().attributeExists("allEditions"))
 				.andExpect(view().name("home/dashboard"));
 	}
 
diff --git a/src/test/java/nl/tudelft/queue/controller/LabControllerTest.java b/src/test/java/nl/tudelft/queue/controller/LabControllerTest.java
index c487fc419ec87a5eb069490570523165f54c52cf..0bf830f5bbed3c3a9ef921b6abaa445eb0fe6c2f 100644
--- a/src/test/java/nl/tudelft/queue/controller/LabControllerTest.java
+++ b/src/test/java/nl/tudelft/queue/controller/LabControllerTest.java
@@ -42,8 +42,10 @@ import nl.tudelft.queue.dto.patch.labs.ExamLabPatchDTO;
 import nl.tudelft.queue.dto.patch.labs.RegularLabPatchDTO;
 import nl.tudelft.queue.dto.patch.labs.SlottedLabPatchDTO;
 import nl.tudelft.queue.model.LabRequest;
+import nl.tudelft.queue.model.QueueSession;
 import nl.tudelft.queue.model.SelectionRequest;
 import nl.tudelft.queue.model.embeddables.*;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestStatus;
 import nl.tudelft.queue.model.enums.RequestType;
 import nl.tudelft.queue.model.events.RequestApprovedEvent;
@@ -51,6 +53,7 @@ import nl.tudelft.queue.model.events.RequestForwardedToAnyEvent;
 import nl.tudelft.queue.model.events.RequestTakenEvent;
 import nl.tudelft.queue.model.labs.*;
 import nl.tudelft.queue.repository.*;
+import nl.tudelft.queue.service.JitsiService;
 import nl.tudelft.queue.service.LabService;
 import nl.tudelft.queue.service.RequestService;
 
@@ -59,6 +62,8 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -82,7 +87,7 @@ class LabControllerTest {
 	@Autowired
 	private MockMvc mvc;
 
-	@Autowired
+	@SpyBean
 	private QueueSessionRepository qsr;
 
 	@SpyBean
@@ -91,6 +96,9 @@ class LabControllerTest {
 	@SpyBean
 	private RequestService rs;
 
+	@SpyBean
+	private JitsiService js;
+
 	@Autowired
 	private RequestRepository rr;
 
@@ -106,6 +114,9 @@ class LabControllerTest {
 	@Autowired
 	private SessionControllerApi sApi;
 
+	@Captor
+	private ArgumentCaptor<QueueSession<LabRequest>> queueSessionArgumentCaptor;
+
 	private PersonSummaryDTO teacher1;
 	private PersonSummaryDTO student5;
 
@@ -121,6 +132,8 @@ class LabControllerTest {
 
 	private Lab regLab1;
 	private Lab expLab1;
+
+	private Lab regHybridLab1;
 	private SlottedLab slottedLab1;
 	private ExamLab examLabNow;
 	private Lab sharedLab;
@@ -156,6 +169,7 @@ class LabControllerTest {
 		slottedLab1 = slr.getById(db.getOopNowSlottedLab1().getId());
 		examLabNow = elr.getById(db.getOopExamNow().getId());
 		sharedLab = db.getRlOopNowSharedLab();
+		regHybridLab1 = db.getOopNowRegularHybridLab1();
 
 		pastLecture = db.getOopPastLecture();
 		qSession2 = db.getOopLectureRandom();
@@ -331,6 +345,35 @@ class LabControllerTest {
 				.build()), eq(student5.getId()), anyBoolean());
 	}
 
+	@Test
+	@WithUserDetails("student5")
+	void enqueWithOnlineModeSelectedWorks() throws Exception {
+		mvc.perform(post("/lab/" + regHybridLab1.getId() + "/enqueue/lab").with(csrf())
+				.queryParam("requestType", "QUESTION")
+				.queryParam("question", "This is the question I am asking??????")
+				.queryParam("assignment", "" + assignment1.getId())
+				.queryParam("onlineMode", "JITSI"))
+				.andExpect(redirectedUrl("/lab/" + regHybridLab1.getId()));
+
+		verify(js, times(1)).createJitsiRoomName(any());
+
+	}
+
+	@Test
+	@WithUserDetails("student5")
+	void enqueHybridLabWithRoomAndOnlineModeShouldFail() throws Exception {
+		mvc.perform(post("/lab/" + regHybridLab1.getId() + "/enqueue/lab").with(csrf())
+				.queryParam("requestType", "QUESTION")
+				.queryParam("question", "This is the question I am asking??????")
+				.queryParam("assignment", "" + assignment1.getId())
+				.queryParam("room", "" + room1.getId())
+				.queryParam("onlineMode", "JITSI"))
+				.andExpect(status().is4xxClientError());
+
+		verify(js, never()).createJitsiRoomName(any());
+
+	}
+
 	@Test
 	@WithUserDetails("student5")
 	void processingRequestForLabForwardsToRequests() throws Exception {
@@ -544,6 +587,16 @@ class LabControllerTest {
 		verify(ls, times(1)).deleteSession(any(Lab.class));
 	}
 
+	@Test
+	@WithUserDetails("admin")
+	void sharedLabDeleteRedirectsToSharedLabPage() throws Exception {
+		mvc.perform(post("/lab/{id}/remove", sharedLab.getId()).with(csrf()))
+				.andExpect(status().is3xxRedirection())
+				.andExpect(redirectedUrlTemplate("/shared-edition/{shared-edition-id}", ec1.getId()));
+
+		verify(ls, times(1)).deleteSession(any(Lab.class));
+	}
+
 	@Test
 	@WithUserDetails("admin")
 	void getLabCopyViewWorks() throws Exception {
@@ -608,6 +661,54 @@ class LabControllerTest {
 		verify(ls).updateSession(isA(ExamLabPatchDTO.class), isA(ExamLab.class));
 	}
 
+	@Test
+	@WithUserDetails("admin")
+	void sessionWithOnlineModesOnlySucceeds() throws Exception {
+
+		mvc.perform(regularPatch(post("/lab/" + regLab1.getId() + "/edit/regular").with(csrf()))
+				.queryParam("onlineModes", "JITSI"))
+				.andExpect(status().is3xxRedirection())
+				.andExpect(redirectedUrl("/lab/" + regLab1.getId()));
+
+		verify(ls).updateSession(isA(RegularLabPatchDTO.class), isA(RegularLab.class));
+	}
+
+	@Test
+	@WithUserDetails("admin")
+	void createHybridLabWorksNormal() throws Exception {
+
+		mvc.perform(regularCreate(post("/edition/" + oopNow.getId() + "/lab/create/regular"))
+				.queryParam("onlineModes", "JITSI").with(csrf()))
+				.andExpect(status().is3xxRedirection());
+
+		verify(ls).createSessions(isA(RegularLabCreateDTO.class), any(), eq(LabService.SessionType.REGULAR));
+
+		verify(qsr, atLeastOnce()).save(queueSessionArgumentCaptor.capture());
+		List<QueueSession<LabRequest>> qsList = queueSessionArgumentCaptor.getAllValues();
+		var result = qsList.stream().filter(x -> x instanceof Lab).map(x -> (Lab) x)
+				.filter(x -> x.getOnlineModes().size() != 0).findFirst();
+		assertThat(result.isPresent()).isTrue();
+		assertThat(result.get().getOnlineModes()).containsExactlyInAnyOrder(OnlineMode.JITSI);
+	}
+
+	@Test
+	@WithUserDetails("admin")
+	void createInvalidNoRoomNoOnlineModeLabShouldFail() throws Exception {
+		mvc.perform(invalidRegularCreate(post("/edition/" + oopNow.getId() + "/lab/create/regular")))
+				.andExpect(status().is4xxClientError());
+	}
+
+	@Test
+	@WithUserDetails("admin")
+	void createLabWithOnlyOnlineWorks() throws Exception {
+		mvc.perform(invalidRegularCreate(post("/edition/" + oopNow.getId() + "/lab/create/regular"))
+				.queryParam("onlineModes", "JITSI").with(csrf()))
+				.andExpect(status().is3xxRedirection());
+
+		verify(ls).createSessions(isA(RegularLabCreateDTO.class), any(), eq(LabService.SessionType.REGULAR));
+
+	}
+
 	@Test
 	@WithUserDetails("student150")
 	void redirectToEnrollPageWhenNotEnrolled() throws Exception {
@@ -709,4 +810,14 @@ class LabControllerTest {
 				.queryParam("rooms", "" + room1.getId())
 				.queryParam("eolGracePeriod", "15");
 	}
+
+	private MockHttpServletRequestBuilder invalidRegularCreate(MockHttpServletRequestBuilder req) {
+		return req.queryParam("name", "Super Lab")
+				.queryParam("slot.opensAt", "09-09-2020 12:09")
+				.queryParam("slot.closesAt", "10-09-2020 12:09")
+				.queryParam("communicationMethod", "STUDENT_VISIT_TA")
+				.queryParam("modules", "" + labModule.getId())
+				.queryParam("requestTypes['" + assignment1.getId() + "']", "QUESTION")
+				.queryParam("eolGracePeriod", "15");
+	}
 }
diff --git a/src/test/java/nl/tudelft/queue/controller/SharedEditionControllerTest.java b/src/test/java/nl/tudelft/queue/controller/SharedEditionControllerTest.java
index b234fbd92b2f30b291292b33646092ae4a276b75..d57f92fa3c64c7555fb02b71c311952496a74093 100644
--- a/src/test/java/nl/tudelft/queue/controller/SharedEditionControllerTest.java
+++ b/src/test/java/nl/tudelft/queue/controller/SharedEditionControllerTest.java
@@ -46,6 +46,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.http.MediaType;
 import org.springframework.security.test.context.support.WithUserDetails;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 
@@ -59,6 +60,7 @@ import test.test.TestQueueApplication;
 
 @Transactional
 @AutoConfigureMockMvc
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
 @SpringBootTest(classes = TestQueueApplication.class)
 public class SharedEditionControllerTest {
 
diff --git a/src/test/java/nl/tudelft/queue/model/enums/OnlineModeTest.java b/src/test/java/nl/tudelft/queue/model/enums/OnlineModeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8af5353103edc12831a9f34519d43a8276f1626
--- /dev/null
+++ b/src/test/java/nl/tudelft/queue/model/enums/OnlineModeTest.java
@@ -0,0 +1,41 @@
+/*
+ * Queue - A Queueing system that can be used to handle labs in higher education
+ * Copyright (C) 2016-2021  Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.queue.model.enums;
+
+import static nl.tudelft.queue.model.enums.OnlineMode.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+class OnlineModeTest {
+
+	@Test
+	void testDisplayNames() {
+		assertThat(OnlineMode.displayNames(Set.of(JITSI))).isEqualTo("Jitsi");
+	}
+
+	@ParameterizedTest
+	@EnumSource(OnlineMode.class)
+	void displayNameIsCapitalized(OnlineMode mode) {
+		assertThat(mode.getDisplayName().charAt(0)).isUpperCase();
+	}
+}
diff --git a/src/test/java/nl/tudelft/queue/service/EditionServiceTest.java b/src/test/java/nl/tudelft/queue/service/EditionServiceTest.java
index 615dac96a298ff92aedcad0ac8977eafbe3dd4af..7dd15cdc9113277fcab99a7e93bd3301bc02ae05 100644
--- a/src/test/java/nl/tudelft/queue/service/EditionServiceTest.java
+++ b/src/test/java/nl/tudelft/queue/service/EditionServiceTest.java
@@ -121,13 +121,14 @@ public class EditionServiceTest {
 		LocalDateTime labTime = now();
 		labSlot = new Slot(labTime.minusMinutes(5), labTime.plusMinutes(10));
 
-		regularLab = new QueueSessionSummaryDTO(0L, REGULAR, "lab", "ReadableName", labSlot, 0, "",
+		regularLab = new QueueSessionSummaryDTO(0L, REGULAR, null, "lab", "ReadableName", labSlot, 0, "",
 				false, false,
 				QueueSessionStatus.OPEN, false);
-		examLab = new QueueSessionSummaryDTO(1L, EXAM, "lab", "ReadableName", labSlot, 0, "",
+		examLab = new QueueSessionSummaryDTO(1L, EXAM, null, "lab", "ReadableName", labSlot, 0, "",
 				false, false,
 				QueueSessionStatus.OPEN, false);
-		slottedLab = new QueueSessionSummaryDTO(2L, SLOTTED, "lab", "ReadableName", labSlot, 0, "", false,
+		slottedLab = new QueueSessionSummaryDTO(2L, SLOTTED, null, "lab", "ReadableName", labSlot, 0, "",
+				false,
 				false,
 				QueueSessionStatus.OPEN, false);
 
@@ -170,11 +171,13 @@ public class EditionServiceTest {
 	void sortLabsSortsOpenBeforeClosed() {
 		QueueSessionSummaryDTO openLab = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"lab", "ReadableName",
 				new Slot(now().minusMinutes(5), now().plusMinutes(10)),
 				0, "", false, false, QueueSessionStatus.OPEN, false);
 		QueueSessionSummaryDTO closedLab = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"lab", "ReadableName",
 				new Slot(now().minusMinutes(10), now().minusMinutes(5)),
 				0, "", false, false, QueueSessionStatus.OPEN, false);
@@ -187,11 +190,13 @@ public class EditionServiceTest {
 	void sortLabsSortsPastLabsAfterFuture() {
 		QueueSessionSummaryDTO pastLab = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"lab", "ReadableName",
 				new Slot(now().minusMinutes(15), now().minusMinutes(10)),
 				0, "", false, false, QueueSessionStatus.OPEN, false);
 		QueueSessionSummaryDTO futureLab = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"lab", "ReadableName",
 				new Slot(now().plusMinutes(20), now().plusMinutes(100)),
 				0, "", false, false, QueueSessionStatus.OPEN, false);
@@ -204,16 +209,19 @@ public class EditionServiceTest {
 	void sortLabsSortsOnMinutes() {
 		QueueSessionSummaryDTO lab1 = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"lab", "ReadableName",
 				new Slot(now().plusMinutes(5), now().plusMinutes(50)),
 				0, "", false, false, QueueSessionStatus.OPEN, false);
 		QueueSessionSummaryDTO lab2 = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"lab", "ReadableName",
 				new Slot(now().plusMinutes(10), now().plusMinutes(15)),
 				0, "", false, false, QueueSessionStatus.OPEN, false);
 		QueueSessionSummaryDTO lab3 = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"lab", "ReadableName",
 				new Slot(now().plusMinutes(70), now().plusMinutes(90)),
 				0, "", false, false, QueueSessionStatus.OPEN, false);
@@ -225,16 +233,19 @@ public class EditionServiceTest {
 	void sortLabsSortsOnName() {
 		QueueSessionSummaryDTO lab1 = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"Alab", "ReadableName",
 				labSlot,
 				0, "", false, false, QueueSessionStatus.OPEN, false);
 		QueueSessionSummaryDTO lab2 = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"Flab", "ReadableName",
 				labSlot,
 				0, "", false, false, QueueSessionStatus.OPEN, false);
 		QueueSessionSummaryDTO lab3 = new QueueSessionSummaryDTO(0L,
 				REGULAR,
+				null,
 				"Zlab", "ReadableName",
 				labSlot,
 				0, "", false, false, QueueSessionStatus.OPEN, false);
diff --git a/src/test/java/nl/tudelft/queue/service/LabServiceTest.java b/src/test/java/nl/tudelft/queue/service/LabServiceTest.java
index aca75ef83a729c7b2ceaf0f80512f8f244fcedb1..2efe779fccbe3a1a0d2f9fd97b04eab55113e471 100644
--- a/src/test/java/nl/tudelft/queue/service/LabServiceTest.java
+++ b/src/test/java/nl/tudelft/queue/service/LabServiceTest.java
@@ -49,6 +49,7 @@ import nl.tudelft.queue.model.embeddables.AllowedRequest;
 import nl.tudelft.queue.model.embeddables.Slot;
 import nl.tudelft.queue.model.embeddables.SlottedLabConfig;
 import nl.tudelft.queue.model.enums.CommunicationMethod;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestType;
 import nl.tudelft.queue.model.enums.SelectionProcedure;
 import nl.tudelft.queue.model.labs.CapacitySession;
@@ -124,6 +125,8 @@ class LabServiceTest {
 	private LabCreateDTO<RegularLab> createDTO;
 	private LabPatchDTO<RegularLab> labPatchDTO;
 
+	private LabPatchDTO<RegularLab> addOnlineModesPatchDTO;
+
 	private Model model;
 
 	@BeforeEach
@@ -145,7 +148,7 @@ class LabServiceTest {
 		room1 = db.getRoomPc1();
 
 		lab1 = RegularLab.builder()
-				.communicationMethod(CommunicationMethod.JITSI_MEET)
+				.communicationMethod(CommunicationMethod.STUDENT_VISIT_TA)
 				.session(session1.getId())
 				.allowedRequests(Set.of(new AllowedRequest(assignment1.getId(), RequestType.QUESTION)))
 				.build();
@@ -153,13 +156,13 @@ class LabServiceTest {
 				.slottedLabConfig(SlottedLabConfig.builder()
 						.selectionOpensAt(LocalDateTime.now().minusDays(1L))
 						.build())
-				.communicationMethod(CommunicationMethod.JITSI_MEET)
+				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
 				.session(session2.getId())
 				.allowedRequests(Set.of(new AllowedRequest(assignment1.getId(), RequestType.QUESTION)))
 				.build();
 
 		createDTO = RegularLabCreateDTO.builder()
-				.communicationMethod(CommunicationMethod.JITSI_MEET)
+				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
 				.modules(Set.of(module1.getId()))
 				.name("Lab 1")
 				.eolGracePeriod(15)
@@ -182,6 +185,8 @@ class LabServiceTest {
 						.closesAt(LocalDateTime.now().plusHours(1))
 						.build())
 				.build();
+		addOnlineModesPatchDTO = RegularLabPatchDTO.builder()
+				.onlineModes(Set.of(OnlineMode.JITSI)).build();
 	}
 
 	@Test
@@ -419,6 +424,15 @@ class LabServiceTest {
 		assertThat(lab1.getCommunicationMethod()).isEqualTo(CommunicationMethod.STUDENT_VISIT_TA);
 	}
 
+	@Test
+	void updateLabToOnlyhaveOnlineModesWorks() {
+		when(sApi.patchSession(any(), any())).thenReturn(Mono.empty());
+
+		ls.updateSession(addOnlineModesPatchDTO, lab1);
+
+		assertThat(lab1.getOnlineModes()).containsExactly(OnlineMode.JITSI);
+	}
+
 	@Test
 	void updateLabIncludesAssignmentsIfNonEmpty() {
 		when(sApi.patchSession(any(), any())).thenReturn(Mono.empty());
@@ -456,4 +470,11 @@ class LabServiceTest {
 		ls.deleteSession(lab1);
 		assertThat(lab1.getDeletedAt()).isNotNull();
 	}
+
+	@Test
+	void getOnlineModesInLabSession() {
+		var testLab = db.getOopNowRegularHybridLab1();
+		var result = ls.getOnlineModesInLabSession(testLab.getSession());
+		assertThat(result).containsExactlyInAnyOrder(OnlineMode.JITSI);
+	}
 }
diff --git a/src/test/java/nl/tudelft/queue/service/RequestServiceTest.java b/src/test/java/nl/tudelft/queue/service/RequestServiceTest.java
index 50d0e05c8b7bc223180b33ac1c5caf6ae87681f3..597f8f8c5670829f720f16e85c60b45bf228141f 100644
--- a/src/test/java/nl/tudelft/queue/service/RequestServiceTest.java
+++ b/src/test/java/nl/tudelft/queue/service/RequestServiceTest.java
@@ -29,10 +29,14 @@ import java.util.stream.Collectors;
 
 import javax.transaction.Transactional;
 
+import nl.tudelft.labracore.api.AssignmentControllerApi;
+import nl.tudelft.labracore.api.RoleControllerApi;
 import nl.tudelft.labracore.api.StudentGroupControllerApi;
 import nl.tudelft.labracore.api.dto.*;
+import nl.tudelft.labracore.lib.security.user.Person;
 import nl.tudelft.queue.cache.SessionCacheManager;
 import nl.tudelft.queue.dto.create.requests.SelectionRequestCreateDTO;
+import nl.tudelft.queue.model.LabRequest;
 import nl.tudelft.queue.model.QSelectionRequest;
 import nl.tudelft.queue.model.SelectionRequest;
 import nl.tudelft.queue.model.embeddables.CapacitySessionConfig;
@@ -40,6 +44,7 @@ import nl.tudelft.queue.model.embeddables.LabRequestConstraints;
 import nl.tudelft.queue.model.enums.RequestStatus;
 import nl.tudelft.queue.model.enums.SelectionProcedure;
 import nl.tudelft.queue.model.labs.CapacitySession;
+import nl.tudelft.queue.model.labs.RegularLab;
 import nl.tudelft.queue.repository.CapacitySessionRepository;
 import nl.tudelft.queue.repository.SelectionRequestRepository;
 
@@ -47,11 +52,14 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.SpyBean;
 import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.test.context.support.WithUserDetails;
 import org.springframework.test.annotation.DirtiesContext;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import test.TestDatabaseLoader;
 import test.labracore.*;
 import test.test.TestQueueApplication;
 
@@ -89,9 +97,17 @@ public class RequestServiceTest {
 
 	private CapacitySession session;
 
+	private RegularLab oopNowRegularLab1;
+
+	private RoleDetailsDTO[] oopNowTAs;
+
+	private LabRequest[] rlOopNowSharedLabRequests;
+
 	CapacitySessionConfig.CapacitySessionConfigBuilder sessionConfigBuilderSelectNow;
 	CapacitySession.CapacitySessionBuilder<?, ? extends CapacitySession.CapacitySessionBuilder<?, ?>> sessionBuilder;
 
+	@Autowired
+	private TestDatabaseLoader db;
 	@Autowired
 	private CapacitySessionRepository csr;
 
@@ -116,12 +132,24 @@ public class RequestServiceTest {
 	@Autowired
 	private PersonApiMocker pApiMocker;
 
+	@Autowired
+	private RoleApiMocker rlApiMocker;
+
 	@Autowired
 	private ModuleApiMocker mApiMocker;
 
+	@Autowired
+	private AssignmentApiMocker asApiMocker;
+
+	@SpyBean
+	private AssignmentControllerApi asApi;
+
 	@Autowired
 	private StudentGroupControllerApi sgApi;
 
+	@Autowired
+	private RoleControllerApi rlApi;
+
 	@BeforeEach
 	void setUp() {
 		sessionBuilder = CapacitySession.builder()
@@ -134,6 +162,12 @@ public class RequestServiceTest {
 				.enrolmentClosesAt(LocalDateTime.now().minusDays(1))
 				.selectionAt(LocalDateTime.now().minusMinutes(3));
 
+		oopNowRegularLab1 = db.getOopNowRegularLab1();
+
+		oopNowTAs = db.getOopNowTAs();
+
+		rlOopNowSharedLabRequests = db.getRlOopNowSharedLabRequests();
+
 		sApiMocker.save(lcSessionNow);
 		sApiMocker.save(lcSessionOld1);
 		sApiMocker.save(lcSessionOld2);
@@ -143,8 +177,10 @@ public class RequestServiceTest {
 		sApiMocker.mock();
 		sgApiMocker.mock();
 		rApiMocker.mock();
+		rlApiMocker.mock();
 		pApiMocker.mock();
 		mApiMocker.mock();
+		asApiMocker.mock();
 	}
 
 	private void mockStudentGroup(Long moduleId, List<Long> requesterIds, Long studentGroupId) {
@@ -304,4 +340,22 @@ public class RequestServiceTest {
 		verify(sgApi).getStudentGroupsById(List.of(6L));
 		verify(sgApi).addGroup(any());
 	}
+
+	@Test
+	void sharedEditionFilterDoesNotCallApiWhenEmptyRequests() {
+		assertThat(rs.filterRequestsSharedEditionCheck(new ArrayList<>(), new Person())).isEmpty();
+
+		verify(asApi, never()).getAssignmentsWithModules(any());
+
+	}
+
+	@Test
+	@WithUserDetails("student200")
+	void oopTaGetsOopRequestsOnlyInSharedSession() {
+		Person p1 = new Person();
+		p1.setId(oopNowTAs[0].getPerson().getId());
+		assertThat(rs.filterRequestsSharedEditionCheck(Arrays.stream(rlOopNowSharedLabRequests).toList(), p1))
+				.containsExactly(rlOopNowSharedLabRequests);
+	}
+
 }
diff --git a/src/test/java/nl/tudelft/queue/service/WebSocketServiceTest.java b/src/test/java/nl/tudelft/queue/service/WebSocketServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e0821c1450b0c1f7f81de16b8f648cbc3115464
--- /dev/null
+++ b/src/test/java/nl/tudelft/queue/service/WebSocketServiceTest.java
@@ -0,0 +1,132 @@
+/*
+ * Queue - A Queueing system that can be used to handle labs in higher education
+ * Copyright (C) 2016-2021  Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.queue.service;
+
+import static nl.tudelft.labracore.api.dto.RolePersonDetailsDTO.TypeEnum.*;
+import static nl.tudelft.labracore.api.dto.RolePersonDetailsDTO.TypeEnum.TA;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.transaction.Transactional;
+
+import nl.tudelft.labracore.api.AssignmentControllerApi;
+import nl.tudelft.labracore.api.EditionControllerApi;
+import nl.tudelft.labracore.api.SessionControllerApi;
+import nl.tudelft.labracore.api.dto.*;
+import nl.tudelft.queue.model.LabRequest;
+import nl.tudelft.queue.model.enums.RequestType;
+import nl.tudelft.queue.model.labs.RegularLab;
+import nl.tudelft.queue.realtime.messages.RequestCreatedMessage;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.SpyBean;
+
+import reactor.core.publisher.Mono;
+import test.TestDatabaseLoader;
+import test.test.TestQueueApplication;
+
+@Transactional
+@SpringBootTest(classes = TestQueueApplication.class)
+class WebSocketServiceTest {
+
+	@Autowired
+	private TestDatabaseLoader db;
+	@SpyBean
+	private WebSocketService wss;
+
+	@Autowired
+	private AssignmentControllerApi aApi;
+
+	@Autowired
+	private EditionControllerApi eApi;
+
+	@Autowired
+	private SessionControllerApi sApi;
+
+	private EditionDetailsDTO oopNow;
+
+	@Captor
+	private ArgumentCaptor<List<PersonSummaryDTO>> peopleCaptor;
+
+	RegularLab rlOopNowSharedLab;
+
+	AssignmentDetailsDTO oopAssignment1;
+
+	LabRequest sharedLabRequest;
+
+	AssignmentModuleDetailsDTO oopAssignment1ModuleDetails;
+
+	@BeforeEach
+	void setup() {
+
+		db.mockAll();
+
+		oopNow = db.getOopNow();
+		oopAssignment1 = db.getOopNowAssignments()[0];
+		rlOopNowSharedLab = db.getRlOopNowSharedLab();
+
+		sharedLabRequest = LabRequest.builder()
+				.requestType(RequestType.SUBMISSION)
+				.assignment(oopAssignment1.getId())
+				.question("Sample Question asdfasfsadfsa")
+				.room(db.getRoomCz1().getId())
+				.session(db.getRlOopNowSharedLab())
+				.requester(db.getOopNowStudents()[0].getId().getPersonId())
+				.build();
+
+		oopAssignment1ModuleDetails = new AssignmentModuleDetailsDTO()
+				.id(oopAssignment1.getId())
+				.module(new ModuleLayer1DTO()
+						.edition(new EditionSummaryDTO().id(db.getOopNow().getId())));
+
+	}
+
+	@Test
+	void websocketMessageGetsSentToCorrectPeople() {
+		var oopImportantPeople = eApi.getEditionParticipants(oopNow.getId())
+				.collectList()
+				.block()
+				.stream()
+				.filter(r -> r != null && Set.of(TEACHER, TEACHER_RO, HEAD_TA, TA).contains(r.getType()))
+				.map(RolePersonDetailsDTO::getPerson)
+				.distinct()
+				.toList();
+
+		var apiReturn = Mono.just(oopAssignment1ModuleDetails);
+		when(aApi.getAssignmentById(any())).thenReturn(apiReturn);
+
+		wss.sendRequestTableMessage(sharedLabRequest, new RequestCreatedMessage()).join();
+
+		verify(sApi, never()).getSessionsById(any());
+		verify(aApi, times(1)).getAssignmentById(oopAssignment1.getId());
+
+		verify(wss, times(1)).sendMessage(peopleCaptor.capture(), eq("/topic/request-table"),
+				eq(new RequestCreatedMessage()));
+		assertThat(peopleCaptor.getValue()).containsExactlyInAnyOrderElementsOf(oopImportantPeople);
+
+	}
+
+}
diff --git a/src/test/java/test/TestDatabaseLoader.java b/src/test/java/test/TestDatabaseLoader.java
index c0bb52896f038862c4fa552e43aa1554edfcfe46..52351105b968742a369bb6d621fa0dd4d83a3428 100644
--- a/src/test/java/test/TestDatabaseLoader.java
+++ b/src/test/java/test/TestDatabaseLoader.java
@@ -38,6 +38,7 @@ import nl.tudelft.queue.model.constraints.ClusterConstraint;
 import nl.tudelft.queue.model.constraints.ModuleDivisionConstraint;
 import nl.tudelft.queue.model.embeddables.*;
 import nl.tudelft.queue.model.enums.CommunicationMethod;
+import nl.tudelft.queue.model.enums.OnlineMode;
 import nl.tudelft.queue.model.enums.RequestType;
 import nl.tudelft.queue.model.enums.SelectionProcedure;
 import nl.tudelft.queue.model.events.*;
@@ -266,9 +267,13 @@ public class TestDatabaseLoader {
 	private RegularLab oopNowRegularLab2;
 	private LabRequest[] oopNowRegularLab2Requests;
 
+	private LabRequest[] rlOopNowSharedLabRequests;
+
 	private RegularLab oopNowRegularLab3;
 	private RegularLab oopNowRegularLab4;
 
+	private RegularLab oopNowRegularHybridLab1;
+
 	private RegularLab oop20RegLab;
 	private RegularLab oop20RegLabDeleted;
 
@@ -458,6 +463,22 @@ public class TestDatabaseLoader {
 				.modules(Set.of(oopNowLabsModule.getId()))
 				.requests(new ArrayList<>())
 				.build());
+
+		oopNowRegularHybridLab1 = lr.save(RegularLab.builder()
+				.session(createOopNowSessionForLab(
+						"Regular Hybrid Lab 1",
+						List.of(oopNowAssignments[0]),
+						List.of(roomPc1, roomCz1)))
+				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
+				.constraints(LabRequestConstraints.builder()
+						.build())
+				.allowedRequests(Set.of(
+						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.QUESTION)))
+				.modules(Set.of(oopNowLabsModule.getId()))
+				.requests(new ArrayList<>())
+				.onlineModes(Set.of(OnlineMode.JITSI))
+				.build());
+
 	}
 
 	public <TS extends TimeSlot, T extends AbstractSlottedLab<TS>> T createSlotsForLab(T lab) {
@@ -734,6 +755,8 @@ public class TestDatabaseLoader {
 		oopNowRegularLab1Requests = new LabRequest[50];
 		oopNowRegularLab2Requests = new LabRequest[50];
 
+		rlOopNowSharedLabRequests = new LabRequest[50];
+
 		oopPastLectureRequests = new SelectionRequest[50];
 
 		for (int i = 50; i < 100; i++) {
@@ -744,6 +767,9 @@ public class TestDatabaseLoader {
 					RequestType.SUBMISSION,
 					null);
 
+			var reqSharedLab1 = createLabRequest(rlOopNowSharedLab, students[i],
+					oopNowAssignments[i % oopNowAssignments.length], RequestType.SUBMISSION, null);
+
 			var reqPastLecture = createSelectionRequest(oopPastLecture, students[i]);
 			createSelectionRequest(oopLectureRandom, students[i]);
 			createSelectionRequest(oopLectureWeightLR, students[i]);
@@ -769,6 +795,7 @@ public class TestDatabaseLoader {
 			oopNowRegularLab1Requests[i - 50] = reqRegLab1;
 			oopNowRegularLab2Requests[i - 50] = reqRegLab2;
 			oopPastLectureRequests[i - 50] = reqPastLecture;
+			rlOopNowSharedLabRequests[i - 50] = reqSharedLab1;
 		}
 	}
 
@@ -1295,8 +1322,9 @@ public class TestDatabaseLoader {
 		var nAssignments = 3;
 		var assignments = new AssignmentDetailsDTO[nAssignments];
 		for (int i = 0; i < nAssignments; i++) {
+			long assignmentId = module.getId() * 1000L + i;
 			assignments[i] = aApiMocker.save(new AssignmentDetailsDTO()
-					.id(module.getId() * 1000L + i)
+					.id(assignmentId)
 					.name("Assignment " + i)
 					.sessions(new ArrayList<>())
 					.description(new Description().text("Nothing special, just an assignment"))
@@ -1308,6 +1336,8 @@ public class TestDatabaseLoader {
 					.submissions(new ArrayList<>())
 					.submissionLimit(null));
 			module.addAssignmentsItem(toView(assignments[i], AssignmentSummaryDTO.class));
+
+			aApiMocker.initializeAssignmentWithModule(assignmentId, module);
 		}
 		return assignments;
 	}
diff --git a/src/test/java/test/labracore/AssignmentApiMocker.java b/src/test/java/test/labracore/AssignmentApiMocker.java
index c693c1e38b5ab8c996fb06cae56ca47b81afeb86..3bc15ad92d02904067bc54810142fab80366296f 100644
--- a/src/test/java/test/labracore/AssignmentApiMocker.java
+++ b/src/test/java/test/labracore/AssignmentApiMocker.java
@@ -20,25 +20,41 @@ package test.labracore;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 import lombok.AllArgsConstructor;
 import nl.tudelft.labracore.api.AssignmentControllerApi;
-import nl.tudelft.labracore.api.dto.AssignmentDetailsDTO;
-import nl.tudelft.labracore.api.dto.AssignmentSummaryDTO;
+import nl.tudelft.labracore.api.dto.*;
 
 import org.modelmapper.ModelMapper;
 
 import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Maps;
 
 @AllArgsConstructor
 public class AssignmentApiMocker extends LabracoreApiMocker<AssignmentDetailsDTO, Long> {
 	private final AssignmentControllerApi aApi;
 
+	private final Map<Long, AssignmentModuleDetailsDTO> assignmentsWithModules = new HashMap<>();
+
 	@Override
 	public void mock() {
 		when(aApi.getAllAssignmentsById(any())).thenAnswer(getAllByIds);
 
+		when(aApi.getAssignmentById(any())).thenReturn(Mono.empty());
+
+		when(aApi.getAssignmentsWithModules(any())).thenAnswer(invocation -> {
+			List<Long> ids = invocation.getArgument(0);
+			return Flux.fromIterable(Maps.filterKeys(assignmentsWithModules, Predicates.in(ids)).values());
+		});
+
 		when(aApi.getAllAssignments()).thenAnswer(invocation -> {
 			var mapper = new ModelMapper();
 			var summaries = data.values().stream()
@@ -49,8 +65,27 @@ public class AssignmentApiMocker extends LabracoreApiMocker<AssignmentDetailsDTO
 		});
 	}
 
+	@Override
+	public AssignmentDetailsDTO save(AssignmentDetailsDTO dto) {
+		super.save(dto);
+
+		var assignmentWithModule = mapper.map(dto, AssignmentModuleDetailsDTO.class);
+
+		assignmentsWithModules.put(Objects.requireNonNull(dto.getId()), assignmentWithModule);
+
+		return dto;
+	}
+
 	@Override
 	public Long getId(AssignmentDetailsDTO dto) {
 		return dto.getId();
 	}
+
+	public void initializeAssignmentWithModule(long assignmentId, ModuleDetailsDTO module) {
+		var moduleSummary = Objects.requireNonNull(assignmentsWithModules.get(assignmentId));
+
+		var mappedModule = mapper.map(module, ModuleLayer1DTO.class);
+
+		moduleSummary.setModule(mappedModule);
+	}
 }