SEBSERV-560 SEBSERV-563 implementation
This commit is contained in:
		
							parent
							
								
									f2f8a561a8
								
							
						
					
					
						commit
						303b3ac548
					
				
					 8 changed files with 167 additions and 68 deletions
				
			
		|  | @ -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) { | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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<ScreenProctoringGroup> reservePlaceInCollectingGroup(Long examId, int maxSize); | ||||
| 
 | ||||
|     Result<ScreenProctoringGroup> 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<Collection<EntityKey>> deleteGroups(Long examId); | ||||
| 
 | ||||
|     void updateGroupSize(String groupUUID, Integer activeCount, Integer totalCount); | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<GroupSessionCount> 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<String> 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<Collection<GroupSessionCount>>() { | ||||
|                     }); | ||||
| 
 | ||||
|         } 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<String> getSupporterIds(final Exam exam) { | ||||
|  |  | |||
|  | @ -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)) { | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti