Skip to content
Snippets Groups Projects
Commit 26226430 authored by Ruben Backx's avatar Ruben Backx :coffee:
Browse files

Merge branch 'development' into 'master'

Deploy

See merge request !797
parents d236f7ae c155cf63
Branches
Tags
2 merge requests!828Draft: UI component for allowed requests for students with multiple roles,!797Deploy
Showing
with 327 additions and 107 deletions
......@@ -61,6 +61,7 @@ stages:
paths:
# Only cache the gradle directory, as we do not use a shared cache
- .gradle/
- build/
policy: pull
.gitlab_reporter:
......@@ -83,11 +84,6 @@ gradle_build:
$CI_PIPELINE_SOURCE == "trigger"
cache:
policy: pull-push
artifacts:
name: build
expire_in: 6 hours
paths:
- build/
before_script:
- apt-get update && apt-get install -y sassc
script:
......@@ -150,6 +146,8 @@ mysql_migration:
services:
- mysql:8.0.34-debian
before_script:
- apt-get update && apt-get install -y sassc
- gradle assemble
- cp build/libs/queue-*.jar build/libs/queue.jar
script:
# Confirm that the migrations are syntactically valid for mysql
......@@ -196,6 +194,8 @@ postgreSQL_migration:
services:
- postgres:latest
before_script:
- apt-get update && apt-get install -y sassc
- gradle assemble
- cp build/libs/queue-*.jar build/libs/queue.jar
script:
# Confirm that the migrations are syntactically valid for postgresql
......@@ -342,7 +342,6 @@ gradle_spotless:
after_script:
- cp -r build/spotless-diagnose-java spotless-diagnose-java/
# Publish the JAR for Queue
publish_jar:
extends: .build_cached
......@@ -358,13 +357,14 @@ publish_jar:
artifacts:
name: queue
expose_as: Queue JAR
expire_in: 7 days
expire_in: 2 hours
paths:
- queue.jar
script:
- apt-get update && apt-get install -y sassc
- gradle assemble
- cp build/libs/queue-*.jar ./queue.jar
# Runs the code quality reporter
code_quality:
rules:
......
......@@ -12,15 +12,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- A graph which details the breakdown of requests over time was added to the lab statistics page. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Enqueueing for a slot can now be restricted after a slot starts. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Allow feedback to be flagged as inappropriate
- Greet users with our code of conduct once every academic year. [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
- Allow feedback to be flagged as inappropriate. [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
- Allow users to report unnapropariate behavior. [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
- A revoke button on the request info page which allows course staff to revoke requests. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
### Changed
- Creating a request for a lab that has an online mode gives the option to select the online mode or the in-person mode. [@mmadara](https://gitlab.ewi.tudelft.nl/mmadara)
- Feedback with deleted requests can now be seen by TAs. Course managers can not see this.
- Deleted feedback can now be seen by admins.
### Fixed
- Feedbacks are now soft deleted correctly. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Teachers that are the only teacher for an edition now can't leave the edition. [@aturgut](https://gitlab.ewi.tudelft.nl/aturgut)
- Deleted information now does not stop certain pages from rendering. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Claim request button now can't be seen when the request is already claimed. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Assistant stats on edition status page no longer stops working if there are deleted requests. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- CTRL + click on a request in the lab view now opens the request in a new tab. [@mmadara](https://gitlab.ewi.tudelft.nl/mmadara)
- Assistant stats on edition status page no longer stops working if there are deleted requests. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Creating a new edition (collection) does not allow for empty title. [@mmadara](https://gitlab.ewi.tudelft.nl/mmadara)
## [2.3.1]
### Added
......@@ -44,6 +54,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- A bar-graph detailing the assignment breakdown between questions and submissions was added in the lab statistics page. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Use ChihuahUI implementation for markdown editors. [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
- Slotted requests show up in request table without needing to refresh the page [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
- Use ChihuahUI implementation for markdown editors. [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
- Enqueueing for a slot can now be restricted after a slot starts. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Allow feedback to be flagged as inappropriate
### Changed
- Written feedback sorting now follows a reverse chronological order. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
......@@ -52,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Assistants who couldn't locate students are no longer eligible to receive feedback from those students. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Enhanced room map overview for better clarity on room map availability. Additionally, changed the ordering of rooms to be lexographical [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Ctrl-click in request table now opens new tab with request. [@cedricwilleken](https://gitlab.ewi.tudelft.nl/cedricwilleken)
- Creating a request for a lab that has an online mode gives the option to select the online mode or the in-person mode. [@mmadara](https://gitlab.ewi.tudelft.nl/mmadara)
### Fixed
- Custom slides are now rendered correctly with markdown in a session presentation. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
......@@ -69,6 +83,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Limited capacity sessions can now have extra information. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Limited capacity sessions were not shown on the home page
- Uploading a new room map will correctly replace the old one. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Minor hotfixes.
- fix dialog; this caused several pages to not render correctly, including the lab page for enqueueing. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx)
- fix lab statistics page. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx)
- fix feedback star rating. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx)
- Feedbacks are now soft deleted correctly. [@hpage](https://gitlab.ewi.tudelft.nl/hpage)
- Teachers that are the only teacher for an edition now can't leave the edition. [@aturgut](https://gitlab.ewi.tudelft.nl/aturgut)
- Creating a new edition (collection) does not allow for empty title. [@mmadara](https://gitlab.ewi.tudelft.nl/mmadara)
## [2.2.2]
......
......@@ -6,13 +6,13 @@ import org.springframework.boot.gradle.tasks.run.BootRun
import java.nio.file.Files
group = "nl.tudelft.ewi.queue"
version = "2.3.1"
version = "2.3.0"
val javaVersion = JavaVersion.VERSION_17
val libradorVersion = "1.3.0"
val labradoorVersion = "1.4.1"
val chihuahUIVersion = "1.1.0"
val chihuahUIVersion = "1.2.2"
val queryDslVersion = "5.0.0"
val csvVersion = "5.8"
val guavaVersion = "32.1.3-jre"
......@@ -391,7 +391,7 @@ dependencies {
implementation("org.webjars:handlebars:4.7.7")
implementation("org.webjars:tempusdominus-bootstrap-4:5.1.2")
implementation("org.webjars:momentjs:2.24.0")
implementation("org.webjars:fullcalendar:5.11.3")
implementation("org.webjars:fullcalendar:6.1.9")
implementation("org.webjars:codemirror:5.62.2")
implementation("org.webjars.npm:chartjs-adapter-date-fns:3.0.0")
// Library for converting markdown to html
......
......@@ -10,8 +10,8 @@ referencePassword=
# If classpath isn't found just use --classpath= in the command (check CONTRIBUTING.MD for HASH and VERSION)
# Alternatively, just download the H2 JAR from Maven Central and put the path to it here.
classpath=$HOME/.gradle/caches/modules-2/files-2.1/com.h2database/h2/VERSION/HASH/h2-VERSION.jar
# URL for the old DB. On Queue this is typically ./testdb-old
# URL for the old DB. On Queue this is typically jdbc:h2:file:./testdb-old
url=jdbc:h2:mem:devdb
# URL for the new DB. On Queue this is typically ./testdb-new
# URL for the new DB. On Queue this is typically jdbc:h2:file:./testdb-new
referenceUrl=jdbc:h2:./devdb1
liquibase.hub.mode=off
......@@ -23,7 +23,6 @@ import static nl.tudelft.queue.service.LabService.SessionType.REGULAR;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
......@@ -43,9 +42,6 @@ import nl.tudelft.labracore.api.ModuleControllerApi;
import nl.tudelft.labracore.api.RoomControllerApi;
import nl.tudelft.labracore.api.dto.*;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.queue.dto.create.embeddables.ExamLabConfigCreateDTO;
import nl.tudelft.queue.dto.create.embeddables.SlottedLabConfigCreateDTO;
import nl.tudelft.queue.dto.create.labs.ExamLabCreateDTO;
import nl.tudelft.queue.dto.create.labs.RegularLabCreateDTO;
import nl.tudelft.queue.dto.patch.FeedbackPatchDTO;
import nl.tudelft.queue.model.Feedback;
......@@ -138,7 +134,6 @@ public class DevDatabaseLoader implements InitializingBean {
private Lab oopOldL0;
private Lab oopL1;
private Lab oopL2;
private Lab oopE;
private Lab adsL1I;
private Lab adsL1A;
......@@ -245,24 +240,6 @@ public class DevDatabaseLoader implements InitializingBean {
LocalDateTime.now().plusWeeks(1).plusMinutes(225)))
.build(), oopNow.getId(), REGULAR).get(0);
oopE = ls.createSessions(ExamLabCreateDTO.builder()
.name("Exam")
.eolGracePeriod(15)
.slottedLabConfig(SlottedLabConfigCreateDTO.builder()
.capacity(3)
.duration(15L)
.disableLateEnrollment(false)
.selectionOpensAt(LocalDateTime.now().minusMinutes(5L)).build())
.examLabConfig(ExamLabConfigCreateDTO.builder()
.percentage(15).build())
.modules(Set.of(oopNowModuleA.getId()))
.requestTypes(Map.of(oopNowModuleA.getAssignments().get(0).getId(), Set.of(SUBMISSION)))
.rooms(Set.of(rooms.get(0).getId()))
.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
.slot(new Slot(LocalDateTime.now(),
LocalDateTime.now().plusMinutes(225)))
.build(), oopNow.getId(), REGULAR).get(0);
adsL1I = ls.createSessions(RegularLabCreateDTO.builder()
.name("ADS Lab 1")
.eolGracePeriod(15)
......
......@@ -18,7 +18,7 @@
package nl.tudelft.queue.controller;
import static java.time.LocalDateTime.now;
import static nl.tudelft.queue.PageUtil.toPage;
import static nl.tudelft.librador.Util.PageUtil.toPage;
import java.io.IOException;
import java.util.Comparator;
......@@ -54,8 +54,8 @@ import nl.tudelft.labracore.api.dto.*;
import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.DefaultRole;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.librador.Util.PageUtil;
import nl.tudelft.librador.dto.view.View;
import nl.tudelft.queue.PageUtil;
import nl.tudelft.queue.cache.*;
import nl.tudelft.queue.csv.EmptyCsvException;
import nl.tudelft.queue.csv.InvalidCsvException;
......@@ -365,8 +365,8 @@ public class EditionController {
var edition = eCache.getRequired(editionId);
// Make sure that session details are cached.
var sessions = sCache.getAndHandleAll(edition.getSessions().stream()
.map(SessionSummaryDTO::getId), ls.deleteSessionsByIds());
var sessions = sCache.getAndHandle(edition.getSessions().stream()
.map(SessionSummaryDTO::getId), id -> ls.deleteSessionById(id));
// Find all labs from the sessions, convert to summaries, and filter.
var labs = es.filterLabs(
......@@ -670,8 +670,8 @@ public class EditionController {
public String status(@PathVariable Long editionId,
Model model) {
var edition = eCache.getRequired(editionId);
var sessions = sCache.getAndHandleAll(edition.getSessions().stream().map(SessionSummaryDTO::getId),
ls.deleteSessionsByIds());
var sessions = sCache.getAndHandle(edition.getSessions().stream().map(SessionSummaryDTO::getId),
id -> ls.deleteSessionById(id));
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
......
......@@ -19,6 +19,7 @@ package nl.tudelft.queue.controller;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.AccessDeniedException;
......@@ -27,27 +28,38 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.librador.Util.PageUtil;
import nl.tudelft.librador.dto.view.View;
import nl.tudelft.queue.PageUtil;
import nl.tudelft.queue.cache.PersonCacheManager;
import nl.tudelft.queue.dto.view.FeedbackViewDTO;
import nl.tudelft.queue.dto.view.statistics.AssistantRatingViewDto;
import nl.tudelft.queue.model.Feedback;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.repository.FeedbackRepository;
import nl.tudelft.queue.repository.LabRequestRepository;
import nl.tudelft.queue.service.EditionStatusService;
import nl.tudelft.queue.service.FeedbackService;
import nl.tudelft.queue.service.PermissionService;
@Controller
@RequestMapping("/feedback")
@AllArgsConstructor
@RequiredArgsConstructor
public class FeedbackController {
private FeedbackService fs;
private final LabRequestRepository labRequestRepository;
private PersonCacheManager pCache;
private final FeedbackService fs;
private FeedbackRepository fr;
private final PersonCacheManager pCache;
private final FeedbackRepository fr;
private final PermissionService ps;
private final EditionStatusService ess;
/**
* Maps the own feedback url to a page. The Feedback page displays feedback for the currently
......@@ -96,6 +108,7 @@ public class FeedbackController {
}
@PostMapping("/report")
@PreAuthorize("@permissionService.canViewFeedback(#assistant)")
public String reportFeedback(@RequestParam Long request, @RequestParam Long assistant,
@RequestParam String callback) {
fs.reportFeedback(new Feedback.Id(request, assistant));
......@@ -121,24 +134,47 @@ public class FeedbackController {
Boolean restrictToCourseManager) {
var assistant = pCache.getRequired(assistantId);
// Use this check instead of permissionService methods
// If someone else is viewing, it's guaranteed to be staff because of previous checks.
boolean viewingOwnFeedback = assistantId.equals(person.getId());
// Anonymise feedback if viewing own feedback
List<Feedback> feedback = assistantId.equals(person.getId())
List<Feedback> feedback = viewingOwnFeedback
? fr.findByAssistantAnonymised(assistantId)
: fr.findByAssistant(assistantId);
// Textual feedback filtered on course manager, stars are not filtered
List<Feedback> textualFeedback = feedback.stream()
.filter(f -> f.getFeedback() != null && !f.getFeedback().isBlank())
.toList();
if (restrictToCourseManager) {
List<Feedback> textualFeedback = fs.filterTextualFeedback(feedback);
// We should never filter for admins.
if (restrictToCourseManager && !ps.isAdmin()) {
textualFeedback = fs.filterFeedbackForManagerCourses(textualFeedback);
}
// admin check needs to be after restrict to course manager, otherwise might raise exception due to deleted feedback.
if (ps.isAdmin()) {
List<Feedback> deletedFeedback = fr.findByAssistantDeleted(assistantId);
feedback = Stream.concat(feedback.stream(), deletedFeedback.stream()).toList();
textualFeedback = Stream
.concat(textualFeedback.stream(), fs.filterTextualFeedback(deletedFeedback).stream())
.toList();
}
model.addAttribute("assistant", assistant);
model.addAttribute("feedback",
PageUtil.toPage(pageable, textualFeedback,
Comparator.comparing(Feedback::getLastUpdatedAt).reversed())
.map(fb -> View.convert(fb, FeedbackViewDTO.class)));
model.addAttribute("stars", fs.countRatings(feedback));
model.addAttribute("stars",
fs.countRatings(feedback));
if (!viewingOwnFeedback) {
List<LabRequest> requestsAssignedToPerson = labRequestRepository.findAllByAssistant(assistantId);
model.addAttribute("assistantRating", new AssistantRatingViewDto(
assistant,
(long) requestsAssignedToPerson.size(),
fs.getAvgStarRating(assistantId, requestsAssignedToPerson)));
}
}
}
......@@ -156,7 +156,7 @@ public class HistoryController {
.flatMap(e -> e.getSessions().stream().map(SessionSummaryDTO::getId))
.collect(Collectors.toList());
sCache.getAndHandleAll(sessionIds, ls.deleteSessionsByIds());
sCache.getAndHandle(sessionIds, id -> ls.deleteSessionById(id));
var labs = qsr.findAllBySessions(sessionIds);
......
......@@ -44,24 +44,36 @@ import nl.tudelft.queue.cache.PersonCacheManager;
import nl.tudelft.queue.cache.SessionCacheManager;
import nl.tudelft.queue.dto.view.QueueEditionDetailsDTO;
import nl.tudelft.queue.dto.view.QueueSessionSummaryDTO;
import nl.tudelft.queue.repository.FeedbackRepository;
import nl.tudelft.queue.repository.LabRepository;
import nl.tudelft.queue.repository.LabRequestRepository;
import nl.tudelft.queue.repository.QueueSessionRepository;
import nl.tudelft.queue.service.EditionService;
import nl.tudelft.queue.service.LabService;
import nl.tudelft.queue.service.PermissionService;
import nl.tudelft.queue.service.*;
@Controller
public class HomeController {
@Autowired
private FeedbackRepository fr;
@Autowired
private QueueSessionRepository qsr;
@Autowired
private LabRepository lr;
@Autowired
private LabRequestRepository lrr;
@Autowired
private EditionStatusService ess;
@Autowired
private LabService ls;
@Autowired
private FeedbackService fs;
@Autowired
private EditionControllerApi eApi;
......@@ -166,10 +178,10 @@ public class HomeController {
.collect(Collectors.toMap(EditionDetailsDTO::getId,
e -> es.queueEditionDTO(e, QueueEditionDetailsDTO.class)));
Set<SessionDetailsDTO> sessions = new HashSet<>(sCache.getAndHandleAll(editions.values().stream()
Set<SessionDetailsDTO> sessions = new HashSet<>(sCache.getAndHandle(editions.values().stream()
.flatMap(e -> e.getSessions().stream())
.filter(s -> s.getEndTime().isAfter(now.minusMonths(1)))
.map(SessionSummaryDTO::getId), missingIds -> ls.deleteSessionsByIds()));
.map(SessionSummaryDTO::getId), id -> ls.deleteSessionById(id)));
var sharedEditions = editions.values().stream().map(EditionDetailsDTO::getEditionCollections)
.flatMap(List::stream).map(e -> ecCache.getRequired(e.getId()))
......
......@@ -424,25 +424,6 @@ public class LabController {
return "redirect:/lab/" + qSession.getId();
}
/**
* Performs the 'revoke request' command and redirects the user back to the lab overview page.
*
* @param student The student that is currently authenticated and is requesting a revoke.
* @param qSession The lab the student is revoking their request in.
* @return A redirect to the lab overview page.
*/
@Transactional
@PostMapping("/lab/{qSession}/revoke")
@PreAuthorize("@permissionService.canRevokeFromSession(#qSession.id)")
public String revokeRequest(@AuthenticatedPerson Person student,
@PathEntity QueueSession<?> qSession) {
qSession.getOpenRequestForPerson(student.getId())
.filter(Request::isRevokable)
.ifPresent(rs::revokeRequest);
return "redirect:/lab/" + qSession.getId();
}
/**
* Gets the lab creation page. This page should be viewable by all teachers and managers (those that are
* allowed to create labs). This page allows for editing a lab before final creation of that lab.
......@@ -609,8 +590,8 @@ public class LabController {
@PostMapping("/lab/{qSession}/remove")
@PreAuthorize("@permissionService.canManageSession(#qSession)")
public String deleteSession(@PathEntity QueueSession<?> qSession) {
var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSession(qSession));
ls.deleteSession(qSession);
var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSessionById(id));
ls.deleteSessionById(qSession.getId());
if (session.getEditionCollection() != null) {
return "redirect:/shared-edition/" + session.getEditionCollection().getId();
......@@ -631,7 +612,7 @@ public class LabController {
@PreAuthorize("@permissionService.canManageSession(#qSession)")
public String getLabCopyView(@PathEntity QueueSession<?> qSession,
Model model) {
var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSession(qSession));
var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSessionById(id));
model.addAttribute("dto", qSession.copyLabCreateDTO(session));
model.addAttribute("lType", qSession.getType());
......@@ -771,7 +752,8 @@ public class LabController {
*/
@Transactional
public void setEnqueuePageAttributes(QueueSession<?> qSession, Model model, Person person) {
var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSession(qSession));
var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSessionById(id));
ls.syncQueueSessionWithCore(qSession);
List<RoomDetailsDTO> rooms = session.getRooms().stream()
.sorted(Comparator.comparing(r -> r.getBuilding().getName() + r.getName())).toList();
......@@ -896,7 +878,7 @@ public class LabController {
* @param model The model to fill using lab information.
*/
private void setSessionEditingPageAttributes(QueueSession<?> qSession, Model model) {
var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSession(qSession));
var session = sCache.getRequired(qSession.getSession(), id -> ls.deleteSessionById(id));
model.addAttribute("lSession", session);
if (session.getEdition() != null) {
......@@ -923,7 +905,7 @@ public class LabController {
if (matcher.matches() && matcher.groupCount() == 1) {
Optional<QueueSession<?>> lab = qsRepository.findById(Long.parseLong(matcher.group(1)));
var session = sCache.getRequired(lab.orElseThrow().getSession(),
id -> ls.deleteSession(lab.get()));
id -> ls.deleteSessionById(id));
var edition = session.getEdition().getId();
return "redirect:/edition/" + edition + "/enrol";
}
......
......@@ -26,7 +26,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.queue.dto.create.CourseRequestCreateDTO;
import nl.tudelft.queue.service.MailService;
import nl.tudelft.queue.service.mail.MailService;
@Controller
@ConditionalOnProperty(name = "queue.mail.enabled", havingValue = "true")
......
......@@ -17,15 +17,22 @@
*/
package nl.tudelft.queue.controller;
import java.time.LocalDateTime;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.AllArgsConstructor;
import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.queue.dto.patch.ProfilePatchDTO;
import nl.tudelft.queue.dto.view.CodeOfConductDTO;
import nl.tudelft.queue.repository.ProfileRepository;
import nl.tudelft.queue.service.CodeOfConductService;
@AllArgsConstructor
@RequestMapping("profile")
......@@ -34,6 +41,8 @@ public class ProfileController {
private ProfileRepository profileRepository;
private CodeOfConductService codeOfConductService;
/**
* Updates the user's profile.
*
......@@ -49,4 +58,33 @@ public class ProfileController {
return ResponseEntity.ok().build();
}
/**
* Get the code of conduct information for the specific person. This allows the front-end to check the
* last time the code of conduct was read and display it if needed.
*
* @param person The person fow whom to get the Code of Conduct information.
* @return
* @throws JsonProcessingException
*/
@GetMapping(value = "code-of-conduct", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<String> doesUserNeedToReadCoc(@AuthenticatedPerson Person person)
throws JsonProcessingException {
CodeOfConductDTO dto = codeOfConductService.getCodeOfConduct(person);
return ResponseEntity.ok(dto.toJsonValue());
}
/**
* Update the latest read variable for a person regarding their code of conduct to the current date/time.
* This allows for keeping track on when the user last read the code of conduct.
*
* @param person The person for which to update
* @return
*/
@PostMapping(value = "code-of-conduct/update")
@ResponseBody
public ResponseEntity<Void> updateLatestReadCoC(@AuthenticatedPerson Person person) {
codeOfConductService.updateLatestCoC(person, LocalDateTime.now());
return ResponseEntity.ok().build();
}
}
/*
* Queue - A Queueing system that can be used to handle labs in higher education
* Copyright (C) 2016-2024 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.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import lombok.AllArgsConstructor;
import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.queue.dto.create.reporting.AcademicCounsellorReportDTO;
import nl.tudelft.queue.dto.create.reporting.ConfidentialAdvisorsReportDTO;
import nl.tudelft.queue.service.ReportService;
@Controller
@RequestMapping("/report")
@AllArgsConstructor
public class ReportController {
private final ReportService reportService;
@GetMapping("/{id}")
public String getReportPage(@PathVariable Long id,
@AuthenticatedPerson Person person, Model model) {
reportService.getReportDetails(id, person, model);
return "report/view";
}
@PostMapping("/submit/academic-counsellor")
public String submitReport(AcademicCounsellorReportDTO dto,
@AuthenticatedPerson Person person, Model model) {
reportService.doReportConfidential(dto, person);
return "redirect:/";
}
@PostMapping("/submit/confidential-advisor")
public String submitReportConfidentialAdvisor(ConfidentialAdvisorsReportDTO dto,
@AuthenticatedPerson Person person,
Model model) {
reportService.doReportConfidential(dto, person);
return "redirect:/";
}
}
......@@ -438,6 +438,31 @@ public class RequestController {
return "redirect:/requests";
}
/**
* * Performs the 'revoke request' command and redirects the user back to the lab overview page, if the
* person who sent * the revoke request is the student themself. If someone else is doing it, it means
* it's a staff member trying to free up a space in a slotted lab, hence we redirect to the request
* itself, just so they can get confirmation.
*
* @param revoker The person who sends in the revoke request. It can be the student themself or a staff
* member.
* @param request The request to revoke.
* @return A redirect based on the person who is requesting the revocation.
*/
@Transactional
@PostMapping("/request/{request}/revoke")
@PreAuthorize("@permissionService.canRevokeRequest(#request)")
public String revokeRequest(@AuthenticatedPerson Person revoker,
@PathEntity Request<?> request) {
rs.revokeRequest(request, revoker.getId());
if (Objects.equals(revoker.getId(), request.getRequester())) {
return "redirect:/lab/" + request.getSession().getId();
} else {
return "redirect:/requests";
}
}
/**
* Allows teachers to choose a specific request to handle which is not yet being processed by a TA to
* allow out of order processing.
......
......@@ -37,6 +37,7 @@ import nl.tudelft.queue.cache.ModuleCacheManager;
import nl.tudelft.queue.dto.view.statistics.session.AssignmentSessionCountViewDto;
import nl.tudelft.queue.dto.view.statistics.session.AssistantSessionStatisticsViewDto;
import nl.tudelft.queue.dto.view.statistics.session.GeneralSessionStatisticsViewDto;
import nl.tudelft.queue.dto.view.statistics.session.RequestDistributionBucketViewDto;
import nl.tudelft.queue.model.embeddables.AllowedRequest;
import nl.tudelft.queue.model.enums.RequestType;
import nl.tudelft.queue.model.labs.Lab;
......@@ -138,4 +139,13 @@ public class SessionStatusController {
return sessionStatusService.countAssignmentFreqs(requests, assignments);
}
@GetMapping("/lab/{qSession}/status/request/distribution")
@PreAuthorize("@permissionService.canManageSession(#qSession)")
public List<RequestDistributionBucketViewDto> requestDistributionOverTime(@PathEntity Lab qSession,
@RequestParam(required = false, defaultValue = "") Set<Long> editions,
@RequestParam(required = false, defaultValue = "15") long nMinutes) {
return sessionStatusService.createRequestDistribution(qSession, editions, nMinutes);
}
}
......@@ -24,13 +24,15 @@ import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.*;
import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.librador.resolver.annotations.PathEntity;
import nl.tudelft.queue.dto.patch.TimeSlotPatchDTO;
import nl.tudelft.queue.model.ClosableTimeSlot;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.model.TimeSlot;
import nl.tudelft.queue.service.RequestService;
import nl.tudelft.queue.service.TimeSlotService;
......@@ -43,6 +45,29 @@ public class TimeSlotController {
@Autowired
private TimeSlotService tss;
/**
* API endpoint for updating timeslots.
*
* @param id The id of the timeslot to update.
* @param patch The updated timeslot data.
*/
@PatchMapping("/time-slot/{timeSlot}")
public @ResponseBody void patchTimeSlot(@PathEntity TimeSlot timeSlot,
@RequestBody TimeSlotPatchDTO patch) {
tss.patchTimeSlot(timeSlot, patch);
}
/**
* API endpoint for removing timeslots.
*
* @param id The id of the timeslot to remove.
*/
@DeleteMapping("/time-slot/{timeSlot}")
@PreAuthorize("@permissionService.canManageTimeSlot()")
public @ResponseBody void deleteTimeSlot(@PathEntity TimeSlot timeSlot) {
tss.deleteTimeSlot(timeSlot);
}
/**
* Closes the given time slot in the given lab if not yet closed. This means no more requests can be
* picked from that time slot other than the requests currently picked from there.
......
/*
* Queue - A Queueing system that can be used to handle labs in higher education
* Copyright (C) 2016-2024 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.dto.create;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import lombok.*;
import nl.tudelft.librador.dto.create.Create;
import nl.tudelft.queue.model.TimeSlot;
import nl.tudelft.queue.model.embeddables.Slot;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class TimeSlotCreateDTO extends Create<TimeSlot> {
@NotNull
private Slot slot;
@Min(0)
private Integer capacity;
public TimeSlotCreateDTO(TimeSlot timeSlot) {
this.slot = timeSlot.getSlot();
this.capacity = timeSlot.getCapacity();
}
@Override
public Class<TimeSlot> clazz() {
return TimeSlot.class;
}
}
......@@ -25,6 +25,7 @@ import nl.tudelft.librador.dto.create.Create;
import nl.tudelft.queue.model.embeddables.ExamLabConfig;
@Data
@Deprecated
@Builder
@NoArgsConstructor
@AllArgsConstructor
......
......@@ -37,20 +37,6 @@ import nl.tudelft.queue.model.embeddables.SlottedLabConfig;
public class SlottedLabConfigCreateDTO extends Create<SlottedLabConfig> {
private static final String TIME_FORMAT = "yyyy-MM-dd'T'HH:mm";
/**
* The time that should be assigned to each of the timeslots. The slot duration is stored in minutes.
*/
@NotNull
@Min(value = 1L, message = "Interval time must be positive")
private Long duration;
/**
* The number of groups that can enrol for one particular time slot before all spots are taken.
*/
@NotNull
@Min(value = 1L, message = "Capacity must be positive")
private Integer capacity;
/**
* The time starting which a slot can be selected and requests can be made for this slot lab.
*/
......@@ -80,8 +66,6 @@ public class SlottedLabConfigCreateDTO extends Create<SlottedLabConfig> {
private Boolean disableLateEnrollment = false;
public SlottedLabConfigCreateDTO(SlottedLabConfig config) {
this.duration = config.getDuration();
this.capacity = config.getCapacity();
this.selectionOpensAt = config.getSelectionOpensAt();
this.consideredFullPercentage = config.getConsideredFullPercentage();
this.previousEmptyAllowedThreshold = config.getPreviousEmptyAllowedThreshold();
......
......@@ -17,6 +17,7 @@
*/
package nl.tudelft.queue.dto.create.labs;
import liquibase.pro.packaged.D;
import lombok.*;
import lombok.experimental.SuperBuilder;
import nl.tudelft.labracore.api.dto.SessionDetailsDTO;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment