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

Hide non-queue editions by default

parent 661b4da4
Branches
Tags
2 merge requests!675Deploy,!665Hide non-queue editions by default
Showing
with 446 additions and 44 deletions
......@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
### Changed
- Course editions not created in Queue will now not be displayed until the teacher 'unhides' them. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx)
### Fixed
......
......@@ -18,7 +18,6 @@
package nl.tudelft.queue.controller;
import static java.time.LocalDateTime.now;
import static nl.tudelft.labracore.lib.LabracoreApiUtil.fromPageable;
import static nl.tudelft.queue.PageUtil.toPage;
import java.io.IOException;
......@@ -29,13 +28,16 @@ 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.*;
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.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;
......@@ -43,17 +45,19 @@ import nl.tudelft.queue.dto.create.CourseRequestCreateDTO;
import nl.tudelft.queue.dto.create.QueueEditionCreateDTO;
import nl.tudelft.queue.dto.create.QueueRoleCreateDTO;
import nl.tudelft.queue.dto.util.EditionFilterDTO;
import nl.tudelft.queue.dto.view.QueueEditionDetailsDTO;
import nl.tudelft.queue.dto.view.QueueSessionSummaryDTO;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.model.QueueEdition;
import nl.tudelft.queue.model.enums.QueueSessionType;
import nl.tudelft.queue.model.labs.Lab;
import nl.tudelft.queue.repository.QueueEditionRepository;
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;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
......@@ -138,6 +142,9 @@ public class EditionController {
@Autowired
private QuestionService qs;
@Autowired
private QueueEditionRepository qer;
@Autowired
@Lazy
private LabService ls;
......@@ -159,16 +166,18 @@ public class EditionController {
PersonDetailsDTO pd = Objects.requireNonNull(pApi.getPersonById(person.getId()).block());
var filter = es.getFilter("/editions");
var editions = eApi
.getEditionsPageActiveOrTaughtBy(person.getId(), fromPageable(pageable), filter.getPrograms(),
filter.getNameSearch())
List<EditionDetailsDTO> lcEditions = eApi.getAllEditionsActiveOrTaughtBy(person.getId()).collectList()
.block();
eCache.register(lcEditions);
erCache.getAndIgnoreMissing(lcEditions.stream().map(EditionDetailsDTO::getId));
eCache.register(editions.getContent());
erCache.getAndIgnoreMissing(editions.getContent().stream().map(EditionDetailsDTO::getId));
var editions = es.queueEditionDTO(lcEditions, QueueEditionDetailsDTO.class);
if (person.getDefaultRole() != DefaultRole.ADMIN) {
editions = editions.stream().filter(e -> !e.getHidden()).toList();
}
var page = PageUtil.toPage(pageable, editions);
model.addAttribute("editions",
new PageImpl<>(editions.getContent(), pageable, editions.getTotalElements()));
model.addAttribute("editions", page);
model.addAttribute("programs", cCache.getAll()
.stream().map(CourseDetailsDTO::getProgram).distinct()
.sorted(Comparator.comparing(ProgramSummaryDTO::getName))
......@@ -257,7 +266,8 @@ public class EditionController {
*/
@GetMapping("/edition/{editionId}")
public String getEditionView(@PathVariable Long editionId, Model model) {
EditionDetailsDTO edition = eCache.getRequired(editionId);
QueueEditionDetailsDTO edition = es.queueEditionDTO(eCache.getRequired(editionId),
QueueEditionDetailsDTO.class);
model.addAttribute("edition", edition);
model.addAttribute("assignments",
......@@ -288,7 +298,7 @@ public class EditionController {
students = es.studentsMatchingFilter(students, studentSearch);
}
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
......@@ -322,7 +332,7 @@ public class EditionController {
sgCache.getAndIgnoreMissing(modules.stream()
.flatMap(m -> m.getGroups().stream().map(StudentGroupSmallSummaryDTO::getId)));
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("modules", modules);
model.addAttribute("assignments",
modules.stream().flatMap(m -> m.getAssignments().stream()).toList());
......@@ -363,7 +373,7 @@ public class EditionController {
// Sort all labs
labs = es.sortLabs(labs);
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("labs", labs);
model.addAttribute("allLabTypes", QueueSessionType.values());
model.addAttribute("queueSessionTypes", queueSessionTypes);
......@@ -389,7 +399,7 @@ public class EditionController {
public String getEditionQuestions(@PathVariable Long editionId, Model model) {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
......@@ -411,7 +421,7 @@ public class EditionController {
public String getAddParticipantPage(@PathVariable Long editionId, Model model) {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
......@@ -439,7 +449,7 @@ public class EditionController {
if (dto.hasErrors()) {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
......@@ -505,7 +515,7 @@ public class EditionController {
Model model) {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
......@@ -558,7 +568,7 @@ public class EditionController {
public String getParticipantLeavePage(@PathVariable Long editionId,
Model model) {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
......@@ -600,7 +610,7 @@ public class EditionController {
var sessions = sCache.getAndHandleAll(edition.getSessions().stream().map(SessionSummaryDTO::getId),
ls.deleteSessionsByIds());
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
......@@ -672,6 +682,7 @@ public class EditionController {
EditionCreateDTO edition = create.apply();
Long id = eApi.addEdition(edition).block();
qer.save(QueueEdition.builder().id(id).hidden(false).build());
return "redirect:/edition/" + id;
}
......@@ -718,7 +729,7 @@ public class EditionController {
Model model) {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
......@@ -764,7 +775,7 @@ public class EditionController {
Model model) {
EditionDetailsDTO edition = eCache.getRequired(editionId);
model.addAttribute("edition", edition);
model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class));
model.addAttribute("assignments",
mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
.stream()
......@@ -842,4 +853,20 @@ public class EditionController {
QueueSessionSummaryDTO.class);
}
/**
* Toggles the visibility of an edition in Queue.
*
* @param editionId The id of the edition
* @return A redirect to the edition page
*/
@Transactional
@PostMapping("/edition/{editionId}/visibility")
@PreAuthorize("@permissionService.canManageEdition(#editionId)")
public String toggleVisibility(@PathVariable Long editionId) {
QueueEdition qEdition = es.getOrCreateQueueEdition(editionId);
qEdition.setHidden(!qEdition.getHidden());
qer.save(qEdition);
return "redirect:/edition/{editionId}";
}
}
......@@ -23,7 +23,6 @@ import static nl.tudelft.labracore.api.dto.RoleEditionDetailsDTO.TypeEnum.TEACHE
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import nl.tudelft.labracore.api.EditionControllerApi;
......@@ -38,6 +37,7 @@ import nl.tudelft.queue.cache.EditionCollectionCacheManager;
import nl.tudelft.queue.cache.PersonCacheManager;
import nl.tudelft.queue.cache.SessionCacheManager;
import nl.tudelft.queue.dto.view.FeedbackViewDTO;
import nl.tudelft.queue.dto.view.QueueEditionDetailsDTO;
import nl.tudelft.queue.dto.view.QueueSessionSummaryDTO;
import nl.tudelft.queue.model.Feedback;
import nl.tudelft.queue.repository.FeedbackRepository;
......@@ -173,7 +173,8 @@ public class HomeController {
var editions = eCache.getAndIgnoreMissing(roles.stream().map(r -> r.getEdition().getId()))
.stream()
.collect(Collectors.toMap(EditionDetailsDTO::getId, Function.identity()));
.collect(Collectors.toMap(EditionDetailsDTO::getId,
e -> es.queueEditionDTO(e, QueueEditionDetailsDTO.class)));
Set<SessionDetailsDTO> sessions = new HashSet<>(sCache.getAndHandleAll(editions.values().stream()
.flatMap(e -> e.getSessions().stream())
......
......@@ -87,6 +87,9 @@ public class LabController {
@Autowired
private LabService ls;
@Autowired
private EditionService es;
@Autowired
private RequestTableService rts;
......
/*
* 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.dto.view;
import javax.validation.constraints.NotNull;
import lombok.*;
import nl.tudelft.labracore.api.dto.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class QueueEditionDetailsDTO extends EditionDetailsDTO implements QueueEditionViewDTO {
@NotNull
private Boolean hidden;
}
/*
* 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.dto.view;
import javax.validation.constraints.NotNull;
import lombok.*;
import nl.tudelft.labracore.api.dto.EditionSummaryDTO;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class QueueEditionSummaryDTO extends EditionSummaryDTO implements QueueEditionViewDTO {
@NotNull
private Boolean hidden;
}
/*
* 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.dto.view;
public interface QueueEditionViewDTO {
Long getId();
void setHidden(Boolean hidden);
}
/*
* 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;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class QueueEdition {
@Id
private Long id;
@NotNull
@Builder.Default
private Boolean hidden = true;
}
/*
* 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.repository;
import nl.tudelft.queue.model.QueueEdition;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
public interface QueueEditionRepository
extends JpaRepository<QueueEdition, Long>, QuerydslPredicateExecutor<QueueEdition> {
}
......@@ -30,6 +30,7 @@ import java.util.zip.ZipOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.transaction.Transactional;
import nl.tudelft.labracore.api.EditionControllerApi;
import nl.tudelft.labracore.api.PersonControllerApi;
......@@ -41,16 +42,20 @@ import nl.tudelft.queue.csv.EmptyCsvException;
import nl.tudelft.queue.csv.InvalidCsvException;
import nl.tudelft.queue.csv.UserCsvHelper;
import nl.tudelft.queue.dto.util.EditionFilterDTO;
import nl.tudelft.queue.dto.view.QueueEditionViewDTO;
import nl.tudelft.queue.dto.view.QueueSessionSummaryDTO;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.model.QueueEdition;
import nl.tudelft.queue.model.QueueSession;
import nl.tudelft.queue.model.embeddables.AllowedRequest;
import nl.tudelft.queue.model.enums.QueueSessionType;
import nl.tudelft.queue.model.enums.RequestType;
import nl.tudelft.queue.model.labs.Lab;
import nl.tudelft.queue.repository.QueueEditionRepository;
import nl.tudelft.queue.repository.QueueSessionRepository;
import org.apache.commons.lang3.StringUtils;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource;
......@@ -61,6 +66,8 @@ import org.springframework.web.multipart.MultipartFile;
@Service
public class EditionService {
private static final ModelMapper mapper = new ModelMapper();
@Autowired
private HttpSession session;
......@@ -76,6 +83,9 @@ public class EditionService {
@Autowired
private EditionControllerApi eca;
@Autowired
private QueueEditionRepository qer;
@Autowired
@Lazy
private LabService ls;
......@@ -86,6 +96,45 @@ public class EditionService {
@Autowired
private AssignmentCacheManager acm;
/**
* Converts any kind of EditionDTO to a QueueEditionDTO.
*
* @param dto The DTO to convert
* @param qClass The class of QueueEditionDTO to convert to
* @return The converted DTO
*/
public <DTO, QDTO extends QueueEditionViewDTO> QDTO queueEditionDTO(DTO dto, Class<QDTO> qClass) {
return queueEditionDTO(List.of(dto), qClass).get(0);
}
/**
* Converts a list of any kind of EditionDTOs to QueueEditionDTOs.
*
* @param dtos The DTOs to convert
* @param qClass The class of QueueEditionDTO to convert to
* @return The converted list of DTOs
*/
public <DTO, QDTO extends QueueEditionViewDTO> List<QDTO> queueEditionDTO(List<DTO> dtos,
Class<QDTO> qClass) {
return dtos.stream().map(dto -> {
QDTO qDto = mapper.map(dto, qClass);
QueueEdition qEdition = getOrCreateQueueEdition(qDto.getId());
qDto.setHidden(qEdition.getHidden());
return qDto;
}).toList();
}
/**
* Gets a queue edition from the database or creates a default one if it does not exist;
*
* @param id The id of the edition
* @return The Queue Edition
*/
@Transactional
public QueueEdition getOrCreateQueueEdition(Long id) {
return qer.findById(id).orElseGet(() -> qer.save(QueueEdition.builder().id(id).build()));
}
/**
* Filters a list of people on their username, display name and student number. If any of the
* aforementioned values match the given search term, the person is included. If not, the person is
......
......@@ -44,6 +44,7 @@ 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.QueueEditionDetailsDTO;
import nl.tudelft.queue.dto.view.requests.LabRequestViewDTO;
import nl.tudelft.queue.dto.view.requests.SelectionRequestViewDTO;
import nl.tudelft.queue.misc.QueueSessionStatus;
......@@ -90,6 +91,9 @@ public class LabService {
@Autowired
private SessionCacheManager sCache;
@Autowired
private EditionService es;
@Autowired
private EditionControllerApi eApi;
......@@ -146,7 +150,10 @@ public class LabService {
*/
public void setOrganizationInModel(SessionDetailsDTO session, Model model) {
model.addAttribute("edition",
(session.getEdition() != null) ? eCache.getRequired(session.getEdition().getId()) : null);
(session.getEdition() != null)
? es.queueEditionDTO(eCache.getRequired(session.getEdition().getId()),
QueueEditionDetailsDTO.class)
: null);
model.addAttribute("ec",
(session.getEditionCollection() != null)
? ecCache.getRequired(session.getEditionCollection().getId())
......
/*
* 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.startup;
import javax.transaction.Transactional;
import nl.tudelft.labracore.api.CourseControllerApi;
import nl.tudelft.labracore.api.EditionControllerApi;
import nl.tudelft.labracore.api.ProgramControllerApi;
import nl.tudelft.labracore.api.dto.*;
import nl.tudelft.queue.model.QueueEdition;
import nl.tudelft.queue.repository.QueueEditionRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
/**
* A service that runs during startup. This startup procedure goes through all editions and unhides them in
* Queue.
*/
@Service
@ConditionalOnExpression("#{'${queue.startup.unhide-all-editions}' == 'true'}")
public class UnhideEditionsService {
private static final Logger logger = LoggerFactory.getLogger(UnhideEditionsService.class);
@Autowired
private QueueEditionRepository qer;
@Autowired
private EditionControllerApi eApi;
@Autowired
private CourseControllerApi cApi;
@Autowired
private ProgramControllerApi pApi;
@Transactional
@EventListener(ApplicationReadyEvent.class)
public void unhideAllEditions() {
// Going by programme > course > edition to not request to much data
for (ProgramSummaryDTO programme : pApi.getAllPrograms().collectList().block()) {
logger.info("Unhiding courses of programme: " + programme.getName());
for (CourseSummaryDTO course : cApi.getAllCoursesByProgram(programme.getId()).collectList()
.block()) {
for (EditionDetailsDTO edition : eApi.getAllEditionsByCourse(course.getId()).collectList()
.block()) {
qer.findById(edition.getId()).ifPresentOrElse(
e -> {
e.setHidden(false);
qer.save(e);
},
() -> qer.save(QueueEdition.builder().id(edition.getId()).hidden(false).build()));
}
}
}
}
}
......@@ -73,6 +73,7 @@ queue:
logo: /img/tudelft-logo.png
startup:
repair-requests: false
unhide-all-editions: false
mail:
enabled: true
support-email: root
......
......@@ -1045,7 +1045,26 @@ databaseChangeLog:
tableName: slotted_lab
columnName: previous_empty_allowed_threshold
columnDataType: INTEGER
# Hide non-queue editions by default
- changeSet:
id: 1684835006964-1
author: ruben (generated)
changes:
- createTable:
columns:
- column:
constraints:
nullable: false
primaryKey: true
primaryKeyName: CONSTRAINT_D
name: id
type: BIGINT
- column:
constraints:
nullable: false
name: hidden
type: BOOLEAN
defaultValueBoolean: true
tableName: queue_edition
......@@ -44,6 +44,21 @@
</nav>
</section>
<th:block th:if="${ec == null && (edition instanceof T(nl.tudelft.queue.dto.view.QueueEditionViewDTO)) && edition.hidden && @permissionService.canManageEdition(edition.id)}">
<div id="edition-hidden-banner" style="cursor: pointer;" class="alert alert-danger mt-md-3" role="alert">
<span>This edition is not visible to students in Queue. Click on this banner to unhide this edition.</span>
</div>
<form id="edition-unhide-form" method="post" th:action="@{/edition/{editionId}/visibility(editionId=${edition.id})}"></form>
<script th:inline="javascript">
document.addEventListener("DOMContentLoaded", function () {
const editionUnhideForm = document.getElementById("edition-unhide-form");
const editionHiddenBanner = document.getElementById("edition-hidden-banner");
editionHiddenBanner.addEventListener("click", function () {
editionUnhideForm.submit();
});
});
</script>
</th:block>
<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>
......
......@@ -125,6 +125,11 @@
<h3>Options</h3>
</div>
<form class="d-inline" th:action="@{/edition/{editionId}/visibility(editionId=${edition.id})}" method="post">
<button class="btn btn-primary">
<span th:if="${edition.hidden}">Unh</span><span th:unless="${edition.hidden}">H</span><span>ide in Queue</span>
</button>
</form>
<a href="#" th:href="@{/edition/{id}/requests/export(id=${edition.id})}" class="btn btn-primary">Export all labs</a>
<a href="#" th:href="@{/edition/{id}/requests/signofflist.csv(id=${edition.id})}"
class="btn btn-secondary">Export latest status of student submission requests</a>
......
......@@ -140,10 +140,14 @@
</div>
<div class="tab-pane fade show active" id="overview" role="tabpanel"
aria-labelledby="overview-tabs">
aria-labelledby="overview-tabs"
th:with="visibleActive = ${activeRoles.?[#this.type.name() == 'TEACHER' or not (#root.editions.get(#this.edition.id).hidden)]},
visibleUpcoming = ${upcomingRoles.?[#this.type.name() == 'TEACHER' or not (#root.editions.get(#this.edition.id).hidden)]},
visibleFinished = ${finishedRoles.?[not (#root.editions.get(#this.edition.id).hidden)]},
visibleArchived = ${archivedRoles.?[not (#root.editions.get(#this.edition.id).hidden)]}">
<div class="boxed-group">
<h3>Active courses you participate in</h3>
<div class="boxed-group-inner" th:if="${#lists.isEmpty(activeRoles)}">
<div class="boxed-group-inner" th:if="${#lists.isEmpty(visibleActive)}">
<span th:if="${user.defaultRole.name() == 'STUDENT'}">
You do not participate in any courses. Why don't you <a th:href="@{/editions}">enrol for your first course</a>?
</span>
......@@ -153,11 +157,13 @@
</div>
<ul class="list-group">
<li class="list-group-item" th:each="role : ${activeRoles}"
th:with="edition = ${editions.get(role.edition.id)}">
<li class="list-group-item" th:each="role : ${visibleActive}"
th:with="edition = ${editions.get(role.edition.id)}"
th:if="${role.type.name() == 'TEACHER'} or not ${editions[role.edition.id].hidden}">
<a th:href="@{/edition/{id}(id=${edition.id})}"
th:text="|${edition.course.name} (${edition.name})|"></a>
<span th:text="${'(' + @roleDTOService.typeDisplayName(role.type.toString()) + ')'}"></span>
<span class="badge badge-pill badge-info text-white" th:if="${edition.hidden}">Hidden</span>
<ul th:unless="${#lists.isEmpty(labs)}">
<th:block th:each="sess : ${edition.sessions}">
<li th:each="lab : ${labs[sess.id]}" th:unless="${lab.isShared}" th:classappend="${lab.slot.open()} ? 'lab-open' : 'lab-closed'">
......@@ -216,24 +222,25 @@
</ul>
</div>
<div class="boxed-group" th:if="${!#lists.isEmpty(upcomingRoles)}">
<div class="boxed-group" th:if="${!#lists.isEmpty(visibleUpcoming)}">
<h3>Upcoming courses</h3>
<ul class="list-group">
<li class="list-group-item" th:each="role : ${upcomingRoles}"
<li class="list-group-item" th:each="role : ${visibleUpcoming}"
th:with="edition = ${editions.get(role.edition.id)}">
<a th:href="@{/edition/{id}(id=${edition.id})}"
th:text="|${edition.course.name} (${edition.name})|"></a>
<span th:text="${'(' + @roleDTOService.typeDisplayName(role.type.toString()) + ')'}"></span>
<span class="badge badge-pill badge-info text-white" th:if="${edition.hidden}">Hidden</span>
</li>
</ul>
</div>
<div class="boxed-group" th:if="${!#lists.isEmpty(finishedRoles)}">
<div class="boxed-group" th:if="${!#lists.isEmpty(visibleFinished)}">
<h3>Finished courses</h3>
<ul class="list-group">
<li class="list-group-item" th:each="role : ${finishedRoles}"
<li class="list-group-item" th:each="role : ${visibleFinished}"
th:with="edition = ${editions.get(role.edition.id)}">
<a th:href="@{/edition/{id}(id=${edition.id})}"
th:text="|${edition.course.name} (${edition.name})|"></a>
......@@ -242,11 +249,11 @@
</ul>
</div>
<div class="boxed-group" th:if="${!#lists.isEmpty(archivedRoles)}">
<div class="boxed-group" th:if="${!#lists.isEmpty(visibleArchived)}">
<h3>Archived courses</h3>
<ul class="list-group">
<th:block th:each="role : ${archivedRoles}" th:with="edition = ${editions.get(role.edition.id)}">
<th:block th:each="role : ${visibleArchived}" th:with="edition = ${editions.get(role.edition.id)}">
<li class="list-group-item">
<a th:href="@{/edition/{id}(id=${edition.id})}"
th:text="|${edition.course.name} (${edition.name})|"></a>
......
......@@ -17,6 +17,7 @@
*/
package nl.tudelft.queue.controller;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
......@@ -33,12 +34,14 @@ import nl.tudelft.labracore.api.EditionControllerApi;
import nl.tudelft.labracore.api.dto.EditionDetailsDTO;
import nl.tudelft.labracore.api.dto.EditionSummaryDTO;
import nl.tudelft.labracore.api.dto.Id;
import nl.tudelft.labracore.api.dto.PageEditionDetailsDTO;
import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
import nl.tudelft.labracore.api.dto.RoleDetailsDTO;
import nl.tudelft.labracore.api.dto.SessionDetailsDTO;
import nl.tudelft.queue.dto.util.EditionFilterDTO;
import nl.tudelft.queue.dto.view.QueueEditionDetailsDTO;
import nl.tudelft.queue.model.labs.Lab;
import nl.tudelft.queue.repository.QueueEditionRepository;
import nl.tudelft.queue.service.EditionService;
import org.hamcrest.core.StringContains;
import org.junit.jupiter.api.BeforeEach;
......@@ -99,6 +102,12 @@ public class EditionControllerTest {
@Autowired
private StudentGroupApiMocker sgApiMocker;
@Autowired
private QueueEditionRepository qer;
@Autowired
private EditionService es;
private EditionDetailsDTO oopNow;
private SessionDetailsDTO session1;
private Lab lab1;
......@@ -128,8 +137,8 @@ public class EditionControllerTest {
@Test
@WithUserDetails("teacher0")
void getEmptyCourseListContainsModelAttributes() throws Exception {
when(eApi.getEditionsPageActiveOrTaughtBy(any(), any(), any(), any()))
.thenReturn(Mono.just(new PageEditionDetailsDTO().content(List.of()).totalElements(0L)));
when(eApi.getAllEditionsActiveOrTaughtBy(any()))
.thenReturn(Flux.just());
when(eApi.getAllEditionsActiveDuringPeriod(any())).thenReturn(Flux.just());
......@@ -156,8 +165,8 @@ public class EditionControllerTest {
@Test
@WithUserDetails("teacher0")
void submitFiltersRemembersFilter() throws Exception {
when(eApi.getEditionsPageActiveOrTaughtBy(any(), any(), any(), any()))
.thenReturn(Mono.just(new PageEditionDetailsDTO().content(List.of()).totalElements(0L)));
when(eApi.getAllEditionsActiveOrTaughtBy(any()))
.thenReturn(Flux.just());
when(eApi.getAllEditionsActiveDuringPeriod(any())).thenReturn(Flux.just());
......@@ -185,8 +194,8 @@ public class EditionControllerTest {
@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.getAllEditionsActiveOrTaughtBy(any()))
.thenReturn(Flux.just());
when(eApi.getAllEditionsActiveDuringPeriod(any())).thenReturn(Flux.just());
......@@ -245,7 +254,8 @@ public class EditionControllerTest {
void everyoneShouldBeAbleToSeeEditionInfo() throws Exception {
mvc.perform(get("/edition/{id}", oopNow.getId()))
.andExpect(status().isOk())
.andExpect(model().attribute("edition", oopNow));
.andExpect(model().attribute("edition",
es.queueEditionDTO(oopNow, QueueEditionDetailsDTO.class)));
}
@Test
......@@ -309,6 +319,17 @@ public class EditionControllerTest {
.andExpect(flash().attributeExists("error"));
}
@Test
@WithUserDetails("admin")
void toggleEdition() throws Exception {
mvc.perform(post("/edition/1/visibility").with(csrf()))
.andExpect(status().is3xxRedirection());
assertThat(qer.findById(1L)).isPresent().hasValueSatisfying(e -> assertThat(e.getHidden()).isFalse());
mvc.perform(post("/edition/1/visibility").with(csrf()))
.andExpect(status().is3xxRedirection());
assertThat(qer.findById(1L)).isPresent().hasValueSatisfying(e -> assertThat(e.getHidden()).isTrue());
}
@ParameterizedTest
@MethodSource(value = "protectedEndpoints")
void testWithoutUserDetailsIsForbidden(MockHttpServletRequestBuilder request) throws Exception {
......@@ -335,6 +356,7 @@ public class EditionControllerTest {
post("/edition/1/participants/1/block"),
get("/edition/1/leave"),
post("/edition/1/leave"),
post("/edition/1/toggle"),
get("/edition/1/status"));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment