diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bb4491ff40ed6d66ea3f7a903a02a59f26a2acf..ea33f0fa9c62bcc693684b1069aa8296eadc29da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added ### Changed +- Waiting time and processing time now uses consistent calculations. [@hpage](https://gitlab.ewi.tudelft.nl/hpage) ### Fixed - Fix the copying of labs [@mmadara](https://gitlab.ewi.tudelft.nl/mmadara) diff --git a/src/main/java/nl/tudelft/queue/controller/EditionStatusController.java b/src/main/java/nl/tudelft/queue/controller/EditionStatusController.java index c28e7b29cc6064bd3f2f234deb4563caee8fe95e..c05cbf99a486c098d41b6a65e96779ccb7127b3c 100644 --- a/src/main/java/nl/tudelft/queue/controller/EditionStatusController.java +++ b/src/main/java/nl/tudelft/queue/controller/EditionStatusController.java @@ -17,6 +17,7 @@ */ package nl.tudelft.queue.controller; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.TreeSet; @@ -41,6 +42,7 @@ import nl.tudelft.queue.model.enums.RequestType; import nl.tudelft.queue.model.labs.Lab; import nl.tudelft.queue.model.records.RequestCountRecord; import nl.tudelft.queue.service.EditionStatusService; +import nl.tudelft.queue.service.SessionService; @Validated @RestController @@ -52,6 +54,9 @@ public class EditionStatusController { @Autowired private AssignmentCacheManager aCache; + @Autowired + private SessionService sessionService; + /** * Creates and returns the data for a histogram regarding the status of requests over time. The data for * requests can be filtered on labs, assignments and rooms to only selectively count. @@ -135,6 +140,7 @@ public class EditionStatusController { @RequestParam(required = false, defaultValue = "") List<RequestType> type) { List<LabRequest> requests = ess.getFilteredRequests(labs, assignments, rooms, type); + // we use ESS here even though it's not really edition statistics. return new LabStatisticsViewDto( ess.countDistinctUsers(requests), ess.countDistinctAssistants(requests), @@ -148,8 +154,8 @@ public class EditionStatusController { .map(AssignmentCountStatisticsViewDto::getAssignmentName) .orElse("N/A"), // order preserving in case of tie ess.mostCountedName(ess.countRequestsPerRoom(rooms, requests)), - ess.averageWaitingTime(requests), - ess.averageProcessingTime(requests)); + ess.averageWaitingTime(requests, ChronoUnit.MILLIS), + ess.averageProcessingTime(requests, ChronoUnit.MILLIS)); } /** @@ -185,7 +191,7 @@ public class EditionStatusController { * @param assignments The assignments to filter on. * @param rooms The rooms to filter on. * @param type The request types to filter on. - * @return The names of assignments mapped to the number of times that room occurs. + * @return The names of assignments mapped to the number of times that room occurs.; */ @GetMapping("/edition/{editionId}/status/freq/room") @PreAuthorize("@permissionService.canViewEditionStatus(#editionId)") diff --git a/src/main/java/nl/tudelft/queue/controller/SessionStatusController.java b/src/main/java/nl/tudelft/queue/controller/SessionStatusController.java index 771e8b162abd2926e47d0b575b616996533c4dee..3b089229855236dc8a396302bb8b6e05c15aea70 100644 --- a/src/main/java/nl/tudelft/queue/controller/SessionStatusController.java +++ b/src/main/java/nl/tudelft/queue/controller/SessionStatusController.java @@ -17,6 +17,7 @@ */ package nl.tudelft.queue.controller; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.Objects; @@ -82,8 +83,8 @@ public class SessionStatusController { requests.stream().filter(rq -> rq.getRequestType() == RequestType.SUBMISSION) .count()), (long) qSession.getQueue().size(), - editionStatusService.averageWaitingTime(requests), - editionStatusService.averageProcessingTime(requests)); + editionStatusService.averageWaitingTime(requests, ChronoUnit.MILLIS), + editionStatusService.averageProcessingTime(requests, ChronoUnit.MILLIS)); } /** diff --git a/src/main/java/nl/tudelft/queue/dto/view/statistics/BucketStatisticsViewDto.java b/src/main/java/nl/tudelft/queue/dto/view/statistics/BucketStatisticsViewDto.java index 089c3f398a3fbd01df9c292dbbfec835cfd1d26c..3bc017889cb15418006b90d7aebe05418235ea4a 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/statistics/BucketStatisticsViewDto.java +++ b/src/main/java/nl/tudelft/queue/dto/view/statistics/BucketStatisticsViewDto.java @@ -32,7 +32,7 @@ import lombok.NoArgsConstructor; public class BucketStatisticsViewDto { // corresponds to edition, but entries are course names so it makes it more presentable - private Map<String, Long> bucketData; + private Map<String, ? extends Number> bucketData; // what gets displayed on the chart under this bucket. private String bucketLabel; diff --git a/src/main/java/nl/tudelft/queue/dto/view/statistics/LabStatisticsViewDto.java b/src/main/java/nl/tudelft/queue/dto/view/statistics/LabStatisticsViewDto.java index 89d6201ea608a6f6606781ff2cf9426a75f3101a..8c2fac3e172c88dafe912aff4298a57503889c83 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/statistics/LabStatisticsViewDto.java +++ b/src/main/java/nl/tudelft/queue/dto/view/statistics/LabStatisticsViewDto.java @@ -17,6 +17,8 @@ */ package nl.tudelft.queue.dto.view.statistics; +import java.util.OptionalDouble; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -39,6 +41,6 @@ public class LabStatisticsViewDto { private String assignment; private String room; - private Long avgWaitingTime; - private Long avgProcessingTime; + private OptionalDouble avgWaitingTime; + private OptionalDouble avgProcessingTime; } diff --git a/src/main/java/nl/tudelft/queue/dto/view/statistics/session/GeneralSessionStatisticsViewDto.java b/src/main/java/nl/tudelft/queue/dto/view/statistics/session/GeneralSessionStatisticsViewDto.java index f9ce36accc00a3027ff1506305bf10ac81489c37..44d1f441077ccbb8999ce34d1e54c12e632fc98f 100644 --- a/src/main/java/nl/tudelft/queue/dto/view/statistics/session/GeneralSessionStatisticsViewDto.java +++ b/src/main/java/nl/tudelft/queue/dto/view/statistics/session/GeneralSessionStatisticsViewDto.java @@ -17,6 +17,8 @@ */ package nl.tudelft.queue.dto.view.statistics.session; +import java.util.OptionalDouble; + import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -31,8 +33,8 @@ public class GeneralSessionStatisticsViewDto { private Long numEnqueued; - private Long avgWaitingTime; + private OptionalDouble avgWaitingTime; - private Long avgProcessingTime; + private OptionalDouble avgProcessingTime; } diff --git a/src/main/java/nl/tudelft/queue/model/LabRequest.java b/src/main/java/nl/tudelft/queue/model/LabRequest.java index 34ad99392cb849b4ea1d812a61e149c6e83f77d7..5561b521697905def7039ef77f255dfafa693e23 100644 --- a/src/main/java/nl/tudelft/queue/model/LabRequest.java +++ b/src/main/java/nl/tudelft/queue/model/LabRequest.java @@ -17,8 +17,6 @@ */ package nl.tudelft.queue.model; -import static java.time.LocalDateTime.now; - import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -112,30 +110,6 @@ public class LabRequest extends Request<Lab> { @OneToMany(mappedBy = "request") private List<Feedback> feedbacks = new ArrayList<>(); - /** - * Gets the waiting time in seconds experienced during this request. The waiting time is defined as the - * time between the first time that the request was processed and the creation time of the request. - * - * @return The waiting time in seconds experienced during this request. - */ - public OptionalDouble waitingTime() { - if (getEventInfo().getFirstProcessedAt() == null || getCreatedAt() == null) { - return OptionalDouble.empty(); - } - - return OptionalDouble - .of(ChronoUnit.SECONDS.between(getCreatedAt(), getEventInfo().getFirstProcessedAt())); - } - - /** - * Alias for {@code sensibleWaitingTime(now())} - * - * @see #sensibleWaitingTime(LocalDateTime) - */ - public OptionalDouble sensibleWaitingTime() { - return sensibleWaitingTime(now()); - } - /** * Gets the waiting time in seconds that is being experienced, or was experienced for this request. The * waiting time is defined differently depending on the specifics of the request: @@ -150,13 +124,18 @@ public class LabRequest extends Request<Lab> { * effectiveEndTime.</li> * </ul> * - * If this request has a timeslot which starts in the future, this method returns an empty optionaldouble. - * - * @param effectiveEndTime the effective end time to use + * If this request has a timeslot which starts in the future, this method returns an empty OptionalLong. + * We also consider the end time of the session to override the effectiveEndTime so that viewing wait time + * statistics for a session that occurs in the past is not skewed. * - * @return The waiting time in seconds this request is experiencing or has experienced. + * @param effectiveEndTime The effective end time to collect wait time statistics. Usually this is the + * present or the end of the lab. if computing bucketed statistics, you can put + * it a time in the past. + * @param unit The unit you want to compute this time difference in. + * @return The waiting time in the desired unit. */ - public OptionalDouble sensibleWaitingTime(LocalDateTime effectiveEndTime) { + public OptionalDouble waitTime(LocalDateTime effectiveEndTime, + ChronoUnit unit) { LocalDateTime startTime; if (getTimeSlot() == null) { startTime = getCreatedAt(); @@ -171,12 +150,37 @@ public class LabRequest extends Request<Lab> { endTime = getEventInfo().getFirstProcessedAt(); } - long time = ChronoUnit.SECONDS.between(startTime, endTime); - if (time < 0) { + return calculateTimeDifferenceWithUnit(startTime, endTime, unit); + } + + /** + * The processing time of a request. If a request has not yet processed, it is not considered and will + * return an empty OptionalLong This also occurs when looking at something in the future. It contributes + * to the calculation if it is currently being processed, or if it has been processed. + * + * + * @param effectiveEndTime The effective end time to collect wait time statistics. Usually this is the + * present or the end of the lab. if computing bucketed statistics, you can put + * it a time in the past. + * @param unit The unit you want to compute this time difference in. + * @return The processing time of the current request. + */ + public OptionalDouble processingTime(LocalDateTime effectiveEndTime, + ChronoUnit unit) { + + final LocalDateTime startTime = getEventInfo().getFirstProcessedAt(); + + if (startTime == null) return OptionalDouble.empty(); + + LocalDateTime endTime; + if (getEventInfo().getHandledAt() == null) { + endTime = effectiveEndTime; + } else { + endTime = getEventInfo().getHandledAt(); } - return OptionalDouble.of(time); + return calculateTimeDifferenceWithUnit(startTime, endTime, unit); } @Override @@ -192,4 +196,23 @@ public class LabRequest extends Request<Lab> { public Class<LabRequestViewDTO> viewClass() { return LabRequestViewDTO.class; } + + /** + * Calculate a time difference with a specified unit. + * + * @param start The lower bound + * @param end The upper bound + * @param unit The unit you wish to retrieve the time difference in + * @return The time difference in the specified unit, if end < start, it will return an empty + * optional + */ + private OptionalDouble calculateTimeDifferenceWithUnit(LocalDateTime start, LocalDateTime end, + ChronoUnit unit) { + long time = unit.between(start, end); + if (time < 0) { + return OptionalDouble.empty(); + } + + return OptionalDouble.of(time); + } } diff --git a/src/main/java/nl/tudelft/queue/model/TimeSlot.java b/src/main/java/nl/tudelft/queue/model/TimeSlot.java index 1d745e1bed8ea6f0e8242d4c602e8f863e129a00..f5762364488295258d3bab86958f3f937b4bc2be 100644 --- a/src/main/java/nl/tudelft/queue/model/TimeSlot.java +++ b/src/main/java/nl/tudelft/queue/model/TimeSlot.java @@ -196,16 +196,4 @@ public class TimeSlot { && !ts.isFullAccordingToPercentage() && !ts.getSlot().closed()) .count() > slottedLab.getSlottedLabConfig().getPreviousEmptyAllowedThreshold(); } - - /** - * Counts the number of slots that are taken but did not finish processing. This is the count of how many - * requests in a certain slot are pending. - * - * @return The number of requests that are pending. - */ - public int countPendingRequests() { - return (int) requests.stream() - .filter(r -> r.getEventInfo().getStatus().isPending()) - .count(); - } } diff --git a/src/main/java/nl/tudelft/queue/model/labs/AbstractSlottedLab.java b/src/main/java/nl/tudelft/queue/model/labs/AbstractSlottedLab.java index 82dd24bf48298b9ba9c5744768afa1ad28a92e05..c1a64128dd817987e0850797ca65bb877431dc7e 100644 --- a/src/main/java/nl/tudelft/queue/model/labs/AbstractSlottedLab.java +++ b/src/main/java/nl/tudelft/queue/model/labs/AbstractSlottedLab.java @@ -17,9 +17,14 @@ */ package nl.tudelft.queue.model.labs; +import static java.time.LocalDateTime.now; + import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Comparator; import java.util.List; +import java.util.OptionalDouble; import java.util.stream.Stream; import jakarta.persistence.Embedded; @@ -29,6 +34,7 @@ import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.experimental.SuperBuilder; import nl.tudelft.queue.model.LabRequest; +import nl.tudelft.queue.model.Request; import nl.tudelft.queue.model.TimeSlot; import nl.tudelft.queue.model.embeddables.SlottedLabConfig; @@ -98,4 +104,37 @@ public abstract class AbstractSlottedLab<TS extends TimeSlot> extends Lab { return slottedLabConfig.isConsecutive(); } + @Override + public OptionalDouble currentWaitingTime(LocalDateTime labEndTime, ChronoUnit unit) { + + Stream<? extends TimeSlot> slots = getTimeSlots().stream(); + OptionalDouble baseWaitTime; + LocalDateTime effectiveEndTime; + + if (labEndTime.isBefore(now())) { + effectiveEndTime = labEndTime; + baseWaitTime = OptionalDouble.of(0); + } else { + slots = slots.filter(ts -> ts.getSlot().getOpensAt().isBefore(now())); + effectiveEndTime = now(); + baseWaitTime = OptionalDouble.empty(); + } + + // Find the last started timeslot with pending requests, if any. + // Otherwise, find the last started timeslot with requests. + // Then pick the first created pending request, if any. Otherwise, pick the first created request. + return slots.max(Comparator.<TimeSlot>comparingInt(ts -> ts.hasPendingRequests() ? 1 : -1) + .thenComparingInt(ts -> ts.getRequests().size() > 0 ? 1 : -1) + .thenComparing(ts -> ts.getSlot().getOpensAt())) + .stream() + .flatMap(ts -> ts.getRequests().stream()) + .max(Comparator + .<LabRequest>comparingInt(r -> r.getEventInfo().getStatus().isPending() ? 1 : -1) + .thenComparing(Comparator.nullsFirst(Comparator + .<LabRequest, LocalDateTime>comparing(Request::getCreatedAt) + .reversed()))) + .map(r -> r.waitTime(effectiveEndTime, unit)) + .orElse(baseWaitTime); + } + } diff --git a/src/main/java/nl/tudelft/queue/model/labs/Lab.java b/src/main/java/nl/tudelft/queue/model/labs/Lab.java index d5be18fd5d41dcc9ec976fbe5abf5001b0b8c484..09ee2598abf42e17c3f63284787950513f7f6d3a 100644 --- a/src/main/java/nl/tudelft/queue/model/labs/Lab.java +++ b/src/main/java/nl/tudelft/queue/model/labs/Lab.java @@ -17,6 +17,10 @@ */ package nl.tudelft.queue.model.labs; +import static java.time.LocalDateTime.now; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -123,4 +127,26 @@ public abstract class Lab extends QueueSession<LabRequest> { protected boolean allowsRequest(LabRequest request) { return allowedRequests.contains(AllowedRequest.of(request.getAssignment(), request.getRequestType())); } + + public OptionalDouble currentWaitingTime(LocalDateTime labEndTime, ChronoUnit unit) { + + if (labEndTime.isBefore(now())) { + // Lab is over, display waiting time of current requests or else 0 + return getPendingRequests().stream() + .min(Comparator.comparing(LabRequest::getCreatedAt)) + .map(r -> r.waitTime(labEndTime, unit)) + .orElse(OptionalDouble.of(0)); + } else { + // Lab is running. Get the waiting time of the oldest pending request. + // If nothing is pending, report the waiting time of the last handled request. + // If nothing was handled, report no waiting time. + return getPendingRequests().stream() + .min(Comparator.comparing(LabRequest::getCreatedAt)) + .map(r -> r.waitTime(now(), unit)) + .orElseGet(() -> getHandled().stream() + .max(Comparator.comparing(LabRequest::getCreatedAt)) + .map(r -> r.waitTime(now(), unit)) + .orElse(OptionalDouble.empty())); + } + } } diff --git a/src/main/java/nl/tudelft/queue/service/EditionStatusService.java b/src/main/java/nl/tudelft/queue/service/EditionStatusService.java index 9bc1f6ef0866cc7ade0e45a4b01a174dbd1c04af..107b98e0dd07f6299bad0c7edb2a22f2607dc1d0 100644 --- a/src/main/java/nl/tudelft/queue/service/EditionStatusService.java +++ b/src/main/java/nl/tudelft/queue/service/EditionStatusService.java @@ -17,6 +17,7 @@ */ package nl.tudelft.queue.service; +import static java.time.LocalDateTime.now; import static java.time.temporal.ChronoField.SECOND_OF_DAY; import java.time.LocalDateTime; @@ -37,11 +38,11 @@ import nl.tudelft.labracore.api.dto.PersonSummaryDTO; import nl.tudelft.labracore.api.dto.RoomDetailsDTO; import nl.tudelft.queue.cache.PersonCacheManager; import nl.tudelft.queue.cache.RoomCacheManager; -import nl.tudelft.queue.cache.SessionCacheManager; import nl.tudelft.queue.dto.view.statistics.AssignmentCountStatisticsViewDto; import nl.tudelft.queue.dto.view.statistics.AssistantRatingViewDto; import nl.tudelft.queue.dto.view.statistics.LabStatisticsViewDto; import nl.tudelft.queue.model.LabRequest; +import nl.tudelft.queue.model.QueueSession; import nl.tudelft.queue.model.Request; import nl.tudelft.queue.model.enums.RequestType; import nl.tudelft.queue.model.labs.Lab; @@ -56,8 +57,6 @@ public class EditionStatusService { private final LabRequestRepository rr; - private final SessionCacheManager sCache; - private final PersonCacheManager pCache; private final FeedbackService fs; @@ -319,88 +318,47 @@ public class EditionStatusService { } /** - * See {@link #averageWaitingTime(List, ChronoUnit, LocalDateTime)} for complete description. + * Calculate average wait time for a list of requests. * - * @param requests The lists of requests to consider - * @return The average waiting time for these requests + * @param requests The list of requests to consider for the wait time calculation. + * @param unit The {@link ChronoUnit} to calculate the average wait time in. + * @return The average wait time in the given unit. Can be none if no qualifying requests are + * present. */ - public Long averageWaitingTime(List<LabRequest> requests) { - return averageWaitingTime(requests, ChronoUnit.MILLIS, LocalDateTime.now()); - } + public OptionalDouble averageWaitingTime(List<LabRequest> requests, + ChronoUnit unit) { - /** - * See {@link #averageProcessingTime(List, ChronoUnit, LocalDateTime)} for complete description. - * - * @param requests The lists of requests to consider - * @return The average processing time for these requests - */ - public Long averageProcessingTime(List<LabRequest> requests) { - return averageProcessingTime(requests, ChronoUnit.MILLIS, LocalDateTime.now()); - } + Map<Long, LocalDateTime> effectiveEndTimes = calculateEffectiveEndTimeForRequestStatistics(requests); - /** - * Calculates the average waiting time over all requests that are being processed or are already - * processed. - * - * Note: for timeslots, we take the start of the timeslot as the start of the waiting period, whereas if - * there is no timeslot, we take the createdAt time. - * - * @param requests The list of requests to calculate over. - * @param unit The unit to calculate the average time into. - * @param referenceTime For requests that have not been processed yet, we still want to calculate the - * waiting time. Furthermore, sometimes we want to calculate how long the average - * time was in the perspective of an earlier time instead of now. - * @return The average time between creating a request and a TA starting to process it. - */ - public Long averageWaitingTime(List<LabRequest> requests, ChronoUnit unit, LocalDateTime referenceTime) { - return (long) requests + return requests .stream() - .mapToLong(r -> { - LocalDateTime startTime = (r.getTimeSlot() != null) - ? r.getTimeSlot().getSlot().getOpensAt() - : r.getCreatedAt(); - - if (referenceTime.isBefore(startTime)) { - return 0L; - } - - return unit.between(startTime, - min(r.getEventInfo().getFirstProcessedAt(), referenceTime)); + .flatMapToDouble(r -> { + LocalDateTime effectiveEndTime = effectiveEndTimes.getOrDefault(r.getId(), now()); + return r.waitTime(effectiveEndTime, unit).stream(); }) - .average().orElse(0.0); + .average(); } /** - * Calculates the average processing time (the time between a TA starting to process the student and the - * request being handled) over the following types of requests + * Calculate average processing time for a list of requests. * - * <li>Requests that have been handled</li> - * <li>Requests that are currently being handled (The time until now is taken in this case).</li> - * - * @param requests The list of requests to calculate over. - * @param unit The unit to calculate the average time into. - * @param referenceTime For requests that have not been handled yet, we still want to calculate the - * average processing time. Furthermore, sometimes we want to calculate the average - * time from the perspective of an earlier time. - * @return The average time between processing a request and it being handled. + * @param requests The list of requests to consider for the average processing time calculation. + * @param unit The {@link ChronoUnit} to calculate the average wait time in. + * @return The average processing time in the given unit. Can be none if no qualifying requests + * are present. */ - public Long averageProcessingTime(List<LabRequest> requests, ChronoUnit unit, - LocalDateTime referenceTime) { - - return (long) requests.stream() - .filter(r -> r.getEventInfo().getFirstProcessedAt() != null) - .mapToLong(r -> { - final LocalDateTime handledAt = r.getEventInfo().getHandledAt(); - final LocalDateTime firstProcessedAt = r.getEventInfo().getFirstProcessedAt(); + public OptionalDouble averageProcessingTime(List<LabRequest> requests, + ChronoUnit unit) { - if (referenceTime.isBefore(firstProcessedAt)) { - return 0L; - } + Map<Long, LocalDateTime> effectiveEndTimes = calculateEffectiveEndTimeForRequestStatistics(requests); - return unit.between(firstProcessedAt, min(handledAt, referenceTime)); + return requests + .stream() + .flatMapToDouble(r -> { + LocalDateTime effectiveEndTime = effectiveEndTimes.getOrDefault(r.getId(), now()); + return r.processingTime(effectiveEndTime, unit).stream(); }) - .average() - .orElse(0.0); + .average(); } /** @@ -461,17 +419,15 @@ public class EditionStatusService { */ public Map<String, LabStatisticsViewDto> countWaitProcessingTimePerLab(List<LabRequest> requests) { - final LocalDateTime now = LocalDateTime.now(); - Map<String, List<LabRequest>> groupedLabRequests = groupRequestsByLab(requests); return new TreeMap<>(groupedLabRequests.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> LabStatisticsViewDto.builder() - .avgWaitingTime(averageWaitingTime(entry.getValue(), ChronoUnit.MINUTES, now)) + .avgWaitingTime(averageWaitingTime(entry.getValue(), ChronoUnit.MINUTES)) .avgProcessingTime( - averageProcessingTime(entry.getValue(), ChronoUnit.MINUTES, now)) + averageProcessingTime(entry.getValue(), ChronoUnit.MINUTES)) .build()))); } @@ -501,6 +457,32 @@ public class EditionStatusService { Map.Entry::getValue)); } + /** + * Used to calculate the effective end time for a list of requests, used for statistics throughout the + * app. + * + * @param requests The list of requests to calculate the effective end time for. + * @return A map of requests to their effective end time. + */ + public Map<Long, LocalDateTime> calculateEffectiveEndTimeForRequestStatistics(List<LabRequest> requests) { + return requests.stream() + .collect(Collectors.toMap( + Request::getId, + request -> calculateEffectiveEndTimeForLab(request.getSession()))); + } + + /** + * The calculation for the 'effective end time' used in many statistics. Basically whether we want a live + * representation or a representation which considers the chosen statistic in the past. + * + * @param qSession The session in question + * @return min(session end time, now) + */ + private LocalDateTime calculateEffectiveEndTimeForLab(QueueSession<?> qSession) { + LocalDateTime endTime = sessionService.getSessionEndTime(qSession); + return min(endTime, now()); + } + /** * Returns the min of two LocalDateTime instances. If one instance is null, it will return the other one * @@ -511,11 +493,13 @@ public class EditionStatusService { */ private LocalDateTime min(LocalDateTime d1, LocalDateTime d2) { if (d1 == null && d2 == null) - throw new IllegalArgumentException("This should not happen"); + throw new IllegalArgumentException("One of the arguments needs to be non-null."); if (d1 == null) return d2; if (d2 == null) return d1; + return d1.isBefore(d2) ? d1 : d2; } + } diff --git a/src/main/java/nl/tudelft/queue/service/LabService.java b/src/main/java/nl/tudelft/queue/service/LabService.java index b5c10313245c8b01e98dee130c10762e1948cea9..85b6e91cb8c44cb4e9e8e4d53044ab743afbc03e 100644 --- a/src/main/java/nl/tudelft/queue/service/LabService.java +++ b/src/main/java/nl/tudelft/queue/service/LabService.java @@ -23,7 +23,6 @@ import java.io.IOException; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; @@ -68,7 +67,6 @@ import nl.tudelft.queue.model.labs.AbstractSlottedLab; import nl.tudelft.queue.model.labs.CapacitySession; import nl.tudelft.queue.model.labs.ExamLab; import nl.tudelft.queue.model.labs.Lab; -import nl.tudelft.queue.model.labs.RegularLab; import nl.tudelft.queue.model.misc.Presentation; import nl.tudelft.queue.repository.LabRepository; import nl.tudelft.queue.repository.LabRequestConstraintRepository; @@ -90,9 +88,6 @@ public class LabService { @Autowired private EditionCollectionCacheManager ecCache; - @Autowired - private SessionCacheManager sCache; - @Autowired private AssignmentCacheManager aCache; @@ -111,9 +106,6 @@ public class LabService { @Autowired private RequestTableService rts; - @Autowired - private TimeSlotService tss; - @Autowired private LabRepository lr; @@ -140,6 +132,7 @@ public class LabService { @Autowired private SessionService sessionService; + @Autowired private SessionCacheManager sessionCache; @@ -562,93 +555,6 @@ public class LabService { } } - public OptionalDouble currentWaitingTime(Lab lab) { - if (lab instanceof RegularLab) { - return currentWaitingTime((RegularLab) lab); - } else if (lab instanceof AbstractSlottedLab<?>) { - return currentWaitingTime((AbstractSlottedLab<? extends TimeSlot>) lab); - } - return OptionalDouble.empty(); - } - - public OptionalDouble currentWaitingTime(AbstractSlottedLab<? extends TimeSlot> lab) { - var labEndTime = sessionService.getSessionDTOFromSession(lab).getEndTime(); - Stream<? extends TimeSlot> slots = lab.getTimeSlots().stream(); - OptionalDouble baseWaitTime; - LocalDateTime effectiveEndTime; - - if (labEndTime.isBefore(now())) { - effectiveEndTime = labEndTime; - baseWaitTime = OptionalDouble.of(0); - } else { - slots = slots.filter(ts -> ts.getSlot().getOpensAt().isBefore(now())); - effectiveEndTime = now(); - baseWaitTime = OptionalDouble.empty(); - } - - // Find the last started timeslot with pending requests, if any. - // Otherwise, find the last started timeslot with requests. - // Then pick the first created pending request, if any. Otherwise, pick the first created request. - return slots.max(Comparator.<TimeSlot>comparingInt(ts -> ts.hasPendingRequests() ? 1 : -1) - .thenComparingInt(ts -> ts.getRequests().size() > 0 ? 1 : -1) - .thenComparing(ts -> ts.getSlot().getOpensAt())) - .stream() - .flatMap(ts -> ts.getRequests().stream()) - .max(Comparator - .<LabRequest>comparingInt(r -> r.getEventInfo().getStatus().isPending() ? 1 : -1) - .thenComparing(Comparator.nullsFirst(Comparator - .<LabRequest, LocalDateTime>comparing(Request::getCreatedAt) - .reversed()))) - .map(r -> r.sensibleWaitingTime(effectiveEndTime)) - .orElse(baseWaitTime); - - } - - public OptionalDouble currentWaitingTime(RegularLab lab) { - var labEndTime = sessionService.getSessionDTOFromSession(lab).getEndTime(); - - if (labEndTime.isBefore(LocalDateTime.now())) { - // Lab is over, display waiting time of current requests or else 0 - return lab.getPendingRequests().stream() - .min(Comparator.comparing(LabRequest::getCreatedAt)) - .map(r -> r.sensibleWaitingTime(labEndTime)) - .orElse(OptionalDouble.of(0)); - } else { - // Lab is running. Get the waiting time of the oldest pending request. - // If nothing is pending, report the waiting time of the last handled request. - // If nothing was handled, report no waiting time. - return lab.getPendingRequests().stream() - .min(Comparator.comparing(LabRequest::getCreatedAt)) - .map(LabRequest::sensibleWaitingTime) - .orElseGet(() -> lab.getHandled().stream() - .max(Comparator.comparing(LabRequest::getCreatedAt)) - .map(LabRequest::sensibleWaitingTime) - .orElse(OptionalDouble.empty())); - } - - } - - /** - * Gets the average waiting time experienced by students in this lab over the last hour. If the lab is - * over, this returns the average waiting time over the entire lab instead. - * - * @return The average waiting time if one can be calculated, for fresh labs this is empty. - */ - public OptionalDouble averageWaitingTime(Lab lab) { - var labEndTime = sessionService.getSessionDTOFromSession(lab).getEndTime(); - if (labEndTime.isBefore(now())) { - // past session, give average over the session - return lab.getHandled().stream() - .flatMapToDouble(r -> r.sensibleWaitingTime(labEndTime).stream()) - .average(); - } else { - return lab.getHandled().stream() - .filter(r -> r.getEventInfo().getLastEventAt().isAfter(now().minusHours(1))) - .flatMapToDouble(r -> r.sensibleWaitingTime().stream()) - .average(); - } - } - public boolean allowsRequest(QueueSession<?> session, Request<?> request) { if (!labRequestConstraintService.allowsRequest(session.getConstraints(), request) && Objects.equals(request.getSession().getId(), session.getId())) { diff --git a/src/main/java/nl/tudelft/queue/service/SessionService.java b/src/main/java/nl/tudelft/queue/service/SessionService.java index 83d4f4062be3e019e7104fb342bd2b42180af9fb..71ae6b358dc72c00ea84356edf3ea66571b6c2ea 100644 --- a/src/main/java/nl/tudelft/queue/service/SessionService.java +++ b/src/main/java/nl/tudelft/queue/service/SessionService.java @@ -17,6 +17,7 @@ */ package nl.tudelft.queue.service; +import java.time.LocalDateTime; import java.util.*; import org.springframework.stereotype.Service; @@ -107,4 +108,15 @@ public class SessionService { SessionDetailsDTO session = getCoreSession(qSession.getSession()); return qSession.copyLabCreateDTO(session); } + + /** + * End time for a core session. + * + * @param qSession The session in question. + * @return The end time of the session + */ + public LocalDateTime getSessionEndTime(QueueSession<?> qSession) { + return getCoreSession(qSession.getSession()).getEndTime(); + } + } diff --git a/src/main/java/nl/tudelft/queue/service/SessionStatusService.java b/src/main/java/nl/tudelft/queue/service/SessionStatusService.java index f57508dcc2958e219c06e0a4e4acbc0cd5b2e3cd..823a06e40a1ae7fd0326d34ed46b717818e25b46 100644 --- a/src/main/java/nl/tudelft/queue/service/SessionStatusService.java +++ b/src/main/java/nl/tudelft/queue/service/SessionStatusService.java @@ -17,6 +17,8 @@ */ package nl.tudelft.queue.service; +import static java.time.LocalDateTime.now; + import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; @@ -76,6 +78,27 @@ public class SessionStatusService { } + /** + * Gets the average waiting time experienced by students in this lab. If the lab is over, this returns the + * average waiting time over the entire lab instead. + * + * @return The average waiting time if one can be calculated, for fresh labs this is empty. + */ + public OptionalDouble averageWaitingTime(Lab qs) { + return ess.averageWaitingTime(qs.getRequests(), ChronoUnit.SECONDS); + } + + /** + * Gets the current waiting time of a lab. Calculated differently between regular and slotted labs. + * + * @param lab The lab to calculate the current waiting time for. + * @return The current waiting time in seconds. + */ + public OptionalDouble currentWaitingTime(Lab lab) { + LocalDateTime labEndTime = sessionService.getSessionEndTime(lab); + return lab.currentWaitingTime(labEndTime, ChronoUnit.SECONDS); + } + /** * Method responsible for creating a list of dtos where each one represents a bucket containing * information about how many requests occured within that timespan. It also segregates between the @@ -93,7 +116,8 @@ public class SessionStatusService { Set<Long> editions, long bucketSizeInMinutes) { - final BiFunction<List<LabRequest>, LocalDateTime, Map<String, Long>> bucketFunction = (labRequests, + final BiFunction<List<LabRequest>, LocalDateTime, Map<String, ? extends Number>> bucketFunction = ( + labRequests, bucketEnd) -> { // bucket function does not consider `bucketEnd` since this is not a running statistic. @@ -127,7 +151,8 @@ public class SessionStatusService { Set<Long> editions, long bucketSizeInMinutes) { - final BiFunction<List<LabRequest>, LocalDateTime, Map<String, Long>> bucketFunction = (labRequests, + final BiFunction<List<LabRequest>, LocalDateTime, Map<String, ? extends Number>> bucketFunction = ( + labRequests, bucketEnd) -> { List<RoomDetailsDTO> rooms = labRequests.stream() .filter(rq -> rq.getRoom() != null) @@ -160,15 +185,17 @@ public class SessionStatusService { final LocalDateTime now = LocalDateTime.now(); // running statistic which considers the last 1 hour to avoid outlier skewing. - BiFunction<List<LabRequest>, LocalDateTime, Map<String, Long>> bucketFunction = (labRequests, + BiFunction<List<LabRequest>, LocalDateTime, Map<String, ? extends Number>> bucketFunction = ( + labRequests, bucketEnd) -> { - List<LabRequest> requestsInTheLastThirtyMinutes = labRequests.stream() + List<LabRequest> requestsInTheLastHour = labRequests.stream() .filter(rq -> rq.getCreatedAt().isAfter(now.minusHours(1L))).toList(); return Map.of( "Average Wait Time", - ess.averageWaitingTime(requestsInTheLastThirtyMinutes, ChronoUnit.MINUTES, bucketEnd), + ess.averageWaitingTime(requestsInTheLastHour, ChronoUnit.MINUTES).orElse(0.0d), + "Average Processing Time", - ess.averageProcessingTime(requestsInTheLastThirtyMinutes, ChronoUnit.MINUTES, bucketEnd)); + ess.averageProcessingTime(requestsInTheLastHour, ChronoUnit.MINUTES).orElse(0.0d)); }; return createBucketsForStatistic(qSession, editions, bucketSizeInMinutes, bucketFunction, true); @@ -188,7 +215,7 @@ public class SessionStatusService { private List<BucketStatisticsViewDto> createBucketsForStatistic(Lab qSession, Set<Long> editions, long bucketSizeInMinutes, - BiFunction<List<LabRequest>, LocalDateTime, Map<String, Long>> bucketFunction, + BiFunction<List<LabRequest>, LocalDateTime, Map<String, ? extends Number>> bucketFunction, boolean runningStatistic) { LocalDateTime now = LocalDateTime.now(); var requests = requestService.getLabRequestsForEditions(qSession.getRequests(), editions); @@ -224,7 +251,8 @@ public class SessionStatusService { .toList(); } - Map<String, Long> bucketData = bucketFunction.apply(requestsInBucket, bucketEnd); + Map<String, ? extends Number> bucketData = bucketFunction.apply(requestsInBucket, + bucketEnd); String bucketLabel = bucketStart.format(dateFormat) + " - " + diff --git a/src/main/resources/static/js/chart_utils.js b/src/main/resources/static/js/chart_utils.js index d4be8a536bf17103c5f8d47cf0907e9fd15144cd..2356876e24a553f1fa18eae48fcd3d083cb5fd06 100644 --- a/src/main/resources/static/js/chart_utils.js +++ b/src/main/resources/static/js/chart_utils.js @@ -17,6 +17,7 @@ */ let colorIdx = 0; +const UNKNOWN = "Unknown"; /** * Color-blind friendly random colors used in graphs. @@ -74,6 +75,8 @@ function getColorForStatus(status) { * @returns {string} A string which has a human representation of the spanned time. */ function msToHumanReadableTime(ms) { + if (!ms) return UNKNOWN; + ms = parseInt(ms); let seconds = (ms / 1000).toFixed(1); let minutes = (ms / (1000 * 60)).toFixed(1); let hours = (ms / (1000 * 60 * 60)).toFixed(1); diff --git a/src/main/resources/static/js/lab_session_status_common.js b/src/main/resources/static/js/lab_session_status_common.js index 63eb0b6946abe2c89bfed3a500fafe6456940955..aa6a14eb1da2971b705e77aa5b6856ce1b02324c 100644 --- a/src/main/resources/static/js/lab_session_status_common.js +++ b/src/main/resources/static/js/lab_session_status_common.js @@ -347,8 +347,8 @@ function updateLabTimeChart(canvas) { Object.entries(data).forEach(([labName, labStatusDto]) => { labels.push(labName); - avgWaitTimes.push(labStatusDto.avgWaitingTime); - avgProcessingTimes.push(labStatusDto.avgProcessingTime); + avgWaitTimes.push(labStatusDto.avgWaitingTime ?? 0); + avgProcessingTimes.push(labStatusDto.avgProcessingTime ?? 0); }); if (actLabTimeChart != null) { diff --git a/src/main/resources/static/js/session_status.js b/src/main/resources/static/js/session_status.js index 42143ff40cca6babf51b2694d5978e04a91cb61e..65db5c729a9bff958e3e4a8d63a51bbba9e3855e 100644 --- a/src/main/resources/static/js/session_status.js +++ b/src/main/resources/static/js/session_status.js @@ -140,11 +140,7 @@ function updateGeneralInformation(infoCards) { $(infoCards).find("#card-question-count").html(numQuestions); $(infoCards).find("#card-submission-count").html(numSubmissions); $(infoCards).find("#card-enqueued-count").html(data["numEnqueued"]); - $(infoCards) - .find("#card-waiting-time") - .html(msToHumanReadableTime(parseInt(data["avgWaitingTime"]))); - $(infoCards) - .find("#card-processing-time") - .html(msToHumanReadableTime(parseInt(data["avgProcessingTime"]))); + $(infoCards).find("#card-waiting-time").html(msToHumanReadableTime(data["avgWaitingTime"])); + $(infoCards).find("#card-processing-time").html(msToHumanReadableTime(data["avgProcessingTime"])); }; } diff --git a/src/main/resources/templates/edition/view/status.html b/src/main/resources/templates/edition/view/status.html index 21b528b82df5f8ca1dd2fd83458a9f39aee8a4c6..74326c2e4541c6acc9bfe8f719c0825892e75ad6 100644 --- a/src/main/resources/templates/edition/view/status.html +++ b/src/main/resources/templates/edition/view/status.html @@ -150,11 +150,11 @@ <td id="lab-static-room">N.A.</td> </tr> <tr> - <td>Average waiting time for the last hour</td> + <td>Average waiting time</td> <td id="lab-static-avgWaitingTime">N.A.</td> </tr> <tr> - <td>Average processing time in the last hour</td> + <td>Average processing time</td> <td id="lab-static-avgProcessingTime">N.A.</td> </tr> </table> diff --git a/src/main/resources/templates/lab/view/components/lab-info.html b/src/main/resources/templates/lab/view/components/lab-info.html index 8bddb4f9b35985f63156ac63ccac9232cb4e3c84..3eefd90348f263de2f01d03a26e23f23b9e6d633 100644 --- a/src/main/resources/templates/lab/view/components/lab-info.html +++ b/src/main/resources/templates/lab/view/components/lab-info.html @@ -163,13 +163,14 @@ </dd> <dt class="fw-500 mt-3">Waiting time</dt> - <dd th:with="avg = ${@labService.averageWaitingTime(qSession.data)}, cur = ${@labService.currentWaitingTime(qSession.data)}"> + <dd + th:with="avg = ${@sessionStatusService.averageWaitingTime(qSession.data)}, cur = ${@sessionStatusService.currentWaitingTime(qSession.data)}"> <th:block th:if="${avg.isPresent() || cur.isPresent()}"> <th:block th:if="${avg.isPresent()}"> <span th:text="|${T(java.lang.Math).round(avg.getAsDouble() / 60)} minutes (average)|"></span> <div class="tooltip"> <button class="tooltip__control fa-solid fa-question"></button> - <p role="tooltip">Computed as the average waiting time of archived requests in the past hour.</p> + <p role="tooltip">Computed as the average waiting time of across all requests in the lab.</p> </div> </th:block> <span th:unless="${avg.isPresent()}">Unknown (average)</span> diff --git a/src/test/java/nl/tudelft/queue/model/LabTest.java b/src/test/java/nl/tudelft/queue/model/LabTest.java index 1726f34ebfa33000752f17a4842551d932b93865..673decdc5eadcc5e69c34574bc8543d56b31d50b 100644 --- a/src/test/java/nl/tudelft/queue/model/LabTest.java +++ b/src/test/java/nl/tudelft/queue/model/LabTest.java @@ -30,7 +30,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; import jakarta.transaction.Transactional; import nl.tudelft.queue.model.embeddables.AllowedRequest; @@ -41,8 +40,7 @@ import nl.tudelft.queue.model.enums.RequestType; import nl.tudelft.queue.model.labs.Lab; import nl.tudelft.queue.model.labs.RegularLab; import nl.tudelft.queue.model.labs.SlottedLab; -import nl.tudelft.queue.service.LabService; -import nl.tudelft.queue.service.SessionService; +import nl.tudelft.queue.service.SessionStatusService; import test.TestDatabaseLoader; import test.labracore.SessionApiMocker; import test.test.TestQueueApplication; @@ -61,16 +59,11 @@ public class LabTest { @Autowired private SessionApiMocker sMock; - @Autowired - private ApplicationContext applicationContext; - @Autowired private TestDatabaseLoader db; @Autowired - private SessionService sessionService; - @Autowired - private LabService labService; + private SessionStatusService sessionStatusService; @BeforeEach void setUp() { @@ -103,7 +96,7 @@ public class LabTest { .build(); request3 = LabRequest.builder() - .id(8932L) + .id(8933L) .requestType(QUESTION) .assignment(56582L) .comment("hdsada") @@ -132,6 +125,10 @@ public class LabTest { .deletedAt(null) .requests(List.of(request1, request2, request3)) .build(); + + request1.setSession(lab1); + request2.setSession(lab1); + request3.setSession(lab1); } @Test @@ -250,13 +247,13 @@ public class LabTest { @Test void averageWaitingTimeAveragesOverHandled() { - assertThat(labService.averageWaitingTime(lab1)).isPresent() - .hasValueCloseTo(60.0, Offset.offset(0.005)); + assertThat(sessionStatusService.averageWaitingTime(lab1)).isPresent() + .hasValueCloseTo(30.0, Offset.offset(0.005)); } @Test void currentWaitingTimeWorksOnLastPending() { - assertThat(labService.currentWaitingTime(lab1)).isPresent() + assertThat(sessionStatusService.currentWaitingTime(lab1)).isPresent() .hasValueCloseTo(30.0, Offset.offset(0.005)); } diff --git a/src/test/java/nl/tudelft/queue/model/RequestTest.java b/src/test/java/nl/tudelft/queue/model/RequestTest.java index 9d749a63ecabf176d6fe875b5c443cb55ed358b2..035fab1e8924451584a478038be8160c8085827a 100644 --- a/src/test/java/nl/tudelft/queue/model/RequestTest.java +++ b/src/test/java/nl/tudelft/queue/model/RequestTest.java @@ -17,13 +17,9 @@ */ package nl.tudelft.queue.model; -import static org.assertj.core.api.Assertions.assertThat; - import java.time.LocalDateTime; -import org.assertj.core.data.Offset; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import nl.tudelft.queue.model.embeddables.RequestEventInfo; @@ -44,23 +40,4 @@ public class RequestTest { .build(); } - @Test - void waitingTimeCalculatesDifferenceBetweenCreatedAtAndFirstProcessedTime() { - assertThat(request.waitingTime()).isPresent() - .hasValueCloseTo(30.0, Offset.offset(0.005)); - } - - @Test - void waitingTimeCalculationFailsForNullCreatedAt() { - request.setCreatedAt(null); - - assertThat(request.waitingTime()).isEmpty(); - } - - @Test - void waitingTimeCalculationFailsForNullFirstProcessedAt() { - request.getEventInfo().setFirstProcessedAt(null); - - assertThat(request.waitingTime()).isEmpty(); - } } diff --git a/src/test/java/nl/tudelft/queue/service/EditionStatusServiceTest.java b/src/test/java/nl/tudelft/queue/service/EditionStatusServiceTest.java index 71be45e54d85a953a68325dd308ec2144d049250..96f51fff72dc626ac4004891da61b57f4966fa77 100644 --- a/src/test/java/nl/tudelft/queue/service/EditionStatusServiceTest.java +++ b/src/test/java/nl/tudelft/queue/service/EditionStatusServiceTest.java @@ -17,6 +17,7 @@ */ package nl.tudelft.queue.service; +import static java.time.LocalDateTime.now; import static org.assertj.core.api.Assertions.assertThat; import java.time.LocalDateTime; @@ -150,8 +151,8 @@ public class EditionStatusServiceTest { new RoomDetailsDTO().id(32L).name("R1") }; private SessionDetailsDTO[] sessions = { - new SessionDetailsDTO().id(34L).start(LocalDateTime.of(2023, 1, 1, 0, 0, 0)) - .endTime(LocalDateTime.of(2023, 1, 1, 0, 0, 2)) + new SessionDetailsDTO().id(34L).start(LocalDateTime.now().minusHours(1L)) + .endTime(LocalDateTime.now().plusHours(3L)) }; @BeforeEach @@ -456,8 +457,14 @@ public class EditionStatusServiceTest { @Test void averageWaitingProcessingTimeEmptyTestShouldYieldZero() { - assertThat(ess.averageWaitingTime(List.of())).isEqualTo(0L); - assertThat(ess.averageProcessingTime(List.of())).isEqualTo(0L); + assertThat(ess.averageWaitingTime(List.of(), ChronoUnit.SECONDS)).isEqualTo(OptionalDouble.empty()); + assertThat(ess.averageWaitingTime(List.of(), ChronoUnit.MILLIS)).isEqualTo(OptionalDouble.empty()); + assertThat(ess.averageWaitingTime(List.of(), ChronoUnit.MINUTES)).isEqualTo(OptionalDouble.empty()); + assertThat(ess.averageProcessingTime(List.of(), ChronoUnit.SECONDS)) + .isEqualTo(OptionalDouble.empty()); + assertThat(ess.averageProcessingTime(List.of(), ChronoUnit.MILLIS)).isEqualTo(OptionalDouble.empty()); + assertThat(ess.averageProcessingTime(List.of(), ChronoUnit.MINUTES)) + .isEqualTo(OptionalDouble.empty()); } @@ -466,52 +473,50 @@ public class EditionStatusServiceTest { LocalDateTime now = LocalDateTime.now(); request1.getEventInfo().setFirstProcessedAt(now.minusSeconds(10L)); - Long result = ess.averageProcessingTime(List.of(request1)); + OptionalDouble result = ess.averageProcessingTime(List.of(request1), ChronoUnit.SECONDS); - assertThat(result).isCloseTo(10L * 1000, Percentage.withPercentage(5)); // 5% tolerance = 500ms + assertThat(result.getAsDouble()).isCloseTo(10L, Percentage.withPercentage(15)); } @Test void averageProcessingTimeNoTimeAssignedTest() { request1.getEventInfo().setStatus(RequestStatus.APPROVED); rr.save(request1); - Long result = ess.averageProcessingTime(List.of(request1)); + OptionalDouble result = ess.averageProcessingTime(List.of(request1), ChronoUnit.MILLIS); - assertThat(result).isEqualTo(0L); + assertThat(result).isEqualTo(OptionalDouble.empty()); } @Test - void averageProcessingTimeNotHandledButIsInTheFutureShouldReturnZero() { - LocalDateTime now = LocalDateTime.now(); + void averageProcessingTimeHandledButIsInTheFutureShouldReturnZero() { - request1.getEventInfo().setFirstProcessedAt(now.plusSeconds(1L)); - Long result = ess.averageProcessingTime(List.of(request1), ChronoUnit.MINUTES, now); + request1.getEventInfo().setFirstProcessedAt(now().plusSeconds(1L)); + OptionalDouble result = ess.averageProcessingTime(List.of(request1), ChronoUnit.MINUTES); - assertThat(result).isEqualTo(0L); + assertThat(result.getAsDouble()).isEqualTo(0.0d); } @Test void averageProcessingTimeTest() { + final LocalDateTime now = LocalDateTime.now(); request1.getEventInfo().setStatus(RequestStatus.APPROVED); request1.getEventInfo().setFirstProcessedAt(now.minusSeconds(12L)); request1.getEventInfo().setHandledAt(now); rr.save(request1); - Long res1 = ess.averageProcessingTime(List.of(request1)); - Long res2 = ess.averageProcessingTime(List.of(request1), ChronoUnit.SECONDS, now); - Long res3 = ess.averageProcessingTime(List.of(request1), ChronoUnit.SECONDS, now.minusSeconds(6L)); - Long res4 = ess.averageProcessingTime(List.of(request1), ChronoUnit.MINUTES, now); + OptionalDouble res1 = ess.averageProcessingTime(List.of(request1), ChronoUnit.MILLIS); + OptionalDouble res2 = ess.averageProcessingTime(List.of(request1), ChronoUnit.SECONDS); + OptionalDouble res4 = ess.averageProcessingTime(List.of(request1), ChronoUnit.MINUTES); request1.getEventInfo().setFirstProcessedAt(now.minusMinutes(2L)); - Long res5 = ess.averageProcessingTime(List.of(request1), ChronoUnit.MINUTES, now.minusMinutes(1L)); + OptionalDouble res5 = ess.averageProcessingTime(List.of(request1), ChronoUnit.MINUTES); - assertThat(res1).isEqualTo(12L * 1000L); - assertThat(res2).isEqualTo(12L); - assertThat(res3).isEqualTo(6L); - assertThat(res4).isEqualTo(0L); - assertThat(res5).isEqualTo(1L); + assertThat(res1.getAsDouble()).isEqualTo(12L * 1000L); + assertThat(res2.getAsDouble()).isEqualTo(12L); + assertThat(res4.getAsDouble()).isEqualTo(0L); + assertThat(res5.getAsDouble()).isEqualTo(2L); } @@ -520,15 +525,15 @@ public class EditionStatusServiceTest { request4.getEventInfo().setStatus(RequestStatus.APPROVED); request4.getEventInfo().setFirstProcessedAt(LocalDateTime.of(2023, 1, 1, 0, 0, 7)); rr.save(request4); - Long result = ess.averageWaitingTime(List.of(request4)); - assertThat(result).isEqualTo(1L * 1000); + OptionalDouble result = ess.averageWaitingTime(List.of(request4), ChronoUnit.MILLIS); + assertThat(result.getAsDouble()).isEqualTo(1L * 1000); } @Test void createBucketsOverCourseTest() { TreeSet<Long> result = ess.createBucketsOverCourse(List.of(lab), 1); + assertThat(result).hasSize(2); - assertThat(result).containsExactlyInAnyOrder(0L, 2L); } @Test diff --git a/src/test/java/nl/tudelft/queue/service/SessionStatusServiceTest.java b/src/test/java/nl/tudelft/queue/service/SessionStatusServiceTest.java index f452e3c04d3761648ab5475e72f4ee74677eea02..83134046d79931d49ede84aaf7b906ecf64b76ed 100644 --- a/src/test/java/nl/tudelft/queue/service/SessionStatusServiceTest.java +++ b/src/test/java/nl/tudelft/queue/service/SessionStatusServiceTest.java @@ -109,7 +109,9 @@ public class SessionStatusServiceTest { var sut = sessionStatusService.createRequestDistribution(oopNowRegularLab1, new HashSet<>(), 5); assertThat(sut).hasSize(1); var numOfResults = sut.stream() - .map(bucket -> bucket.getBucketData().values().stream().reduce(0L, Long::sum)) + .map(bucket -> bucket.getBucketData().values().stream() + .mapToLong(Number::longValue) + .reduce(0L, Long::sum)) .reduce(0L, Long::sum); assertThat(numOfResults).isEqualTo(50); }