diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ea08c3d83053a53b691f37926a66c6d1a4197b5..f1c85aceaa053be2a80d109bc2a272bf2263dbb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,24 +17,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A few general statistics was added in the lab statistics page. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) - 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) -### Changed - -### Fixed - ### Changed - Written feedback sorting now follows a reverse chronological order. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) +- Users will see a dialog instead of a new page when promopted to unenrol from an edition. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) + - Text used to indicate the number of times a lab is rescheduled is now more intuitive. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) +- Assistants who couldn't locate students are no longer eligible to receive feedback from those students. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) ### Fixed - The request history page can now be seen for participants. [@mmadara](https://gitlab.ewi.tudelft.nl/mmadara) - Teachers can now view their own feedback again. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) - The feedback graph for assistants now incorporates feedback from other courses, with written feedback limited to visibility for course managers. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) - +- The button that lets users unenrol from an edition now disabled when appropriate. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) ### Fixed - The assignment count graph on the edition statistic page was fixed. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) - -### Fixed - - Limited capacity sessions were not shown on the home page +- Limited capacity sessions were not shown on the home page [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx) +- 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 ## [2.2.2] diff --git a/src/main/java/nl/tudelft/queue/controller/EditionController.java b/src/main/java/nl/tudelft/queue/controller/EditionController.java index ff7419e2f23fcba383febc96562e228647b3d7a6..f1ac03002b4c7d4be3f40e6f0199cd67de34a3c1 100644 --- a/src/main/java/nl/tudelft/queue/controller/EditionController.java +++ b/src/main/java/nl/tudelft/queue/controller/EditionController.java @@ -611,27 +611,6 @@ public class EditionController { return "redirect:/edition/" + editionId + "/participants"; } - /** - * Gets the page to confirm for a student that they want to unenrol from the given course edition. - * - * @param editionId The id of the edition from which a student wants to unenrol. - * @param model The model to fill out for Thymeleaf template resolution. - * @return The Thymeleaf template to resolve. - */ - @GetMapping("/edition/{editionId}/leave") - @PreAuthorize("@permissionService.canLeaveEdition(#editionId)") - public String getParticipantLeavePage(@PathVariable Long editionId, - Model model) { - EditionDetailsDTO edition = eCache.getRequired(editionId); - model.addAttribute("edition", es.queueEditionDTO(edition, QueueEditionDetailsDTO.class)); - model.addAttribute("assignments", - mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId)) - .stream() - .flatMap(m -> m.getAssignments().stream()).toList()); - - return "edition/view/leave"; - } - /** * Processes a POST request from the user wanting to leave the given edition. Instead of deleting the role * of the user from the database entirely, we opt to block their role and thus disallow them access to the diff --git a/src/main/java/nl/tudelft/queue/model/embeddables/RequestEventInfo.java b/src/main/java/nl/tudelft/queue/model/embeddables/RequestEventInfo.java index 41f54e795ef016e6de7c40a28d6207db0a9aacae..08bff6c5b9c37a1326ebbaa68cf4bf32f0f1da85 100644 --- a/src/main/java/nl/tudelft/queue/model/embeddables/RequestEventInfo.java +++ b/src/main/java/nl/tudelft/queue/model/embeddables/RequestEventInfo.java @@ -21,6 +21,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import javax.persistence.*; @@ -31,6 +32,7 @@ import nl.tudelft.queue.cqsr.Aggregate; import nl.tudelft.queue.model.RequestEvent; import nl.tudelft.queue.model.enums.RequestStatus; import nl.tudelft.queue.model.events.EventWithAssistant; +import nl.tudelft.queue.model.events.StudentNotFoundEvent; @Data @Builder @@ -114,15 +116,22 @@ public class RequestEventInfo extends Aggregate<RequestEventInfo, RequestEvent<? /** * Collects a list of ids of the assistants that are involved with this request. The assistants are given - * in order of most-recently involved to least-recently involved. + * in order of most-recently involved to least-recently involved. It also excludes assistants which could + * not find the student. This is done to ensure that students do not give feedback to a TA who never + * interacted with them. * * @return The list of assistant ids. */ public List<Long> involvedAssistants() { + Set<Long> disqualifiedAssistants = events.stream() + .filter(event -> event instanceof StudentNotFoundEvent) + .map(event -> ((StudentNotFoundEvent) event).getAssistant()).collect(Collectors.toSet()); + return events.stream() .sorted(Comparator.comparing((RequestEvent<?> re) -> re.getTimestamp()).reversed()) .filter(event -> event instanceof EventWithAssistant) .map(event -> ((EventWithAssistant) event).getAssistant()) + .filter(assistant -> !disqualifiedAssistants.contains(assistant)) .distinct().collect(Collectors.toList()); } } diff --git a/src/main/java/nl/tudelft/queue/service/FeedbackService.java b/src/main/java/nl/tudelft/queue/service/FeedbackService.java index 86ce39541c7a785f3aa9ad5061c7fa30f94880f7..59ff45c099f96626f45be186983ede03e649cd84 100644 --- a/src/main/java/nl/tudelft/queue/service/FeedbackService.java +++ b/src/main/java/nl/tudelft/queue/service/FeedbackService.java @@ -64,9 +64,12 @@ public class FeedbackService { private SessionCacheManager sessionCacheManager; /** - * Finds all assistant that are involved in the given request. This list of people is generated for - * students to be able to give feedback on the one TA they want to give feedback on. This list is ordered - * by event occurrence: the last occurred event will see its assistant at the top of the list. + * Finds all assistants that are involved in the given request. This list of people is generated for + * students to be able to give feedback to TAs that helped them. This list is ordered by event occurrence: + * the last occurred event will see its assistant at the top of the list. It also excludes assistants + * which could not find the student. This is done to ensure that students do not give feedback to a TA who + * never interacted with them. + * * * @param requestId The id of the request to find all assistants for. * @return A list of person summaries representing the various assistants helping out with the diff --git a/src/main/java/nl/tudelft/queue/service/RoleDTOService.java b/src/main/java/nl/tudelft/queue/service/RoleDTOService.java index 2be5d8a9f17ab96a13cad6302cf79566370684e4..e3fa8a88effebb8009fc8f66ed324e937ade8251 100644 --- a/src/main/java/nl/tudelft/queue/service/RoleDTOService.java +++ b/src/main/java/nl/tudelft/queue/service/RoleDTOService.java @@ -33,7 +33,6 @@ import nl.tudelft.labracore.api.dto.*; import nl.tudelft.labracore.lib.security.user.Person; import nl.tudelft.queue.cache.EditionCacheManager; import nl.tudelft.queue.cache.EditionRolesCacheManager; -import nl.tudelft.queue.cache.SessionCacheManager; import nl.tudelft.queue.model.QueueSession; @Service @@ -46,8 +45,6 @@ public class RoleDTOService { private EditionCacheManager eCache; - private SessionCacheManager sessionCacheManager; - public List<String> names(List<PersonSummaryDTO> people) { return people.stream().map(PersonSummaryDTO::getDisplayName).collect(Collectors.toList()); } diff --git a/src/main/resources/migrations.yml b/src/main/resources/migrations.yml index 6edc60d777c97cddae90bab142b20529dea475a1..6ae10b57c0de0e7fd2138e6315e10bee5d2e59e2 100644 --- a/src/main/resources/migrations.yml +++ b/src/main/resources/migrations.yml @@ -1233,3 +1233,30 @@ databaseChangeLog: referencedTableName: lab validate: true + # Delete feedbacks where student could not be found + - changeSet: + id: delete-feedbacks-where-student-not-found-mariadb + author: Henry Page + changes: + - sql: + comment: (MariaDB) Sets DELETED_AT on feedback if feedback is present on TA that could not find student + dbms: mariadb + sql: + UPDATE feedback AS f + INNER JOIN request_event AS re ON f.assistant_id = re.assistant AND f.request_id = re.request_id + SET f.deleted_at = re.timestamp WHERE re.dtype = 'StudentNotFoundEvent'; + - changeSet: + id: delete-feedbacks-where-student-not-found-pgsql + author: Henry Page + changes: + - sql: + comment: (PGSQL) Sets DELETED_AT on feedback if feedback is present on TA that could not find student + dbms: postgresql + sql: + UPDATE feedback AS f + SET deleted_at = re.timestamp + FROM request_event AS re + WHERE f.assistant_id = re.assistant AND f.request_id = re.request_id AND re.dtype = 'StudentNotFoundEvent'; + + + diff --git a/src/main/resources/templates/edition/view.html b/src/main/resources/templates/edition/view.html index 6ea362b85c67576106aa70384bc9079378b6f16a..b528a5330607a809ab2023ae96473569e093ba46 100644 --- a/src/main/resources/templates/edition/view.html +++ b/src/main/resources/templates/edition/view.html @@ -76,15 +76,16 @@ <a th:href="@{/edition/{id}/enrol(id=${edition.id})}" th:if="${@permissionService.canEnrolForEdition(edition.id)}" class="button"> Enrol for this edition </a> - <a - th:href="@{/edition/{id}/leave(id=${edition.id})}" + + <button th:if="${@permissionService.canViewEdition(edition.id)}" - th:disabled="not ${@permissionService.canLeaveEdition(edition.id)}" class="button" - data-style="outlined" - data-type="error"> + data-type="error" + th:attrappend="data-style=${@permissionService.canLeaveEdition(edition.id)}?outlined" + th:disabled="not ${@permissionService.canLeaveEdition(edition.id)}" + th:data-dialog="leave-edition-dialog"> Leave edition - </a> + </button> </div> </div> @@ -145,6 +146,26 @@ </button> </div> + <dialog + class="dialog" + id="leave-edition-dialog" + th:if="${ec == null and @permissionService.canViewEdition(edition.id)} and ${@permissionService.canLeaveEdition(edition.id)}"> + <form th:action="@{/edition/{id}/leave(id=${edition.id})}" method="post" class="flex vertical p-7"> + <h3 class="underlined font-500">Leave Edition</h3> + + <p> + Are you sure you want to leave the edition + <strong class="fw-500" th:text="|${edition.course.name} - ${edition.name}|"></strong> + ? + </p> + + <div class="flex space-between"> + <button type="button" data-cancel class="button p-less" data-style="outlined">Cancel</button> + <button type="submit" class="button p-less" data-type="error">Leave Edition</button> + </div> + </form> + </dialog> + <div layout:fragment="subcontent">Sub content page</div> </main> </body> diff --git a/src/main/resources/templates/edition/view/leave.html b/src/main/resources/templates/edition/view/leave.html deleted file mode 100644 index dd24c395e7e6c50ff26e384252d96b1af3efb36e..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/edition/view/leave.html +++ /dev/null @@ -1,47 +0,0 @@ -<!-- - - Queue - A Queueing system that can be used to handle labs in higher education - Copyright (C) 2016-2020 Delft University of Technology - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. - ---> -<!DOCTYPE html> -<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{edition/view}"> - <!--@thymesVar id="edition" type="nl.tudelft.labracore.api.dto.EditionDetailsDTO"--> - - <head> - <title th:text="|Leave - ${edition.course.name} (${edition.name})|"></title> - </head> - - <body> - <section layout:fragment="subcontent"> - <h3 class="font-500 mb-5">Leave edition</h3> - - <p class="mb-5"> - Are you sure you want to leave the edition - <strong th:text="|${edition.course.name} - ${edition.name}|"></strong> - ? - </p> - - <form th:action="@{/edition/{id}/leave(id=${edition.id})}" method="post"> - <button type="submit" class="button" data-type="error">Leave</button> - <span> - or - <a class="link" th:href="@{/edition/{id}(id=${edition.id})}">go back</a> - </span> - </form> - </section> - </body> -</html> diff --git a/src/main/resources/templates/error/400.html b/src/main/resources/templates/error/400.html index 48c387bbe46638244ba04d1d8d95084556001758..2a8e304dba591fbb89f4da0567d80e88e48d0aa4 100644 --- a/src/main/resources/templates/error/400.html +++ b/src/main/resources/templates/error/400.html @@ -18,7 +18,7 @@ --> <!DOCTYPE html> -<html lang="en" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{container}"> +<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{container}"> <head> <title>Queue: Bad Request</title> </head> @@ -30,9 +30,14 @@ <h2 class="font-500">Bad Request</h2> <p> - This is likely a bug. Please report what you did in the Queue Mattermost channel or through e-mail to eip-ewi@tudelft.nl so we can - quickly resolve the issue. + This is likely a bug. Please report what you did in the + <a href="https://mattermost.tudelft.nl/educational-software-support/channels/queue">Queue Mattermost channel</a> + or through e-mail to eip-ewi@tudelft.nl so we can quickly resolve the issue. </p> + <br /> + <header class="contribution-wrapper"> + <th:block th:replace="~{error/contribution :: contribution}"></th:block> + </header> </main> </body> </html> diff --git a/src/main/resources/templates/error/403.html b/src/main/resources/templates/error/403.html index 689501c08063849039e341cf04478f9f5c746405..75547bc3ec4fd8b4c78f303ced6542bfb1a460f3 100644 --- a/src/main/resources/templates/error/403.html +++ b/src/main/resources/templates/error/403.html @@ -29,7 +29,16 @@ <h2 class="font-500 mb-3">Access Denied</h2> - <p>You do not have permission to enter. This incident will be reported.</p> + <p> + You do not have permission to enter. This incident will be reported. If you believe this to be a mistake, please report what you did + in the + <a href="https://mattermost.tudelft.nl/educational-software-support/channels/queue">Queue Mattermost channel</a> + or through e-mail to eip-ewi@tudelft.nl so we can quickly resolve the issue. + </p> + <br /> + <header class="contribution-wrapper"> + <th:block th:replace="~{error/contribution :: contribution}"></th:block> + </header> </main> </body> </html> diff --git a/src/main/resources/templates/error/404.html b/src/main/resources/templates/error/404.html index ab2e0b59a89e6f71602603fe69880438dbaea1b1..2bef9e8506ef9bc807c27f9e7e76a28388bf7f75 100644 --- a/src/main/resources/templates/error/404.html +++ b/src/main/resources/templates/error/404.html @@ -29,6 +29,10 @@ <h2 class="font-500 mb-3">Object not found</h2> <p>The requested object could not be found.</p> + <br /> + <header class="contribution-wrapper"> + <th:block th:replace="~{error/contribution :: contribution}"></th:block> + </header> </main> </body> </html> diff --git a/src/main/resources/templates/error/422.html b/src/main/resources/templates/error/422.html index eef3d33b4c46e25df8b710769cc10066903cbda9..57e3bd80589eb231b9fb2727aedaf0fba4e176c3 100644 --- a/src/main/resources/templates/error/422.html +++ b/src/main/resources/templates/error/422.html @@ -26,7 +26,19 @@ <body> <main layout:fragment="content"> <h1 class="font-800 mb-5">Error</h1> - <h2 class="font-500">Something went wrong during validation of an entity.</h2> + + <h2 class="font-500 mb-3">Something went wrong during validation of an entity.</h2> + + <p> + Double-check that you've provided all the necessary information in the form without any mistakes. If you believe this to be another + issue, please report what you did in the + <a href="https://mattermost.tudelft.nl/educational-software-support/channels/queue">Queue Mattermost channel</a> + or through e-mail to eip-ewi@tudelft.nl so we can quickly resolve the issue. + </p> + <br /> + <header class="contribution-wrapper"> + <th:block th:replace="~{error/contribution :: contribution}"></th:block> + </header> </main> </body> </html> diff --git a/src/main/resources/templates/error/500.html b/src/main/resources/templates/error/500.html index 6ccc52508c118d33118354432e23953f3222dd00..7bae699ece9b8f9850237a631529bfe58a2497f8 100644 --- a/src/main/resources/templates/error/500.html +++ b/src/main/resources/templates/error/500.html @@ -26,11 +26,13 @@ <body> <main layout:fragment="content"> <h1 class="font-800 mb-5">Error</h1> - <p> - Oops. Something went wrong on our end... - <br /> - If this occurs again, contact us with a description of what you did before we show you this screen - </p> + <h2 class="font-500">Oops. Something went wrong on our end...</h2> + <br /> + <p>If this occurs again, contact us with a description of what you did before we show you this screen</p> + <br /> + <header class="contribution-wrapper"> + <th:block th:replace="~{error/contribution :: contribution}"></th:block> + </header> </main> </body> </html> diff --git a/src/main/resources/templates/error/contribution.html b/src/main/resources/templates/error/contribution.html new file mode 100644 index 0000000000000000000000000000000000000000..65548485e8d6d8d9168568d3acd8d5ad55d2c32b --- /dev/null +++ b/src/main/resources/templates/error/contribution.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> + <header class="contribution" th:fragment="contribution"> + <div class="banner mb-5" data-type="info" role="banner"> + <span class="fa-regular fa-lightbulb fa-2x" aria-hidden="true"></span> + <p> + Want to help us resolve this issue? This system is open source and can be found + <a href="https://gitlab.ewi.tudelft.nl/eip/labrador/queue">here</a> + . You can file a bug report there or you can even contribute a fix for the issue yourself! Check the + <a href="https://gitlab.ewi.tudelft.nl/eip/labrador/queue/-/blob/development/CONTRIBUTING.md">contributing file</a> + for more details. + </p> + </div> + </header> +</html> diff --git a/src/main/resources/templates/error/error.html b/src/main/resources/templates/error/error.html index deb7fcfd17182ebc0973470cfe5ece4ac929b154..34070f2323c0a3c14ba2a876ed4e04f61ae50156 100644 --- a/src/main/resources/templates/error/error.html +++ b/src/main/resources/templates/error/error.html @@ -27,6 +27,10 @@ <main layout:fragment="content"> <h1 class="font-800 mb-5">Error</h1> <h2 class="font-500">Oops. Something went wrong...</h2> + <br /> + <header class="contribution-wrapper"> + <th:block th:replace="~{error/contribution :: contribution}"></th:block> + </header> </main> </body> </html> diff --git a/src/main/resources/templates/lab/create/capacity.html b/src/main/resources/templates/lab/create/capacity.html index 84e18b02d0b361d680152356ae95dc74656cb60d..364c85771ecdd5fea4914f2f9827fd20f6a36fc8 100644 --- a/src/main/resources/templates/lab/create/capacity.html +++ b/src/main/resources/templates/lab/create/capacity.html @@ -143,5 +143,9 @@ </script> </section> </th:block> + + <th:block layout:fragment="extra-config"> + <th:block th:replace="~{lab/create/components/extra-info :: extra-info}"></th:block> + </th:block> </body> </html> diff --git a/src/main/resources/templates/lab/create/components/extra-info.html b/src/main/resources/templates/lab/create/components/extra-info.html new file mode 100644 index 0000000000000000000000000000000000000000..aa0c48dc672cb9e838a9a9f0eb2cab9f38e330e5 --- /dev/null +++ b/src/main/resources/templates/lab/create/components/extra-info.html @@ -0,0 +1,26 @@ +<html lang="en" xmlns:th="http://www.thymeleaf.org"> + <!--@thymesVar id="dto" type="nl.tudelft.queue.dto.create.QueueSessionCreateDTO"--> + + <body> + <th:block th:object="${dto}"> + <section th:fragment="extra-info"> + <h3 class="font-500 mb-3"><label for="markdown-editor">Extra info</label></h3> + <div> + <textarea id="markdown-editor" th:field="*{extraInfo}">some extra info!</textarea> + </div> + + <div class="mt-500 underlined"></div> + + <script th:inline="javascript" type="text/javascript"> + let ide = new SimpleMDE({ + element: document.getElementById("markdown-editor"), + forceSync: true, + }); + $("#form").submit(() => { + $("#markdown-editor").textContent = ide.value(); + }); + </script> + </section> + </th:block> + </body> +</html> diff --git a/src/main/resources/templates/lab/create/components/lab-general.html b/src/main/resources/templates/lab/create/components/lab-general.html index 743406d9712e0cc25aea7c01c2f8e0dae19f210d..8943da111d57ee08e3256bf1d0308efcda60de6f 100644 --- a/src/main/resources/templates/lab/create/components/lab-general.html +++ b/src/main/resources/templates/lab/create/components/lab-general.html @@ -276,24 +276,5 @@ </script> </section> </th:block> - - <section th:fragment="lab-extra"> - <h3 class="font-500 mb-3"><label for="markdown-editor">Extra lab info</label></h3> - <div> - <textarea id="markdown-editor" th:field="*{extraInfo}">some extra info!</textarea> - </div> - - <div class="mt-500 underlined"></div> - - <script th:inline="javascript" type="text/javascript"> - let ide = new SimpleMDE({ - element: document.getElementById("markdown-editor"), - forceSync: true, - }); - $("#form").submit(() => { - $("#markdown-editor").textContent = ide.value(); - }); - </script> - </section> </body> </html> diff --git a/src/main/resources/templates/lab/create/regular.html b/src/main/resources/templates/lab/create/regular.html index e478418622c075155cd1627a07a8d4ba6bd6aa90..71121e2c6102e4f818995ffa12ac6417d808ca17 100644 --- a/src/main/resources/templates/lab/create/regular.html +++ b/src/main/resources/templates/lab/create/regular.html @@ -28,7 +28,7 @@ </th:block> <th:block layout:fragment="extra-config"> - <th:block th:replace="~{lab/create/components/lab-general :: lab-extra}"></th:block> + <th:block th:replace="~{lab/create/components/extra-info.html :: extra-info}"></th:block> </th:block> <th:block layout:fragment="advanced-config"> diff --git a/src/main/resources/templates/lab/edit/capacity.html b/src/main/resources/templates/lab/edit/capacity.html index 4b0affd967f7c65bc986b078b2b16e3e79e7f4d0..922a0ee3806478c902f126190c28efcf4055d410 100644 --- a/src/main/resources/templates/lab/edit/capacity.html +++ b/src/main/resources/templates/lab/edit/capacity.html @@ -175,5 +175,9 @@ </script> </section> </th:block> + + <th:block layout:fragment="extra-config"> + <th:block th:replace="~{lab/edit/components/extra-info :: extra-info}"></th:block> + </th:block> </body> </html> diff --git a/src/main/resources/templates/lab/edit/components/extra-info.html b/src/main/resources/templates/lab/edit/components/extra-info.html new file mode 100644 index 0000000000000000000000000000000000000000..11b7d4465dd829fc77a4467239ed490a51c2a1d0 --- /dev/null +++ b/src/main/resources/templates/lab/edit/components/extra-info.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html lang="en" xmlns:th="http://www.thymeleaf.org"> + <!--@thymesVar id="qSession" type="nl.tudelft.queue.dto.patch.QueueSessionPatchDTO"--> + + <body> + <th:block th:object="${qSession}"> + <section th:fragment="extra-info"> + <h3 class="font-500 mb-3"><label for="markdown-editor">Extra info</label></h3> + <div class="article"> + <textarea id="markdown-editor" name="extraInfo" th:text="${qSession.extraInfo}">some extra info!</textarea> + </div> + + <div class="mt-500 underlined"></div> + + <script th:inline="javascript" type="text/javascript"> + let ide = new SimpleMDE({ + element: document.getElementById("markdown-editor"), + forceSync: true, + }); + $("#form").submit(() => { + $("#markdown-editor").textContent = ide.value(); + }); + </script> + </section> + </th:block> + </body> +</html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <title>Title</title> + </head> + <body></body> +</html> diff --git a/src/main/resources/templates/lab/edit/components/lab-general.html b/src/main/resources/templates/lab/edit/components/lab-general.html index 4d39d180290b45ad79e46d4ac2c7d9712397d84d..3a732906320a049fa8cd7f33baf40e2fd033f152 100644 --- a/src/main/resources/templates/lab/edit/components/lab-general.html +++ b/src/main/resources/templates/lab/edit/components/lab-general.html @@ -257,24 +257,5 @@ </script> </section> </th:block> - - <section th:fragment="lab-extra"> - <h3 class="font-500 mb-3"><label for="markdown-editor">Extra lab info</label></h3> - <div class="article"> - <textarea id="markdown-editor" name="extraInfo" th:text="${qSession.extraInfo}">some extra info!</textarea> - </div> - - <div class="mt-500 underlined"></div> - - <script th:inline="javascript" type="text/javascript"> - let ide = new SimpleMDE({ - element: document.getElementById("markdown-editor"), - forceSync: true, - }); - $("#form").submit(() => { - $("#markdown-editor").textContent = ide.value(); - }); - </script> - </section> </body> </html> diff --git a/src/main/resources/templates/lab/edit/regular.html b/src/main/resources/templates/lab/edit/regular.html index d1fbd63cca602be4c2d1be3eb1bf22d1ace73de3..f288bdca167b0a9c96f996f0d6366789bbb4c9b3 100644 --- a/src/main/resources/templates/lab/edit/regular.html +++ b/src/main/resources/templates/lab/edit/regular.html @@ -28,7 +28,7 @@ </th:block> <th:block layout:fragment="extra-config"> - <th:block th:replace="~{lab/edit/components/lab-general :: lab-extra}"></th:block> + <th:block th:replace="~{lab/edit/components/extra-info :: extra-info}"></th:block> </th:block> <th:block layout:fragment="advanced-config"> diff --git a/src/main/resources/templates/lab/edit/slotted.html b/src/main/resources/templates/lab/edit/slotted.html index 3ca1fdc070e1d5e26fbd3da45c71e0de0e8afd9c..99bccd62d79e9e45961f4af3455ec1cb4ae4d198 100644 --- a/src/main/resources/templates/lab/edit/slotted.html +++ b/src/main/resources/templates/lab/edit/slotted.html @@ -44,7 +44,7 @@ </section> <th:block layout:fragment="extra-config"> - <th:block th:replace="~{lab/edit/components/lab-general :: lab-extra}"></th:block> + <th:block th:replace="~{lab/edit/components/extra-info :: extra-info}"></th:block> </th:block> <section layout:fragment="advanced-config"> diff --git a/src/main/resources/templates/lab/view/capacity.html b/src/main/resources/templates/lab/view/capacity.html index e70926bd6561bf9d537ec87ff074300e12ce19ff..9f8900250a3d57c40e8ce75b9acf21cce5dd1bd4 100644 --- a/src/main/resources/templates/lab/view/capacity.html +++ b/src/main/resources/templates/lab/view/capacity.html @@ -25,7 +25,7 @@ </head> <body> - <div class="grid auto-fit" layout:fragment="session-info"> + <div layout:fragment="session-info"> <th:block th:replace="lab/view/components/capacity-session-info :: capacity-lab-info"></th:block> </div> <th:block layout:fragment="request-table"> diff --git a/src/main/resources/templates/lab/view/components/capacity-session-info.html b/src/main/resources/templates/lab/view/components/capacity-session-info.html index 3acb2d7fed1c165d91160363176cf8d957523bab..f4d804ca8d3a99b89be5b73c4bcf1700524381be 100644 --- a/src/main/resources/templates/lab/view/components/capacity-session-info.html +++ b/src/main/resources/templates/lab/view/components/capacity-session-info.html @@ -28,89 +28,98 @@ <body> <th:block th:fragment="capacity-lab-info"> - <div class="surface p-0"> - <h3 class="surface__header">Session info</h3> - - <div class="surface__content"> - <dl> - <dt class="fw-500">Slot</dt> - <dd - th:text="|${#temporals.format(qSession.session.start, 'dd MMMM yyyy HH:mm')} - ${#temporals.format(qSession.session.endTime, 'dd MMMM yyyy HH:mm')}|"></dd> - - <dt class="fw-500 mt-3">Rooms</dt> - <dd th:text="${#strings.listJoin(@roomDTOService.names(rooms), ', ')}"></dd> - - <dt class="fw-500 mt-3">Total capacity</dt> - <dd th:text="${qSession.capacity}"></dd> - - <th:block th:if="${qSession.capacitySessionConfig.procedure.isFcfs()}"> - <dt class="fw-500 mt-3">Currently selected or pending per room</dt> - <dd> - <ul class="list"> - <li - th:each="e : ${qSession.selectedOrPendingCountsPerRoom().entrySet()}" - th:text="|${e.key.building.name}: ${e.key.name} (${e.value}/${e.key.capacity})|"></li> - </ul> - </dd> - </th:block> - - <th:block th:unless="${qSession.capacitySessionConfig.procedure.isFcfs()}" th:with="data = ${qSession.data}"> - <!--@thymesVar id="data" type="nl.tudelft.queue.model.labs.CapacitySession"--> - <th:block th:if="${qSession.isAfterSelection()}"> - <dt class="fw-500 mt-3">Total selected</dt> - <dd th:text="|${#lists.size(data.getSelectedRequests())} / ${#lists.size(data.getProcessedRequests())}|"></dd> - </th:block> - <th:block th:unless="${qSession.isAfterSelection()}"> - <dt class="fw-500 mt-3">Currently enqueued for selection</dt> - <dd th:text="${#lists.size(data.getPendingRequests())}"></dd> - </th:block> - </th:block> - - <dt class="fw-500 mt-3">Modules</dt> - <dd th:if="${#lists.isEmpty(modules)}" class="text-danger">This lab does not have any modules configured</dd> - <dd th:unless="${#lists.isEmpty(modules)}"> - <ul class="list"> - <li th:each="m : ${modules}" th:text="${m.name}"></li> - </ul> - </dd> - - <th:block th:if="${!#lists.isEmpty(qSession.constraints.toList())}"> - <dt class="fw-500 mt-3">Constraints</dt> - <dd> - <ul> - <li - th:each="constraint : ${qSession.constraints.toList()}" - th:text="|${constraint.constraintDescription()} ${constraint.canCreateRequest(#authenticatedP.id) ? '' : '(you do not satisfy this)'}|" - th:classappend="${!constraint.canCreateRequest(#authenticatedP.id)} ? 'text-danger'"></li> - </ul> - </dd> - </th:block> - </dl> + <div class="flex vertical"> + <div class="grid auto-fit"> + <div class="surface p-0"> + <h3 class="surface__header">Session info</h3> + + <div class="surface__content"> + <dl> + <dt class="fw-500">Slot</dt> + <dd + th:text="|${#temporals.format(qSession.session.start, 'dd MMMM yyyy HH:mm')} - ${#temporals.format(qSession.session.endTime, 'dd MMMM yyyy HH:mm')}|"></dd> + + <dt class="fw-500 mt-3">Rooms</dt> + <dd th:text="${#strings.listJoin(@roomDTOService.names(rooms), ', ')}"></dd> + + <dt class="fw-500 mt-3">Total capacity</dt> + <dd th:text="${qSession.capacity}"></dd> + + <th:block th:if="${qSession.capacitySessionConfig.procedure.isFcfs()}"> + <dt class="fw-500 mt-3">Currently selected or pending per room</dt> + <dd> + <ul class="list"> + <li + th:each="e : ${qSession.selectedOrPendingCountsPerRoom().entrySet()}" + th:text="|${e.key.building.name}: ${e.key.name} (${e.value}/${e.key.capacity})|"></li> + </ul> + </dd> + </th:block> + + <th:block th:unless="${qSession.capacitySessionConfig.procedure.isFcfs()}" th:with="data = ${qSession.data}"> + <!--@thymesVar id="data" type="nl.tudelft.queue.model.labs.CapacitySession"--> + <th:block th:if="${qSession.isAfterSelection()}"> + <dt class="fw-500 mt-3">Total selected</dt> + <dd th:text="|${#lists.size(data.getSelectedRequests())} / ${#lists.size(data.getProcessedRequests())}|"></dd> + </th:block> + <th:block th:unless="${qSession.isAfterSelection()}"> + <dt class="fw-500 mt-3">Currently enqueued for selection</dt> + <dd th:text="${#lists.size(data.getPendingRequests())}"></dd> + </th:block> + </th:block> + + <dt class="fw-500 mt-3">Modules</dt> + <dd th:if="${#lists.isEmpty(modules)}" class="text-danger">This lab does not have any modules configured</dd> + <dd th:unless="${#lists.isEmpty(modules)}"> + <ul class="list"> + <li th:each="m : ${modules}" th:text="${m.name}"></li> + </ul> + </dd> + + <th:block th:if="${!#lists.isEmpty(qSession.constraints.toList())}"> + <dt class="fw-500 mt-3">Constraints</dt> + <dd> + <ul> + <li + th:each="constraint : ${qSession.constraints.toList()}" + th:text="|${constraint.constraintDescription()} ${constraint.canCreateRequest(#authenticatedP.id) ? '' : '(you do not satisfy this)'}|" + th:classappend="${!constraint.canCreateRequest(#authenticatedP.id)} ? 'text-danger'"></li> + </ul> + </dd> + </th:block> + </dl> + </div> + </div> + + <div class="surface p-0"> + <h3 class="surface__header">Limited Capacity Info</h3> + <div class="surface__content"> + <dl> + <dt class="fw-500">Enrolment</dt> + <dd> + <th:block + th:text="${#temporals.format(qSession.capacitySessionConfig.enrolmentOpensAt, 'dd MMMM yyyy HH:mm')}"></th:block> + - + <th:block + th:text="${#temporals.format(qSession.capacitySessionConfig.enrolmentClosesAt, 'dd MMMM yyyy HH:mm')}"></th:block> + + <span class="chip" data-type="accept" th:if="${qSession.capacitySessionConfig.enrolmentOpen()}">Open</span> + <span class="chip" data-type="error" th:unless="${qSession.capacitySessionConfig.enrolmentOpen()}">Closed</span> + </dd> + + <dt class="fw-500 mt-3">Selection happens at</dt> + <dd th:text="${#temporals.format(qSession.capacitySessionConfig.selectionAt, 'dd MMMM yyyy HH:mm')}"></dd> + + <dt class="fw-500 mt-3">Selection procedure</dt> + <dd th:text="${qSession.capacitySessionConfig.procedure.displayName}"></dd> + </dl> + </div> + </div> </div> - </div> - <div class="surface p-0"> - <h3 class="surface__header">Limited Capacity Info</h3> - <div class="surface__content"> - <dl> - <dt class="fw-500">Enrolment</dt> - <dd> - <th:block - th:text="${#temporals.format(qSession.capacitySessionConfig.enrolmentOpensAt, 'dd MMMM yyyy HH:mm')}"></th:block> - - - <th:block - th:text="${#temporals.format(qSession.capacitySessionConfig.enrolmentClosesAt, 'dd MMMM yyyy HH:mm')}"></th:block> - - <span class="chip" data-type="accept" th:if="${qSession.capacitySessionConfig.enrolmentOpen()}">Open</span> - <span class="chip" data-type="error" th:unless="${qSession.capacitySessionConfig.enrolmentOpen()}">Closed</span> - </dd> - - <dt class="fw-500 mt-3">Selection happens at</dt> - <dd th:text="${#temporals.format(qSession.capacitySessionConfig.selectionAt, 'dd MMMM yyyy HH:mm')}"></dd> - - <dt class="fw-500 mt-3">Selection procedure</dt> - <dd th:text="${qSession.capacitySessionConfig.procedure.displayName}"></dd> - </dl> + <div class="surface p-0" th:if="${!#strings.isEmpty(qSession.extraInfo)}"> + <h3 class="surface__header">Extra information for this session</h3> + <div class="surface__content" th:utext="${qSession.extraInfo}"></div> </div> </div> </th:block> diff --git a/src/test/java/e2e/EndToEndTest.java b/src/test/java/e2e/EndToEndTest.java index 548f30f3bf584d02db162401fc99b3936f16c963..c3a31b0e8eaae90c117528c2af669ec646ffee2e 100644 --- a/src/test/java/e2e/EndToEndTest.java +++ b/src/test/java/e2e/EndToEndTest.java @@ -35,9 +35,12 @@ public abstract class EndToEndTest { protected BrowserContext context; protected Page page; + protected String basePath; + public EndToEndTest() { this.playwright = Playwright.create(); this.browser = playwright.webkit().launch(); + this.basePath = "http://localhost:8081"; } @BeforeEach @@ -51,13 +54,17 @@ public abstract class EndToEndTest { playwright.close(); } + protected String withPath(String path) { + return basePath + path; + } + /** * Login to the application. * * @param user The username */ protected void login(String user) { - page.navigate("http://localhost:8081/login"); + page.navigate(withPath("/login")); page.getByLabel("Username").click(); page.getByLabel("Username").fill(user); page.getByLabel("Username").press("Tab"); diff --git a/src/test/java/e2e/LabEndToEndTest.java b/src/test/java/e2e/LabEndToEndTest.java index 3861f8f3eafd1acc1575b1612cd141e4960c1258..d3ccc37678bc85624702271ddef60e299ce3814b 100644 --- a/src/test/java/e2e/LabEndToEndTest.java +++ b/src/test/java/e2e/LabEndToEndTest.java @@ -20,12 +20,17 @@ package e2e; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import java.nio.file.Paths; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import com.microsoft.playwright.Page; + +import nl.tudelft.labracore.api.dto.EditionDetailsDTO; import nl.tudelft.queue.model.labs.RegularLab; import test.TestDatabaseLoader; import test.test.TestQueueApplication; @@ -41,16 +46,19 @@ public class LabEndToEndTest extends EndToEndTest { RegularLab rlOopNowSharedLab; + EditionDetailsDTO oopNow; + @BeforeEach void setup() { oopNowRegularLab1 = db.getOopNowRegularLab1(); rlOopNowSharedLab = db.getRlOopNowSharedLab(); + oopNow = db.getOopNow(); } @Test void testStatisticsWorks() { login("cseteacher1"); - page.navigate("http://localhost:8081/lab/" + oopNowRegularLab1.getId() + "/status"); + page.navigate(withPath("/lab/" + oopNowRegularLab1.getId() + "/status")); assertThat(page).hasTitle("Status - Session #" + oopNowRegularLab1.getId()); var table = page.locator("#assistant-frequency-table"); assertThat(table).isVisible(); @@ -58,4 +66,14 @@ public class LabEndToEndTest extends EndToEndTest { assertNotEquals(table.first().all().size(), 0); } + @Test + void capacitySessionCanHaveExtraInfo() { + login("cseteacher1"); + page.navigate(withPath("/edition/2/lab/create?type=CAPACITY")); + page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("build/screenshots/test.png"))); + assertThat(page.getByText("Extra info")).isVisible(); + var sut = page.locator("#markdown-editor"); + assertThat(sut).isEditable(); + } + } diff --git a/src/test/java/nl/tudelft/queue/service/FeedbackServiceTest.java b/src/test/java/nl/tudelft/queue/service/FeedbackServiceTest.java index f376416cdb290bd8144f93949b5c850ec55a4483..282f33ecfa76358857f3f82324b994046c9af513 100644 --- a/src/test/java/nl/tudelft/queue/service/FeedbackServiceTest.java +++ b/src/test/java/nl/tudelft/queue/service/FeedbackServiceTest.java @@ -134,9 +134,6 @@ public class FeedbackServiceTest { new RequestForwardedToAnyEvent(request1, people[0].getId(), "Hey"), new RequestTakenEvent(request1, people[1].getId()), new RequestApprovedEvent(request1, people[1].getId(), ""))); - for (RequestEvent<?> event : request1.getEventInfo().getEvents()) { - rer.applyAndSave(event); - } request2 = rr.save(LabRequest.builder() .room(32L).assignment(32L).studentGroup(32L).requester(32L).comment("Hello") @@ -150,7 +147,26 @@ public class FeedbackServiceTest { new RequestTakenEvent(request2, people[3].getId()), new RequestForwardedToPersonEvent(request2, people[3].getId(), people[4].getId(), ""), new RequestApprovedEvent(request2, people[4].getId(), ""))); - for (RequestEvent<?> event : request2.getEventInfo().getEvents()) { + + request3 = rr.save(LabRequest.builder() + .room(32L).assignment(32L).studentGroup(32L).requester(32L).comment("Hello") + .requestType(RequestType.QUESTION) + .session(lab) + .build()); + + request3.getEventInfo().getEvents().addAll(List.of( + new RequestCreatedEvent(request3), + new RequestTakenEvent(request3, people[0].getId()), + new StudentNotFoundEvent(request3, people[0].getId()), + new RequestTakenEvent(request3, people[1].getId()), + new RequestForwardedToAnyEvent(request3, people[1].getId(), "Hey"), + new RequestTakenEvent(request3, people[2].getId()), + new RequestApprovedEvent(request3, people[2].getId(), "Approved"), + new RequestTakenEvent(request3, people[3].getId()), + new StudentNotFoundEvent(request3, people[3].getId()))); + + for (RequestEvent<?> event : Stream.of(request1, request2, request3) + .flatMap(rq -> rq.getEventInfo().getEvents().stream()).toList()) { rer.applyAndSave(event); } @@ -191,6 +207,13 @@ public class FeedbackServiceTest { .containsExactlyInAnyOrder(people[4], people[3], people[2], people[0]); } + @Test + void requestsDoNotHaveAssistantsInvolvedThatCouldNotFindStudent() { + assertThat(fs.assistantsInvolvedInRequest(request3.getId())) + .containsExactlyElementsOf(List.of(people[2], people[1])); + + } + @Test void countRatingsWorks() { var p0FeedBacks = fr.findAllByAssistant(people[0].getId()); diff --git a/src/test/java/nl/tudelft/queue/service/RoleDTOServiceTest.java b/src/test/java/nl/tudelft/queue/service/RoleDTOServiceTest.java index eb0a1de98f8514515ff6e7386198bb46d42a4bab..4c6a72d481749af0e3e1d283d4eb63f3aaa05e2c 100644 --- a/src/test/java/nl/tudelft/queue/service/RoleDTOServiceTest.java +++ b/src/test/java/nl/tudelft/queue/service/RoleDTOServiceTest.java @@ -34,7 +34,6 @@ import nl.tudelft.labracore.lib.security.user.DefaultRole; import nl.tudelft.labracore.lib.security.user.Person; import nl.tudelft.queue.cache.EditionCacheManager; import nl.tudelft.queue.cache.EditionRolesCacheManager; -import nl.tudelft.queue.cache.SessionCacheManager; import reactor.core.publisher.Flux; public class RoleDTOServiceTest { @@ -43,8 +42,6 @@ public class RoleDTOServiceTest { private EditionCacheManager cacheManager; - private SessionCacheManager sessionCacheManager; - private final ModelMapper mapper = new ModelMapper(); private final RoleControllerApi roleControllerApi; @@ -69,10 +66,8 @@ public class RoleDTOServiceTest { public RoleDTOServiceTest() { this.eCache = mock(EditionRolesCacheManager.class); this.cacheManager = mock(EditionCacheManager.class); - this.sessionCacheManager = mock(SessionCacheManager.class); this.roleControllerApi = mock(RoleControllerApi.class); - this.roleDTOService = new RoleDTOService(eCache, roleControllerApi, cacheManager, - sessionCacheManager); + this.roleDTOService = new RoleDTOService(eCache, roleControllerApi, cacheManager); } @BeforeEach