Skip to content
Snippets Groups Projects
Commit 14aa1cbe authored by Henry Page's avatar Henry Page :speech_balloon:
Browse files

fix merge conflicts

parents 8d2b9a58 ddaa2a3f
Branches
Tags
2 merge requests!664Version 2.1.4,!654Resolve "Cascade soft delete of core entities to queue"
Showing
with 293 additions and 56 deletions
......@@ -11,7 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Slotted labs and exam labs now support consecutive filling [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
### Changed
......@@ -22,6 +21,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
## [2.1.3]
### Added
- Slotted labs and exam labs now support consecutive filling. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
### Changed
- Breadcrumb now shows course names and editions instead of codes. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Lab creation buttons are now merged into one button. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx)
- Labs and status page are completely disabled when there is no modules/assignments. This prevents users from accidentally creating a lab without assignments, which leads them to an non-descriptive error page. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx)
### Fixed
- Slotted Labs are now offset based, a change to the start date will now move the slots accordingly. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Fixed a bug where students could 'intentionally' queue in disabled slots within slotted labs. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Admin tabs no longer show up on the 'create edition' page. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx)
### Deprecated
### Removed
## [2.1.2]
### Added
- New history page that shows the handled requests in the current session. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx)
......
......@@ -4,7 +4,7 @@ import com.diffplug.gradle.spotless.SpotlessExtension
import org.springframework.boot.gradle.tasks.run.BootRun
group = "nl.tudelft.ewi.queue"
version = "2.1.2"
version = "2.1.3"
val javaVersion = JavaVersion.VERSION_17
......
......@@ -21,6 +21,7 @@ import nl.tudelft.labracore.api.AssignmentControllerApi;
import nl.tudelft.labracore.api.dto.AssignmentCreateDTO;
import nl.tudelft.labracore.api.dto.EditionDetailsDTO;
import nl.tudelft.labracore.api.dto.ModuleDetailsDTO;
import nl.tudelft.labracore.api.dto.ModuleSummaryDTO;
import nl.tudelft.queue.cache.AssignmentCacheManager;
import nl.tudelft.queue.cache.EditionCacheManager;
import nl.tudelft.queue.cache.ModuleCacheManager;
......@@ -113,6 +114,10 @@ public class AssignmentController {
var edition = eCache.getRequired(module.getEdition().getId());
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("assignment", assignment);
return "assignment/remove";
......@@ -150,6 +155,10 @@ public class AssignmentController {
EditionDetailsDTO edition = eCache.getRequired(module.getEdition().getId());
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("_module", module);
model.addAttribute("dto", dto);
......
......@@ -29,7 +29,6 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import javax.validation.Valid;
import nl.tudelft.labracore.api.*;
......@@ -52,6 +51,7 @@ import nl.tudelft.queue.repository.QueueSessionRepository;
import nl.tudelft.queue.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
......@@ -139,6 +139,7 @@ public class EditionController {
private QuestionService qs;
@Autowired
@Lazy
private LabService ls;
/**
......@@ -164,7 +165,6 @@ public class EditionController {
.block();
eCache.register(editions.getContent());
erCache.getAndIgnoreMissing(editions.getContent().stream().map(EditionDetailsDTO::getId));
model.addAttribute("editions",
......@@ -257,7 +257,13 @@ public class EditionController {
*/
@GetMapping("/edition/{editionId}")
public String getEditionView(@PathVariable Long editionId, Model model) {
model.addAttribute("edition", eCache.getRequired(editionId));
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
return "edition/view/info";
}
......@@ -283,6 +289,10 @@ public class EditionController {
}
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("students", toPage(pageable, students));
return "edition/view/participants";
......@@ -314,6 +324,8 @@ public class EditionController {
model.addAttribute("edition", edition);
model.addAttribute("modules", modules);
model.addAttribute("assignments",
modules.stream().flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("groups", modules.stream()
.collect(Collectors.toMap(ModuleDetailsDTO::getId,
m -> sgCache.getAndIgnoreMissing(
......@@ -333,7 +345,6 @@ public class EditionController {
*/
@GetMapping("/edition/{editionId}/labs")
@PreAuthorize("@permissionService.canViewEdition(#editionId)")
@Transactional
public String getEditionSessionsView(@PathVariable Long editionId,
@RequestParam(required = false, defaultValue = "") List<QueueSessionType> queueSessionTypes,
@RequestParam(required = false, defaultValue = "") List<Long> modules,
......@@ -358,6 +369,10 @@ public class EditionController {
model.addAttribute("queueSessionTypes", queueSessionTypes);
model.addAttribute("allModules", edition.getModules());
model.addAttribute("modules", modules);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
return "edition/view/labs";
}
......@@ -372,7 +387,13 @@ public class EditionController {
@GetMapping("/edition/{editionId}/questions")
@PreAuthorize("@permissionService.canManageQuestions(#editionId)")
public String getEditionQuestions(@PathVariable Long editionId, Model model) {
model.addAttribute("edition", eCache.getRequired(editionId));
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("questions", qApi.getQuestionsByEdition(editionId).collectList().block());
return "edition/view/questions";
......@@ -388,7 +409,13 @@ public class EditionController {
@GetMapping("/edition/{editionId}/participants/create")
@PreAuthorize("@permissionService.canManageParticipants(#editionId)")
public String getAddParticipantPage(@PathVariable Long editionId, Model model) {
model.addAttribute("edition", eCache.getRequired(editionId));
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("role", new QueueRoleCreateDTO(editionId));
return "edition/create/participant";
......@@ -410,7 +437,13 @@ public class EditionController {
RoleCreateDTO create = dto.apply();
if (dto.hasErrors()) {
model.addAttribute("edition", eCache.getRequired(editionId));
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("role", dto);
return "edition/create/participant";
......@@ -473,6 +506,10 @@ public class EditionController {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("role", rCache.getRequired(new Id()
.personId(personId).editionId(editionId)));
......@@ -520,7 +557,12 @@ public class EditionController {
@PreAuthorize("@permissionService.canLeaveEdition(#editionId)")
public String getParticipantLeavePage(@PathVariable Long editionId,
Model model) {
model.addAttribute("edition", eCache.getRequired(editionId));
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
return "edition/view/leave";
}
......@@ -552,7 +594,6 @@ public class EditionController {
*/
@GetMapping("/edition/{editionId}/status")
@PreAuthorize("@permissionService.canViewEditionStatus(#editionId)")
@Transactional
public String status(@PathVariable Long editionId,
Model model) {
var edition = eCache.getRequired(editionId);
......@@ -560,6 +601,10 @@ public class EditionController {
ls.deleteSessionsByIds());
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("activeLabs", getLabSummariesFromSessions(
sessions, s -> s.getStart().isBefore(now()) && now().isBefore(s.getEnd())));
......@@ -674,6 +719,10 @@ public class EditionController {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("question", qApi.getQuestionById(questionId).block());
model.addAttribute("assignments",
mApi.getModulesById(edition.getModules().stream().map(ModuleSummaryDTO::getId)
......@@ -716,6 +765,10 @@ public class EditionController {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
.flatMap(m -> m.getAssignments().stream()).toList());
model.addAttribute("question", qApi.getQuestionById(questionId).block());
return "edition/remove/question";
......
......@@ -20,6 +20,7 @@ package nl.tudelft.queue.dto.patch.labs;
import javax.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;
import nl.tudelft.queue.dto.patch.LabPatchDTO;
import nl.tudelft.queue.dto.patch.SlottedLabConfigPatchDTO;
import nl.tudelft.queue.model.labs.AbstractSlottedLab;
......@@ -28,8 +29,10 @@ import nl.tudelft.queue.model.labs.AbstractSlottedLab;
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
public abstract class AbstractSlottedLabPatchDTO<D extends AbstractSlottedLab<?>> extends LabPatchDTO<D> {
@NotNull
@Builder.Default
private SlottedLabConfigPatchDTO slottedLabConfig = new SlottedLabConfigPatchDTO();
private Boolean canSelectDueSlots;
......@@ -45,4 +48,5 @@ public abstract class AbstractSlottedLabPatchDTO<D extends AbstractSlottedLab<?>
updateNonNull(canSelectDueSlots, data::setCanSelectDueSlots);
updateNonNull(earlyOpenTime, data::setEarlyOpenTime);
}
}
......@@ -18,13 +18,13 @@
package nl.tudelft.queue.dto.patch.labs;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder;
import nl.tudelft.queue.model.labs.SlottedLab;
@Data
@Builder
@SuperBuilder
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class SlottedLabPatchDTO extends AbstractSlottedLabPatchDTO<SlottedLab> {
......
......@@ -49,7 +49,8 @@ public class ClosableTimeSlot extends TimeSlot {
public ClosableTimeSlot(
@NotNull AbstractSlottedLab<ClosableTimeSlot> lab,
@NotNull Slot slot,
@Min(1) @NotNull int capacity) {
super(lab, slot, capacity);
@Min(1) @NotNull int capacity,
@Min(0) @NotNull int offsetSequenceNumber) {
super(lab, slot, capacity, offsetSequenceNumber);
}
}
......@@ -25,10 +25,7 @@ import javax.persistence.*;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.*;
import nl.tudelft.queue.model.embeddables.Slot;
import nl.tudelft.queue.model.enums.RequestStatus;
import nl.tudelft.queue.model.labs.AbstractSlottedLab;
......@@ -47,7 +44,7 @@ import nl.tudelft.queue.model.labs.Lab;
@NoArgsConstructor
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(uniqueConstraints = {
@UniqueConstraint(columnNames = { "lab_id", "opensAt" })
@UniqueConstraint(columnNames = { "lab_id", "offset_sequence_number" })
})
public class TimeSlot {
@Id
......@@ -56,10 +53,6 @@ public class TimeSlot {
/**
* The lab that this TimeSlot is a part of.
* <p>
* This column cannot be updated after initial creation, because it makes no sense to reassign a timeslot
* to a different lab. Additionally, this column has to be given a name manually to correspond to the
* unique constraint names defined at class-level.
*/
@NotNull
@ManyToOne
......@@ -74,7 +67,7 @@ public class TimeSlot {
*/
@NotNull
@Embedded
@Column(updatable = false)
@Column
private Slot slot;
/**
......@@ -85,6 +78,14 @@ public class TimeSlot {
@NotNull
private int capacity;
/**
* The 'block' number in which this slot is processed in, represented as a zero-based offset.
*/
@Min(0)
@NotNull
@Column(name = "offset_sequence_number")
private Integer offsetSequenceNumber;
/**
* The requests that currently claim this slot.
*/
......@@ -103,10 +104,12 @@ public class TimeSlot {
*/
public TimeSlot(@NotNull AbstractSlottedLab<? extends TimeSlot> lab,
@NotNull Slot slot,
@Min(1) @NotNull int capacity) {
@Min(1) @NotNull int capacity,
@Min(0) @NotNull int offsetSequenceNumber) {
this.lab = lab;
this.slot = slot;
this.capacity = capacity;
this.offsetSequenceNumber = offsetSequenceNumber;
}
/**
......
......@@ -40,6 +40,21 @@ import org.springframework.format.annotation.DateTimeFormat;
public class SlottedLabConfig {
private static final String TIME_FORMAT = "dd-MM-yyyy HH:mm";
/**
* Constructor for the SlottedLabConfig, for non-consecutive labs.
*
* @param duration The duration of the slotted lab
* @param capacity The capacity of the slotted lab
* @param selectionOpensAt The time when selection opens.
*/
public SlottedLabConfig(Long duration, Integer capacity, LocalDateTime selectionOpensAt) {
this.duration = duration;
this.capacity = capacity;
this.selectionOpensAt = selectionOpensAt;
this.consideredFullPercentage = 0;
this.previousEmptyAllowedThreshold = 0;
}
/**
* The time that should be assigned to each of the timeslots. The slot duration is stored in minutes.
*/
......
......@@ -42,6 +42,7 @@ import nl.tudelft.queue.csv.*;
import nl.tudelft.queue.dto.create.QueueSessionCreateDTO;
import nl.tudelft.queue.dto.create.labs.AbstractSlottedLabCreateDTO;
import nl.tudelft.queue.dto.patch.QueueSessionPatchDTO;
import nl.tudelft.queue.dto.patch.labs.AbstractSlottedLabPatchDTO;
import nl.tudelft.queue.dto.view.CalendarEntryViewDTO;
import nl.tudelft.queue.dto.view.requests.LabRequestViewDTO;
import nl.tudelft.queue.dto.view.requests.SelectionRequestViewDTO;
......@@ -335,6 +336,13 @@ public class LabService {
.assignments(assignments)
.rooms(rooms)).block();
session.setExtraInfo(dto.getExtraInfo());
if (dto instanceof AbstractSlottedLabPatchDTO<?> slottedLabPatchDTO
&& session instanceof AbstractSlottedLab<?> slottedLab
&& dto.getSlot() != null) {
tss.updateSlots(slottedLabPatchDTO, slottedLab);
}
dto.apply(session);
}
......
......@@ -139,6 +139,11 @@ public class RequestService {
* 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.getTimeSlot() != null && !labRequest.getTimeSlot().canTakeSlot()) {
throw new AccessDeniedException("Time slot is not available");
}
if (((LabRequest) request).getOnlineMode() == OnlineMode.JITSI) {
labRequest.setJitsiRoom(js.createJitsiRoomName(labRequest));
}
......
......@@ -17,7 +17,9 @@
*/
package nl.tudelft.queue.service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
......@@ -26,6 +28,7 @@ import javax.validation.constraints.NotNull;
import nl.tudelft.queue.dto.create.embeddables.SlottedLabConfigCreateDTO;
import nl.tudelft.queue.dto.create.labs.AbstractSlottedLabCreateDTO;
import nl.tudelft.queue.dto.patch.labs.AbstractSlottedLabPatchDTO;
import nl.tudelft.queue.model.ClosableTimeSlot;
import nl.tudelft.queue.model.TimeSlot;
import nl.tudelft.queue.model.embeddables.Slot;
......@@ -34,6 +37,7 @@ import nl.tudelft.queue.model.labs.ExamLab;
import nl.tudelft.queue.repository.ClosableTimeSlotRepository;
import nl.tudelft.queue.repository.TimeSlotRepository;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization;
import org.springframework.scheduling.annotation.Scheduled;
......@@ -84,11 +88,13 @@ public class TimeSlotService {
long duration = config.getDuration();
// Generate a stream of the start times of all timeslots and map it to a timeslot
List<TimeSlot> slots = Stream.iterate(dto.getSlot().getOpensAt(),
c -> !c.plusMinutes(duration).isAfter(dto.getSlot().getClosesAt()),
c -> c.plusMinutes(duration))
.map(c -> createSlotForLab(lab, new Slot(c, c.plusMinutes(duration)), config.getCapacity()))
// Generate a stream of the start times of all timeslots and map it to a timeslot,
// the generation of slots is accompanied by the generation of offsets.
List<TimeSlot> slots = Stream.iterate(Pair.of(dto.getSlot().getOpensAt(), 0),
x -> !x.getKey().plusMinutes(duration).isAfter(dto.getSlot().getClosesAt()),
x -> Pair.of(x.getKey().plusMinutes(duration), x.getValue() + 1))
.map(c -> createSlotForLab(lab, new Slot(c.getKey(), c.getKey().plusMinutes(duration)),
config.getCapacity(), c.getValue()))
.collect(Collectors.toList());
// Simply save all slots. Maybe we will need to do something extra with these in the future.
......@@ -103,11 +109,11 @@ public class TimeSlotService {
* @param capacity The capacity of the time slot.
* @return The created time slot intended for the given lab.
*/
public TimeSlot createSlotForLab(AbstractSlottedLab<?> lab, Slot slot, int capacity) {
public TimeSlot createSlotForLab(AbstractSlottedLab<?> lab, Slot slot, int capacity, int offset) {
if (lab instanceof ExamLab) {
return new ClosableTimeSlot((ExamLab) lab, slot, capacity);
return new ClosableTimeSlot((ExamLab) lab, slot, capacity, offset);
}
return new TimeSlot(lab, slot, capacity);
return new TimeSlot(lab, slot, capacity, offset);
}
/**
......@@ -122,4 +128,26 @@ public class TimeSlotService {
ctsr.findAllClosablyActive().forEach(this::closeTimeSlot);
}
/**
* Updates time slots based on offset sequence number found in TimeSlot Object
*
* @param slottedLabPatchDTO The DTO which represents the PatchDTO for the Slotted Lab
* @param slottedLab The session representing the Slotted Lab
*/
@Transactional
public void updateSlots(@NotNull AbstractSlottedLabPatchDTO<?> slottedLabPatchDTO,
@NotNull AbstractSlottedLab<?> slottedLab) {
Long duration = slottedLab.getSlottedLabConfig().getDuration();
List<? extends TimeSlot> timeSlots = slottedLab.getTimeSlots();
LocalDateTime newStartTime = slottedLabPatchDTO.getSlot().getOpensAt();
BiFunction<LocalDateTime, Integer, Slot> calculateSlotLambda = (startTime, offset) -> new Slot(
startTime.plusMinutes(offset * duration),
startTime.plusMinutes(offset * duration + duration));
timeSlots.forEach(slot -> slot
.setSlot(calculateSlotLambda.apply(newStartTime, slot.getOffsetSequenceNumber())));
}
}
......@@ -903,6 +903,61 @@ databaseChangeLog:
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;
# Add offset sequence index number
- changeSet:
id: 1679059001947-1
author: henry (generated)
changes:
- addColumn:
columns:
- column:
name: offset_sequence_number
type: INTEGER
tableName: time_slot
- changeSet:
id: 1679059001947-2
author: henry (generated)
changes:
- dropUniqueConstraint:
constraintName: UK12t0una5fqrocchumju6p2a5
tableName: time_slot
- changeSet:
id: 1679059001947-M1
author: Henry Page
changes:
- sql:
dbms: mysql, mariadb
sql:
UPDATE time_slot
JOIN (
SELECT id, ROW_NUMBER() OVER (PARTITION BY lab_id ORDER BY opens_at) - 1 AS row_idx_num
FROM time_slot
) AS r ON time_slot.id = r.id
SET time_slot.offset_sequence_number = r.row_idx_num;
- sql:
dbms: postgresql
sql:
UPDATE time_slot
SET offset_sequence_number = r.row
FROM (SELECT id, (rank() OVER (PARTITION BY lab_id ORDER BY opens_at) - 1) AS row
FROM time_slot) AS r
WHERE time_slot.id = r.id;
- changeSet:
id: 1679059001947-3
author: henry (generated)
changes:
- addUniqueConstraint:
columnNames: lab_id, offset_sequence_number
constraintName: UKg80kvvrhf2o4x0vs4uoh44elu
tableName: time_slot
- changeSet:
id: 1679059001947-4
author: henry (generated)
changes:
- addNotNullConstraint:
tableName: time_slot
columnName: offset_sequence_number
columnDataType: INTEGER
# Consecutive Filling Settings
- changeSet:
id: 1679676383754-1
......@@ -990,3 +1045,7 @@ databaseChangeLog:
tableName: slotted_lab
columnName: previous_empty_allowed_threshold
columnDataType: INTEGER
......@@ -19,7 +19,7 @@
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{admin/view}">
layout:decorate="~{layout}">
<!--@thymesVar id="edition" type="nl.tudelft.queue.dto.create.QueueEditionCreateDTO"-->
......@@ -38,7 +38,7 @@
</head>
<body>
<section layout:fragment="subcontent">
<section layout:fragment="content">
<div class="page-sub-header">
<h3>Create edition</h3>
</div>
......
......@@ -38,12 +38,19 @@
<a th:unless="${edition != null && @permissionService.canEnrolForEdition(edition.id)}"
th:href="@{/}">My Courses</a>
</li>
<li class="breadcrumb-item active" th:if="${ec == null}" th:text="${edition.course.code}">TI1316</li>
<li class="breadcrumb-item active" th:if="${ec == null}" th:text="|${edition.course.name} - ${edition.name}|">TI1316</li>
<li class="breadcrumb-item active" th:unless="${ec == null}" th:text="${ec.name}">TI1316</li>
</ol>
</nav>
</section>
<div class="alert alert-danger mt-md-3" role="alert" th:if="${ec == null && #lists.isEmpty(edition.modules)}">
<span>This edition does not have any modules, create a module first to be able to create sessions.</span>
</div>
<div class="alert alert-danger mt-md-3" role="alert" th:if="${ec == null && assignments != null && !#lists.isEmpty(edition.modules) && #lists.isEmpty(assignments)}">
<span>This edition does not have any assignments, create an assignments first to be able to create sessions.</span>
</div>
<ul class="nav nav-tabs" th:if="${ec == null}">
<li class="nav-item">
<a href="#" class="nav-link"
......@@ -66,14 +73,14 @@
<i class="fa fa-boxes" aria-hidden="true"></i> Modules
</a>
</li>
<li class="nav-item" th:if="${@permissionService.canViewEdition(edition.id)}">
<li class="nav-item" th:if="${@permissionService.canViewEdition(edition.id) && !#lists.isEmpty(edition.modules) && !#lists.isEmpty(assignments)}">
<a href="#" class="nav-link"
th:classappend="${#request.requestURI.matches('.*/labs.*') ? 'active' : ''}"
th:href="@{/edition/{id}/labs(id=${edition.id})}">
<i class="fa fa-calendar" aria-hidden="true"></i> Labs
</a>
</li>
<li class="nav-item" th:if="${@permissionService.canManageAssignments(edition.id)}">
<li class="nav-item" th:if="${@permissionService.canManageAssignments(edition.id) && !#lists.isEmpty(edition.modules) && !#lists.isEmpty(assignments)}">
<a href="#" class="nav-link"
th:classappend="${#request.requestURI.matches('.*/status.*') ? 'active' : ''}"
th:href="@{/edition/{id}/status(id=${edition.id})}">
......
......@@ -48,13 +48,9 @@
</div>
<div class="page-sub-header">
<div class="float-right" th:if="${@permissionService.canManageSessions(edition.id)}">
<a th:each="lType : ${T(nl.tudelft.queue.model.enums.QueueSessionType).values()}"
href="#" th:href="@{/edition/{id}/lab/create(id=${edition.id}, type=${lType.name()})}"
th:text="|Create ${lType.displayName} Lab|"
class="btn btn-primary mr-1">
</a>
</div>
<button type="button" class="btn btn-primary float-right" data-toggle="modal" data-target="#create-modal" th:if="${@permissionService.canManageSessions(edition.id)}">
<span>Create session</span>
</button>
<h3>Labs</h3>
</div>
......@@ -152,6 +148,37 @@
</th:block>
</ul>
</th:block>
<div class="modal" id="create-modal" tabindex="-1" role="dialog" aria-hidden="true" th:if="${@permissionService.canManageSessions(edition.id)}">
<div class="modal-dialog modal-dialog-centered">
<form class="modal-content" method="get" th:action="@{/edition/{id}/lab/create(id=${edition.id})}">
<div class="modal-header">
<h3 class="modal-title">Create session</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="create-session-form" class="form">
<div class="form-group">
<label class="form-control-label" for="session-type">Session type</label>
<select class="form-control selectpicker" id="session-type" name="type">
<option th:each="lType : ${T(nl.tudelft.queue.model.enums.QueueSessionType).values()}"
th:value="${lType.name()}"
th:text="${lType.displayName}">
</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">
Create
</button>
</div>
</form>
</div>
</div>
</section>
</body>
</html>
......@@ -93,19 +93,19 @@
<thead class="thead-light">
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Description</th>
<th style="padding-left: .75rem;" scope="col">Name</th>
<th style="padding-left: .75rem;" scope="col">Description</th>
<th scope="col"></th>
</tr>
</thead>
<tr th:each="assignment, aIdx : ${m.getAssignments()}">
<th scope="row" th:text="${assignment.id}"></th>
<td th:text="${assignment.id}"></td>
<td th:text="${assignment.name}"></td>
<td th:text="${assignment.description.text}"></td>
<td>
<a th:href="@{/assignment/{aId}/remove(aId=${assignment.id})}"
th:if="${@permissionService.canManageModule(m.id)}"
class="btn btn-danger float-right to-be-implemented">
class="btn btn-danger float-right px-2 py-1" style="font-size: .8rem;">
<i class="fa fa-trash"></i>
</a>
</td>
......@@ -125,14 +125,14 @@
<thead class="thead-light">
<tr>
<th scope="col" class="fit-width">#</th>
<th scope="col">Name</th>
<th scope="col">Members</th>
<th style="padding-left: .75rem;" scope="col">Name</th>
<th style="padding-left: .75rem;" scope="col">Members</th>
<th scope="col" class="fit">Capacity</th>
</tr>
</thead>
<tr th:each="group, gIdx : ${groups.get(m.id)}"
th:with="students = ${@roleDTOService.studentsL1(group.members)}">
<th class="fit-width" scope="row" th:text="${group.id}"></th>
<td class="fit-width" th:text="${group.id}"></td>
<td th:text="${group.name}"></td>
<td th:text="${#strings.listJoin(@roleDTOService.names(students), ', ')}"></td>
<td class="fit">
......
......@@ -56,6 +56,7 @@
<section layout:fragment="slot-config"></section>
<section layout:fragment="exam-config"></section>
<section layout:fragment="capacity-config"></section>
<section layout:fragment="extra-config"></section>
<section layout:fragment="advanced-config"></section>
......
......@@ -40,7 +40,7 @@
<th:block th:object="${dto}">
<section layout:fragment="capacity-config">
<h4>Limited Capacity Session</h4>
<h4 class="mt-5">Limited Capacity Session</h4>
<hr/>
<div class="form-group form-row">
......
......@@ -54,7 +54,7 @@
<div id="experimental-tab" class="tab-pane fade" role="tabpanel">
<div class="form-group form-row">
<div class="custom-control custom-switch col-sm-5 align-self-center">
<div class="custom-control custom-switch col-sm-5 align-self-center" style="margin-left: 2.25rem;">
<input id="experimental-toggle" class="custom-control-input" type="checkbox" th:field="*{enableExperimental}"/>
<label class="custom-control-label" for="experimental-toggle">
Enable experimental features
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment