From 303b3ac548a21f3ac3edd9f2a613e1aed66d13af Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 16 Jul 2024 09:38:29 +0200 Subject: [PATCH] SEBSERV-560 SEBSERV-563 implementation --- .../gui/content/monitoring/FinishedExam.java | 40 +++++++++ .../monitoring/MonitoringRunningExam.java | 5 +- .../MonitoringProctoringService.java | 11 +-- .../dao/ScreenProctoringGroupDAO.java | 5 +- .../impl/ScreenProctoringGroupDAOImpl.java | 63 ++++++++++---- .../session/ScreenProctoringService.java | 5 ++ .../ScreenProctoringAPIBinding.java | 87 +++++++++++-------- .../ScreenProctoringServiceImpl.java | 19 +++- 8 files changed, 167 insertions(+), 68 deletions(-) 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 index 047fd861..889ce0cc 100644 --- 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 @@ -8,11 +8,21 @@ package ch.ethz.seb.sebserver.gui.content.monitoring; +import static ch.ethz.seb.sebserver.gbl.model.user.UserFeatures.Feature.EXAM_SCREEN_PROCTORING; +import static ch.ethz.seb.sebserver.gbl.model.user.UserFeatures.Feature.MONITORING_RUNNING_EXAM_SCREEN_PROCTORING; + import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.function.BooleanSupplier; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; +import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings; +import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetScreenProctoringGroups; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.MonitoringProctoringService; +import org.apache.commons.lang3.BooleanUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.client.service.UrlLauncher; import org.eclipse.swt.widgets.Composite; @@ -93,6 +103,7 @@ public class FinishedExam implements TemplateComposer { private final RestService restService; private final I18nSupport i18nSupport; private final DownloadService downloadService; + private final MonitoringProctoringService monitoringProctoringService; private final String exportFileName; private final int pageSize; @@ -100,12 +111,14 @@ public class FinishedExam implements TemplateComposer { final ServerPushService serverPushService, final PageService pageService, final DownloadService downloadService, + final MonitoringProctoringService monitoringProctoringService, @Value("${sebserver.gui.seb.client.logs.export.filename:SEBClientLogs}") final String exportFileName, @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; this.restService = pageService.getRestService(); this.downloadService = downloadService; + this.monitoringProctoringService = monitoringProctoringService; this.exportFileName = exportFileName; this.pageSize = pageSize; @@ -209,6 +222,33 @@ public class FinishedExam implements TemplateComposer { .withExec(this::exportCSV) .noEventPropagation() .publish(); + + // screen proctoring link + final ScreenProctoringSettings screenProctoringSettings = new ScreenProctoringSettings(exam); + final boolean screenProctoringEnabled = + currentUser.isFeatureEnabled(MONITORING_RUNNING_EXAM_SCREEN_PROCTORING) + && BooleanUtils.toBoolean(screenProctoringSettings.enableScreenProctoring); + if (screenProctoringEnabled) { + this.pageService + .getRestService() + .getBuilder(GetScreenProctoringGroups.class) + .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) + .call() + .onError(error -> log.error("Failed to get screen proctoring group data:", error)) + .getOr(Collections.emptyList()) + .forEach(group -> { + actionBuilder + .newAction(ActionDefinition.MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP) + .withEntityKey(exam.getEntityKey()) + .withExec(_action -> monitoringProctoringService.openScreenProctoringTab( + screenProctoringSettings, + group, + _action)) + .withNameAttributes(group.name, group.size) + .noEventPropagation() + .publish(); + }); + } } private PageAction exportCSV(final PageAction action) { 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 b8654332..12516396 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 @@ -22,7 +22,6 @@ import java.util.function.Consumer; import java.util.function.Function; import ch.ethz.seb.sebserver.gbl.model.exam.AllowedSEBVersion; -import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.text.StringEscapeUtils; import org.eclipse.swt.SWT; @@ -391,7 +390,7 @@ public class MonitoringRunningExam implements TemplateComposer { .getBuilder(GetScreenProctoringGroups.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .call() - .onError(error -> log.error("Failed to get collecting room data:", error)) + .onError(error -> log.error("\"Failed to get screen proctoring group data:", error)) .getOr(Collections.emptyList()) : Collections.emptyList(); @@ -684,7 +683,7 @@ public class MonitoringRunningExam implements TemplateComposer { if (!this.actionItemPerClientGroup.isEmpty()) { - this.actionItemPerClientGroup.entrySet().stream().forEach(entry -> { + this.actionItemPerClientGroup.entrySet().forEach(entry -> { final int numOfConnections = monitoringStatus.getNumOfConnections(entry.getKey()); if (numOfConnections >= 0) { final TreeItem treeItem = entry.getValue(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java index 91bd9d63..fee1dbfc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/MonitoringProctoringService.java @@ -215,13 +215,8 @@ public class MonitoringProctoringService { this.pageService.publishAction( actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP) .withEntityKey(entityKey) - .withExec(_action -> openScreenProctoringTab( - settings, - group, - _action)) - .withNameAttributes( - group.name, - group.size) + .withExec(_action -> openScreenProctoringTab(settings, group, _action)) + .withNameAttributes(group.name, group.size) .noEventPropagation() .create(), _treeItem -> proctoringGUIService.registerScreeProctoringGroupAction(group, _treeItem)); @@ -308,7 +303,7 @@ public class MonitoringProctoringService { this.proctorRoomConnectionsPopup.show(pc, collectingRoom.subject); } - private PageAction openScreenProctoringTab( + public PageAction openScreenProctoringTab( final ScreenProctoringSettings settings, final ScreenProctoringGroup group, final PageAction _action) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ScreenProctoringGroupDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ScreenProctoringGroupDAO.java index cdb6dbe7..49f48fb6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ScreenProctoringGroupDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ScreenProctoringGroupDAO.java @@ -54,9 +54,7 @@ public interface ScreenProctoringGroupDAO { * * @param examId the exam identifier * @param maxSize the maximum size of connection collected in one collecting group. Size of 0 means no limit. - * @param newGroupFunction Function to create data for a new collecting group if needed. - * @return Result refer to the collecting group record of place or to an error when happened - * @throws If the Result contains a AllGroupsFullException, there must be created a new Group first */ + * @return Result refer to the collecting group record of place or to an error when happened*/ Result reservePlaceInCollectingGroup(Long examId, int maxSize); Result releasePlaceInCollectingGroup(Long examId, Long groupId); @@ -80,4 +78,5 @@ public interface ScreenProctoringGroupDAO { * @return Result refer to a collection of entity keys for all delete group records or to an error when happened */ Result> deleteGroups(Long examId); + void updateGroupSize(String groupUUID, Integer activeCount, Integer totalCount); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ScreenProctoringGroupDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ScreenProctoringGroupDAOImpl.java index 0459bafd..4a2e5334 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ScreenProctoringGroupDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ScreenProctoringGroupDAOImpl.java @@ -8,8 +8,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; -import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo; -import static org.mybatis.dynamic.sql.SqlBuilder.isIn; +import static org.mybatis.dynamic.sql.SqlBuilder.*; import java.util.Collection; import java.util.Collections; @@ -142,7 +141,8 @@ public class ScreenProctoringGroupDAOImpl implements ScreenProctoringGroupDAO { .findFirst(); if (room.isPresent()) { - return updateCollectingGroup(room.get()); + return room.get(); + //return updateCollectingGroup(room.get()); } else { throw new AllGroupsFullException(); } @@ -234,6 +234,31 @@ public class ScreenProctoringGroupDAOImpl implements ScreenProctoringGroupDAO { return tryCatch.onError(TransactionHandler::rollback); } + @Override + @Transactional + public void updateGroupSize( + final String groupUUID, + final Integer activeCount, + final Integer totalCount) { + + try { + + UpdateDSL.updateWithMapper( + this.screenProctoringGroopRecordMapper::update, + ScreenProctoringGroopRecordDynamicSqlSupport.screenProctoringGroopRecord) + .set(ScreenProctoringGroopRecordDynamicSqlSupport.size) + .equalTo(activeCount) + .where(ScreenProctoringGroopRecordDynamicSqlSupport.uuid, isEqualTo(groupUUID)) + .and(ScreenProctoringGroopRecordDynamicSqlSupport.size, isNotEqualTo(activeCount)) + .build() + .execute(); + + } catch (final Exception e) { + log.warn("Failed to update SPS group size: {}", e.getMessage()); + } + + } + private ScreenProctoringGroup toDomainModel(final ScreenProctoringGroopRecord record) { return new ScreenProctoringGroup( record.getId(), @@ -244,22 +269,22 @@ public class ScreenProctoringGroupDAOImpl implements ScreenProctoringGroupDAO { record.getData()); } - private ScreenProctoringGroopRecord updateCollectingGroup( - final ScreenProctoringGroopRecord screenProctoringGroopRecord) { - - final Long id = screenProctoringGroopRecord.getId(); - - UpdateDSL.updateWithMapper( - this.screenProctoringGroopRecordMapper::update, - ScreenProctoringGroopRecordDynamicSqlSupport.screenProctoringGroopRecord) - .set(ScreenProctoringGroopRecordDynamicSqlSupport.size) - .equalTo(screenProctoringGroopRecord.getSize() + 1) - .where(ScreenProctoringGroopRecordDynamicSqlSupport.id, isEqualTo(id)) - .build() - .execute(); - - return this.screenProctoringGroopRecordMapper.selectByPrimaryKey(id); - } +// private ScreenProctoringGroopRecord updateCollectingGroup( +// final ScreenProctoringGroopRecord screenProctoringGroupRecord) { +// +// final Long id = screenProctoringGroupRecord.getId(); +// +// UpdateDSL.updateWithMapper( +// this.screenProctoringGroopRecordMapper::update, +// ScreenProctoringGroopRecordDynamicSqlSupport.screenProctoringGroopRecord) +// .set(ScreenProctoringGroopRecordDynamicSqlSupport.size) +// .equalTo(screenProctoringGroupRecord.getSize() + 1) +// .where(ScreenProctoringGroopRecordDynamicSqlSupport.id, isEqualTo(id)) +// .build() +// .execute(); +// +// return this.screenProctoringGroopRecordMapper.selectByPrimaryKey(id); +// } public static final class AllGroupsFullException extends RuntimeException { private static final long serialVersionUID = 3283129187819160485L; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ScreenProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ScreenProctoringService.java index 36c0a309..b4612e5b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ScreenProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ScreenProctoringService.java @@ -33,6 +33,7 @@ public interface ScreenProctoringService extends SessionUpdateTask { @Override default void processSessionUpdateTask() { updateClientConnections(); + updateActiveGroups(); } boolean isScreenProctoringEnabled(Long examId); @@ -98,6 +99,10 @@ public interface ScreenProctoringService extends SessionUpdateTask { * SPS connection instruction to SEB client to connect and start sending screenshots. */ void updateClientConnections(); + /** This goes through all running exams with screen proctoring enabled and updates the group attributes + * (mainly the number of active clients in the group) by call ing SPS API and store newest data. */ + void updateActiveGroups(); + @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) void synchronizeSPSUser(final String userUUID); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java index eb0fa5d8..0219a18f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringAPIBinding.java @@ -82,6 +82,8 @@ class ScreenProctoringAPIBinding { String TOKEN_ENDPOINT = "/oauth/token"; String TEST_ENDPOINT = "/admin-api/v1/proctoring/group"; + String GROUP_COUNT_ENDPOINT = "/admin-api/v1/proctoring/active_counts"; + String USER_ACCOUNT_ENDPOINT = "/admin-api/v1/useraccount/"; String USERSYNC_SEBSERVER_ENDPOINT = USER_ACCOUNT_ENDPOINT + "usersync/sebserver"; String ENTITY_PRIVILEGES_ENDPOINT = USER_ACCOUNT_ENDPOINT + "entityprivilege"; @@ -151,7 +153,7 @@ class ScreenProctoringAPIBinding { } @JsonIgnoreProperties(ignoreUnknown = true) - static final class ExamUpdate { + final class ExamUpdate { @JsonProperty(EXAM.ATTR_NAME) final String name; @JsonProperty(EXAM.ATTR_DESCRIPTION) @@ -187,6 +189,27 @@ class ScreenProctoringAPIBinding { } } + @JsonIgnoreProperties(ignoreUnknown = true) + static final class GroupSessionCount { + @JsonProperty("uuid") + public final String groupUUID; + @JsonProperty("activeCount") + public final Integer activeCount; + @JsonProperty("totalCount") + public final Integer totalCount; + + @JsonCreator + public GroupSessionCount( + @JsonProperty("uuid") final String groupUUID, + @JsonProperty("activeCount") final Integer activeCount, + @JsonProperty("totalCount") final Integer totalCount) { + + this.groupUUID = groupUUID; + this.activeCount = activeCount; + this.totalCount = totalCount; + } + } + private final UserDAO userDAO; private final Cryptor cryptor; private final AsyncService asyncService; @@ -749,6 +772,35 @@ class ScreenProctoringAPIBinding { return; } + public Collection getActiveGroupSessionCounts() { + try { + + final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(null); + + final String uri = UriComponentsBuilder + .fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL()) + .path(SPS_API.GROUP_COUNT_ENDPOINT) + .build() + .toUriString(); + + + final ResponseEntity exchange = apiTemplate.exchange(uri, HttpMethod.POST); + if (exchange.getStatusCode() != HttpStatus.OK) { + log.error("Failed to request active group session counts: {}", exchange); + return Collections.emptyList(); + } + + return this.jsonMapper.readValue( + exchange.getBody(), + new TypeReference>() { + }); + + } catch (final Exception e) { + log.error("Failed to get active group session counts: {}", e.getMessage()); + return Collections.emptyList(); + } + } + private void synchronizeUserAccount( final String userUUID, final ScreenProctoringServiceOAuthTemplate apiTemplate) { @@ -1187,39 +1239,6 @@ class ScreenProctoringAPIBinding { return apiTemplateExam; } - -// if (this.apiTemplate == null || !this.apiTemplate.isValid(examId)) { -// if (examId != null) { -// -// if (log.isDebugEnabled()) { -// log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId); -// } -// -// final ScreenProctoringSettings settings = this.proctoringSettingsDAO -// .getScreenProctoringSettings(new EntityKey(examId, EntityType.EXAM)) -// .getOrThrow(); -// this.testConnection(settings).getOrThrow(); -// this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, settings); -// -// } else if (this.webserviceInfo.getScreenProctoringServiceBundle().bundled) { -// -// if (log.isDebugEnabled()) { -// log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId); -// } -// -// final WebserviceInfo.ScreenProctoringServiceBundle bundle = this.webserviceInfo -// .getScreenProctoringServiceBundle(); -// -// this.testConnection(bundle).getOrThrow(); -// this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, bundle); -// -// -// } else { -// throw new IllegalStateException("No SPS API access information found!"); -// } -// } -// -// return this.apiTemplate; } private static List getSupporterIds(final Exam exam) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java index 9e41e1bc..bd7812cd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java @@ -18,7 +18,6 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsSetupChangeEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.session.*; -import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -244,6 +243,24 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { } } + @Override + public void updateActiveGroups() { + try { + + screenProctoringAPIBinding + .getActiveGroupSessionCounts() + .forEach(groupCount -> { + screenProctoringGroupDAO.updateGroupSize( + groupCount.groupUUID, + groupCount.activeCount, + groupCount.totalCount); + }); + + } catch (final Exception e) { + log.warn("Failed to update actual group session counts."); + } + } + @Override public void notifyExamSaved(final Exam exam) { if (!this.isScreenProctoringEnabled(exam.id)) {