From 6d56e71dbef20785a38bbfe3da19c191309615aa Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 23 Mar 2022 08:06:25 +0100 Subject: [PATCH] SEBSERV-240 implementation --- .../ch/ethz/seb/sebserver/gbl/api/API.java | 1 + .../gui/content/action/ActionCategory.java | 4 +- .../gui/content/action/ActionDefinition.java | 9 ++ .../gui/content/activity/ActivitiesPane.java | 15 +- .../content/activity/ActivityDefinition.java | 1 + .../activity/PageStateDefinitionImpl.java | 6 + .../gui/content/monitoring/FinishedExam.java | 127 +++++++++++++++ .../content/monitoring/FinishedExamList.java | 149 ++++++++++++++++++ .../monitoring/MonitoringRunningExam.java | 7 +- .../api/session/GetFinishedExamPage.java | 41 +++++ .../session/ExamSessionService.java | 9 ++ .../session/impl/ExamSessionServiceImpl.java | 9 ++ .../api/ExamMonitoringController.java | 61 +++++++ src/main/resources/messages.properties | 27 ++++ 14 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamPage.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 2c5c715f..083cd248 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -193,6 +193,7 @@ public final class API { public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification"; public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection"; public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states"; + public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams"; public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java index ad3a207c..eed8b28a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java @@ -35,7 +35,9 @@ public enum ActionCategory { LOGS_SEB_CLIENT_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1), VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0), FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.filter"), 50), - PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60); + PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60), + + FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1); public final LocTextKey title; public final int slotPosition; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 2922a097..851baf74 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -832,6 +832,15 @@ public enum ActionDefinition { PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, ActionCategory.PROCTORING), + FINISHED_EXAM_VIEW_LIST( + new LocTextKey("sebserver.finished.action.list"), + PageStateDefinitionImpl.FINISHED_EXAM_LIST), + VIEW_EXAM_FROM_FINISHED_LIST( + new LocTextKey("sebserver.finished.exam.action.list.view"), + ImageIcon.SHOW, + PageStateDefinitionImpl.FINISHED_EXAM, + ActionCategory.FINISHED_EXAM_LIST), + LOGS_USER_ACTIVITY_LIST( new LocTextKey("sebserver.logs.activity.userlogs"), PageStateDefinitionImpl.USER_ACTIVITY_LOGS), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java index 35b35bd0..7a07a2f3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java @@ -339,14 +339,25 @@ public class ActivitiesPane implements TemplateComposer { // Monitoring exams if (isSupporter) { - final TreeItem clientConfig = this.widgetFactory.treeItemLocalized( + + final TreeItem monitoringExams = this.widgetFactory.treeItemLocalized( monitoring, ActivityDefinition.MONITORING_EXAMS.displayName); injectActivitySelection( - clientConfig, + monitoringExams, actionBuilder .newAction(ActionDefinition.RUNNING_EXAM_VIEW_LIST) .create()); + + final TreeItem clientConfig = this.widgetFactory.treeItemLocalized( + monitoring, + ActivityDefinition.FINISHED_EXAMS.displayName); + injectActivitySelection( + clientConfig, + actionBuilder + .newAction(ActionDefinition.FINISHED_EXAM_VIEW_LIST) + .create()); + } // SEB Client Logs diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java index a8d57e0d..190cc77f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java @@ -28,6 +28,7 @@ public enum ActivityDefinition implements Activity { SEB_CERTIFICATE_MANAGEMENT(new LocTextKey("sebserver.certificate.action.list")), MONITORING(new LocTextKey("sebserver.overall.activity.title.monitoring")), MONITORING_EXAMS(new LocTextKey("sebserver.monitoring.action.list")), + FINISHED_EXAMS(new LocTextKey("sebserver.finished.action.list")), SEB_CLIENT_LOGS(new LocTextKey("sebserver.logs.activity.seblogs")); public final LocTextKey displayName; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java index 54b5d28e..8365bfa2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java @@ -33,6 +33,8 @@ import ch.ethz.seb.sebserver.gui.content.exam.IndicatorTemplateForm; import ch.ethz.seb.sebserver.gui.content.exam.LmsSetupForm; import ch.ethz.seb.sebserver.gui.content.exam.LmsSetupList; import ch.ethz.seb.sebserver.gui.content.exam.QuizLookupList; +import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExam; +import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExamList; import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringClientConnection; import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringRunningExam; import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringRunningExamList; @@ -96,6 +98,10 @@ public enum PageStateDefinitionImpl implements PageStateDefinition { MONITORING_RUNNING_EXAM(Type.FORM_VIEW, MonitoringRunningExam.class, ActivityDefinition.MONITORING_EXAMS), MONITORING_CLIENT_CONNECTION(Type.FORM_VIEW, MonitoringClientConnection.class, ActivityDefinition.MONITORING_EXAMS), + FINISHED_EXAM_LIST(Type.LIST_VIEW, FinishedExamList.class, ActivityDefinition.FINISHED_EXAMS), + FINISHED_EXAM(Type.FORM_VIEW, FinishedExam.class, ActivityDefinition.FINISHED_EXAMS), + FINISHED_CLIENT_CONNECTION(Type.FORM_VIEW, MonitoringClientConnection.class, ActivityDefinition.FINISHED_EXAMS), + USER_ACTIVITY_LOGS(Type.LIST_VIEW, UserActivityLogs.class, ActivityDefinition.USER_ACTIVITY_LOGS), SEB_CLIENT_LOGS(Type.LIST_VIEW, SEBClientEvents.class, ActivityDefinition.SEB_CLIENT_LOGS) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java new file mode 100644 index 00000000..f611d80c --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.content.monitoring; + +import java.util.Collection; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.service.ResourceService; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageService; +import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder; +import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionPage; +import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; +import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; +import ch.ethz.seb.sebserver.gui.table.TableBuilder; +import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType; + +@Lazy +@Component +@GuiProfile +public class FinishedExam implements TemplateComposer { + + private static final LocTextKey TITLE_TEXT_KEY = + new LocTextKey("sebserver.finished.exam.connections.title"); + private static final LocTextKey EMPTY_LIST_TEXT_KEY = + new LocTextKey("sebserver.finished.exam.connections.empty"); + private static final LocTextKey TABLE_COLUMN_NAME = + new LocTextKey("sebserver.finished.exam.connections.name"); + private static final LocTextKey TABLE_COLUMN_INFO = + new LocTextKey("sebserver.finished.exam.connections.info"); + private static final LocTextKey TABLE_COLUMN_STATUS = + new LocTextKey("sebserver.finished.exam.connections.status"); + + private final TableFilterAttribute nameFilter = + new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_SESSION_ID); + private final TableFilterAttribute infoFilter = + new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.ATTR_INFO); + private final TableFilterAttribute statusFilter; + + private final PageService pageService; + private final RestService restService; + private final ResourceService resourceService; + private final int pageSize; + + public FinishedExam( + final ServerPushService serverPushService, + final PageService pageService, + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { + + this.pageService = pageService; + this.restService = pageService.getRestService(); + this.resourceService = pageService.getResourceService(); + this.pageSize = pageSize; + + this.statusFilter = new TableFilterAttribute( + CriteriaType.SINGLE_SELECTION, + ClientConnection.FILTER_ATTR_STATUS, + pageService.getResourceService()::localizedClientConnectionStatusResources); + } + + @Override + public void compose(final PageContext pageContext) { + final EntityKey examKey = pageContext.getEntityKey(); + + final RestService restService = this.pageService.getRestService(); + final PageActionBuilder actionBuilder = this.pageService + .pageActionBuilder(pageContext.clearEntityKeys()); + + final Collection indicators = restService.getBuilder(GetIndicators.class) + .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, examKey.modelId) + .call() + .getOrThrow(); + + final TableBuilder tableBuilder = + this.pageService.entityTableBuilder(restService.getRestCall(GetClientConnectionPage.class)) + .withEmptyMessage(EMPTY_LIST_TEXT_KEY) + .withPaging(10) + .withStaticFilter(ClientConnection.FILTER_ATTR_EXAM_ID, examKey.modelId) + + .withColumn(new ColumnDefinition<>( + Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID, + TABLE_COLUMN_NAME, + ClientConnection::getUserSessionId) + .withFilter(this.nameFilter)) + + .withColumn(new ColumnDefinition<>( + ClientConnection.ATTR_INFO, + TABLE_COLUMN_INFO, + ClientConnection::getInfo) + .withFilter(this.infoFilter)) + + .withColumn(new ColumnDefinition( + Domain.CLIENT_CONNECTION.ATTR_STATUS, + TABLE_COLUMN_STATUS, + row -> this.pageService.getResourceService() + .localizedClientConnectionStatusName(row.getStatus())) + .withFilter(this.statusFilter)) + + .withDefaultAction(t -> actionBuilder + .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION) + .withParentEntityKey(examKey) + .create()); + + tableBuilder.compose(pageContext); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java new file mode 100644 index 00000000..19fa548d --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.content.monitoring; + +import org.eclipse.swt.widgets.Composite; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; +import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; +import ch.ethz.seb.sebserver.gbl.model.user.UserRole; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; +import ch.ethz.seb.sebserver.gui.content.exam.ExamList; +import ch.ethz.seb.sebserver.gui.service.ResourceService; +import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageService; +import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder; +import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; +import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetFinishedExamPage; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; +import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; +import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; +import ch.ethz.seb.sebserver.gui.table.EntityTable; +import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; + +@Lazy +@Component +@GuiProfile +public class FinishedExamList implements TemplateComposer { + + private static final LocTextKey PAGE_TITLE_KEY = + new LocTextKey("sebserver.finished.exam.list.title"); + private final static LocTextKey EMPTY_SELECTION_TEXT_KEY = + new LocTextKey("sebserver.finished.exam.info.pleaseSelect"); + private final static LocTextKey COLUMN_TITLE_NAME_KEY = + new LocTextKey("sebserver.finished.exam.list.column.name"); + private final static LocTextKey COLUMN_TITLE_TYPE_KEY = + new LocTextKey("sebserver.finished.exam.list.column.type"); + private final static LocTextKey EMPTY_LIST_TEXT_KEY = + new LocTextKey("sebserver.finished.exam.list.empty"); + + private final TableFilterAttribute nameFilter = + new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME); + private final TableFilterAttribute typeFilter; + + private final PageService pageService; + private final ResourceService resourceService; + private final int pageSize; + + protected FinishedExamList( + final PageService pageService, + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { + + this.pageService = pageService; + this.resourceService = pageService.getResourceService(); + this.pageSize = pageSize; + + this.typeFilter = new TableFilterAttribute( + CriteriaType.SINGLE_SELECTION, + Exam.FILTER_ATTR_TYPE, + this.resourceService::examTypeResources); + } + + @Override + public void compose(final PageContext pageContext) { + final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); + final CurrentUser currentUser = this.resourceService.getCurrentUser(); + final RestService restService = this.resourceService.getRestService(); + final I18nSupport i18nSupport = this.resourceService.getI18nSupport(); + + // content page layout with title + final Composite content = widgetFactory.defaultPageLayout( + pageContext.getParent(), + PAGE_TITLE_KEY); + + final PageActionBuilder actionBuilder = this.pageService + .pageActionBuilder(pageContext.clearEntityKeys()); + + // table + final EntityTable table = + this.pageService.entityTableBuilder(restService.getRestCall(GetFinishedExamPage.class)) + .withEmptyMessage(EMPTY_LIST_TEXT_KEY) + .withPaging(this.pageSize) + .withRowDecorator(ExamList.decorateOnExamConsistency(this.pageService)) + .withDefaultSort(QuizData.QUIZ_ATTR_NAME) + + .withColumn(new ColumnDefinition<>( + QuizData.QUIZ_ATTR_NAME, + COLUMN_TITLE_NAME_KEY, + Exam::getName) + .withFilter(this.nameFilter) + .sortable()) + + .withColumn(new ColumnDefinition( + Domain.EXAM.ATTR_TYPE, + COLUMN_TITLE_TYPE_KEY, + this.resourceService::localizedExamTypeName) + .withFilter(this.typeFilter) + .sortable()) + + .withColumn(new ColumnDefinition<>( + QuizData.QUIZ_ATTR_START_TIME, + new LocTextKey( + "sebserver.finished.exam.list.column.startTime", + i18nSupport.getUsersTimeZoneTitleSuffix()), + Exam::getStartTime) + .sortable()) + + .withColumn(new ColumnDefinition<>( + QuizData.QUIZ_ATTR_END_TIME, + new LocTextKey( + "sebserver.finished.exam.list.column.endTime", + i18nSupport.getUsersTimeZoneTitleSuffix()), + Exam::getEndTime) + .sortable()) + + .withDefaultAction(actionBuilder + .newAction(ActionDefinition.VIEW_EXAM_FROM_FINISHED_LIST) + .create()) + + .withSelectionListener(this.pageService.getSelectionPublisher( + pageContext, + ActionDefinition.VIEW_EXAM_FROM_FINISHED_LIST)) + + .compose(pageContext.copyOf(content)); + + actionBuilder + + .newAction(ActionDefinition.VIEW_EXAM_FROM_FINISHED_LIST) + .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY) + .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false); + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java index aa61abd4..21800970 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java @@ -98,7 +98,7 @@ public class MonitoringRunningExam implements TemplateComposer { private final boolean distributedSetup; private final long pollInterval; - protected MonitoringRunningExam( + public MonitoringRunningExam( final ServerPushService serverPushService, final PageService pageService, final AsyncRunner asyncRunner, @@ -122,10 +122,9 @@ public class MonitoringRunningExam implements TemplateComposer { @Override public void compose(final PageContext pageContext) { - final RestService restService = this.resourceService.getRestService(); final EntityKey entityKey = pageContext.getEntityKey(); final CurrentUser currentUser = this.resourceService.getCurrentUser(); - final Exam exam = restService.getBuilder(GetExam.class) + final Exam exam = this.restService.getBuilder(GetExam.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .call() .getOrThrow(); @@ -134,7 +133,7 @@ public class MonitoringRunningExam implements TemplateComposer { exam.supporter.contains(user.uuid); final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN); - final Collection indicators = restService.getBuilder(GetIndicators.class) + final Collection indicators = this.restService.getBuilder(GetIndicators.class) .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, entityKey.modelId) .call() .getOrThrow(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamPage.java new file mode 100644 index 00000000..b70a7cf4 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamPage.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.Page; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class GetFinishedExamPage extends RestCall> { + + public GetFinishedExamPage() { + super(new TypeKey<>( + CallType.GET_PAGE, + EntityType.EXAM, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_MONITORING_ENDPOINT + API.EXAM_MONITORING_FINISHED_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java index e66ebdaa..24b85227 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java @@ -134,6 +134,15 @@ public interface ExamSessionService { FilterMap filterMap, Predicate predicate); + /** Gets all finished Exams for a particular FilterMap. + * + * @param filterMap the FilterMap containing the filter attributes + * @param predicate additional filter predicate + * @return Result referencing the list of all currently finished Exams or to an error if happened. */ + Result> getFilteredFinishedExams( + FilterMap filterMap, + Predicate predicate); + /** Streams the default SEB Exam Configuration to a ClientConnection with given connectionToken. * * @param institutionId the Institution identifier diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index c172a014..c32e8803 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -254,6 +254,15 @@ public class ExamSessionServiceImpl implements ExamSessionService { .collect(Collectors.toList())); } + @Override + public Result> getFilteredFinishedExams( + final FilterMap filterMap, + final Predicate predicate) { + + filterMap.putIfAbsent(Exam.FILTER_ATTR_STATUS, ExamStatus.FINISHED.name()); + return this.examDAO.allMatching(filterMap, predicate); + } + @Override public void streamDefaultExamConfig( final Long institutionId, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index 0c1639b2..b0712afd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -174,6 +174,67 @@ public class ExamMonitoringController { ExamAdministrationController.pageSort(sort)); } + /** Get a page of all currently finished exams + * + * GET /{api}/{entity-type-endpoint-name} + * + * GET /admin-api/v1/monitoring + * GET /admin-api/v1/monitoring?page_number=2&page_size=10&sort=-name + * GET /admin-api/v1/monitoring?name=seb&active=true + * + * @param institutionId The institution identifier of the request. + * Default is the institution identifier of the institution of the current user + * @param pageNumber the number of the page that is requested + * @param pageSize the size of the page that is requested + * @param sort the sort parameter to sort the list of entities before paging + * the sort parameter is the name of the entity-model attribute to sort with a leading '-' sign for + * descending sort order + * @param allRequestParams a MultiValueMap of all request parameter that is used for filtering + * @return Page of domain-model-entities of specified type */ + @RequestMapping( + path = API.EXAM_MONITORING_FINISHED_ENDPOINT, + method = RequestMethod.GET, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + public Page getFinishedExamsPage( + @RequestParam( + name = API.PARAM_INSTITUTION_ID, + required = true, + defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, + @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber, + @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize, + @RequestParam(name = Page.ATTR_SORT, required = false) final String sort, + @RequestParam final MultiValueMap allRequestParams, + final HttpServletRequest request) { + + this.authorization.checkRole( + institutionId, + EntityType.EXAM, + UserRole.EXAM_SUPPORTER, + UserRole.EXAM_ADMIN); + + final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString()); + + // if current user has no read access for specified entity type within other institution + // then the current users institutionId is put as a SQL filter criteria attribute to extends query performance + if (!this.authorization.hasGrant(PrivilegeType.READ, EntityType.EXAM)) { + filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId)); + } + + final Collection exams = this.examSessionService + .getFilteredFinishedExams( + filterMap, + exam -> this.hasRunningExamPrivilege(exam, institutionId)) + .getOrThrow(); + + return this.paginationService.buildPageFromList( + pageNumber, + pageSize, + sort, + exams, + ExamAdministrationController.pageSort(sort)); + } + @RequestMapping( path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT, method = RequestMethod.GET, diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index ef29c23f..c5d5dd54 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1868,6 +1868,33 @@ sebserver.monitoring.exam.connection.status.CLOSED=Closed sebserver.monitoring.exam.connection.status.ABORTED=Aborted sebserver.monitoring.exam.connection.status.DISABLED=Canceled +################################ +# Finished Exams +################################ +sebserver.finished.action.list=Finished Exams +sebserver.finished.exam.list.title=Finished Exams +sebserver.finished.exam.list.actions= + +sebserver.finished.exam.info.pleaseSelect=At first please select an Exam from the list +sebserver.finished.exam.list.empty=There are currently no finished exams + +sebserver.finished.exam.list.column.name=Name +sebserver.finished.exam.list.column.name.tooltip=The name of the exam

Use the filter above to narrow down to a specific exam name
{0} +sebserver.finished.exam.list.column.type=Type +sebserver.finished.exam.list.column.type.tooltip=The type of the exam

Use the filter above to set a specific exam type
{0} +sebserver.finished.exam.list.column.startTime=Start Time {0} +sebserver.finished.exam.list.column.startTime.tooltip=The start date and time of the exam

{0} +sebserver.finished.exam.list.column.endTime=End Time {0} +sebserver.finished.exam.list.column.endTime.tooltip=The end date and time of the exam

{0} +sebserver.finished.exam.action.list.view=View Finished Exam + +sebserver.finished.exam.connections.title=Search Connections +sebserver.finished.exam.connections.action=Search +sebserver.finished.exam.connections.empty=No Client Connections available +sebserver.finished.exam.connections.name=Session or User Name +sebserver.finished.exam.connections.info=Connection Info +sebserver.finished.exam.connections.status=Status + ################################ # Logs ################################