SEBSERV-163 finished grouping with filter
This commit is contained in:
		
							parent
							
								
									ee0034c367
								
							
						
					
					
						commit
						b5704dea95
					
				
					 18 changed files with 459 additions and 142 deletions
				
			
		|  | @ -197,6 +197,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_CLIENT_GROUP_FILTER = "hidden-client-group"; | ||||
|     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 + "}"; | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.CLIENT_GROUP; | |||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||
| 
 | ||||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||||
| public class ClientGroup implements ClientGroupData { | ||||
| public class ClientGroup implements ClientGroupData, Comparable<ClientGroup> { | ||||
| 
 | ||||
|     public static final String FILTER_ATTR_EXAM_ID = "examId"; | ||||
| 
 | ||||
|  | @ -285,4 +285,9 @@ public class ClientGroup implements ClientGroupData { | |||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int compareTo(final ClientGroup o) { | ||||
|         return o == null ? -1 : this.id.compareTo(o.id); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -84,6 +84,29 @@ public class ClientConnectionData implements GrantEntity { | |||
|         return this.groups != null && this.groups.contains(clientGroupId); | ||||
|     } | ||||
| 
 | ||||
|     @JsonIgnore | ||||
|     public boolean containsAllClientGroup(final Set<Long> clientGroupIds) { | ||||
|         if (this.groups == null || clientGroupIds == null || clientGroupIds.isEmpty()) { | ||||
|             return false; | ||||
|         } | ||||
|         return this.groups != null && this.groups.containsAll(clientGroupIds); | ||||
|     } | ||||
| 
 | ||||
|     @JsonIgnore | ||||
|     public boolean filter(final Set<Long> clientGroupIdsToHide) { | ||||
|         if (this.groups == null || clientGroupIdsToHide == null || clientGroupIdsToHide.isEmpty()) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         for (final Long id : this.groups) { | ||||
|             if (!clientGroupIdsToHide.contains(id)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public EntityType entityType() { | ||||
|         return this.clientConnection.entityType(); | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gbl.monitoring; | |||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
|  | @ -25,6 +26,7 @@ public class MonitoringSEBConnectionData { | |||
| 
 | ||||
|     public static final String ATTR_CONNECTIONS = "connections"; | ||||
|     public static final String ATTR_STATUS_MAPPING = "statusMapping"; | ||||
|     public static final String ATTR_CLIENT_GROUP_MAPPING = "clientGroupMapping"; | ||||
| 
 | ||||
|     @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) | ||||
|     public final Long examId; | ||||
|  | @ -32,16 +34,20 @@ public class MonitoringSEBConnectionData { | |||
|     public final Collection<ClientConnectionData> connections; | ||||
|     @JsonProperty(ATTR_STATUS_MAPPING) | ||||
|     public final int[] connectionsPerStatus; | ||||
|     @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) | ||||
|     public final Map<Long, Integer> connectionsPerClientGroup; | ||||
| 
 | ||||
|     @JsonCreator | ||||
|     public MonitoringSEBConnectionData( | ||||
|             @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) final Long examId, | ||||
|             @JsonProperty(ATTR_CONNECTIONS) final Collection<ClientConnectionData> connections, | ||||
|             @JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus) { | ||||
|             @JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus, | ||||
|             @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) final Map<Long, Integer> connectionsPerClientGroup) { | ||||
| 
 | ||||
|         this.examId = examId; | ||||
|         this.connections = connections; | ||||
|         this.connectionsPerStatus = connectionsPerStatus; | ||||
|         this.connectionsPerClientGroup = connectionsPerClientGroup; | ||||
|     } | ||||
| 
 | ||||
|     public Long getExamId() { | ||||
|  | @ -64,6 +70,14 @@ public class MonitoringSEBConnectionData { | |||
|         return this.connectionsPerStatus[status.code]; | ||||
|     } | ||||
| 
 | ||||
|     @JsonIgnore | ||||
|     public int getNumberOfConnection(final Long clientGroupId) { | ||||
|         if (this.connectionsPerClientGroup == null || !this.connectionsPerClientGroup.containsKey(clientGroupId)) { | ||||
|             return -1; | ||||
|         } | ||||
|         return this.connectionsPerClientGroup.get(clientGroupId); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         final int prime = 31; | ||||
|  |  | |||
|  | @ -42,7 +42,8 @@ public enum ActionCategory { | |||
|     LOGS_USER_ACTIVITY_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1), | ||||
|     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), | ||||
|     STATE_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.statefilter"), 40), | ||||
|     GROUP_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.groupfilter"), 50), | ||||
|     PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60), | ||||
| 
 | ||||
|     FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1); | ||||
|  |  | |||
|  | @ -882,44 +882,54 @@ public enum ActionDefinition { | |||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"), | ||||
|             ImageIcon.TOGGLE_OFF, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.FILTER), | ||||
|             ActionCategory.STATE_FILTER), | ||||
|     MONITOR_EXAM_SHOW_REQUESTED_CONNECTION( | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.show.requested"), | ||||
|             ImageIcon.TOGGLE_ON, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.FILTER), | ||||
|             ActionCategory.STATE_FILTER), | ||||
| 
 | ||||
|     MONITOR_EXAM_HIDE_ACTIVE_CONNECTION( | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.active"), | ||||
|             ImageIcon.TOGGLE_OFF, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.FILTER), | ||||
|             ActionCategory.STATE_FILTER), | ||||
|     MONITOR_EXAM_SHOW_ACTIVE_CONNECTION( | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.show.active"), | ||||
|             ImageIcon.TOGGLE_ON, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.FILTER), | ||||
| 
 | ||||
|             ActionCategory.STATE_FILTER), | ||||
|     MONITOR_EXAM_HIDE_CLOSED_CONNECTION( | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.closed"), | ||||
|             ImageIcon.TOGGLE_OFF, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.FILTER), | ||||
|             ActionCategory.STATE_FILTER), | ||||
|     MONITOR_EXAM_SHOW_CLOSED_CONNECTION( | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.show.closed"), | ||||
|             ImageIcon.TOGGLE_ON, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.FILTER), | ||||
|             ActionCategory.STATE_FILTER), | ||||
|     MONITOR_EXAM_HIDE_DISABLED_CONNECTION( | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.disabled"), | ||||
|             ImageIcon.TOGGLE_OFF, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.FILTER), | ||||
|             ActionCategory.STATE_FILTER), | ||||
|     MONITOR_EXAM_SHOW_DISABLED_CONNECTION( | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.show.disabled"), | ||||
|             ImageIcon.TOGGLE_ON, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.FILTER), | ||||
|             ActionCategory.STATE_FILTER), | ||||
| 
 | ||||
|     MONITOR_EXAM_HIDE_CLIENT_GROUP_CONNECTION( | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.clientgroup"), | ||||
|             ImageIcon.TOGGLE_OFF, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.GROUP_FILTER), | ||||
|     MONITOR_EXAM_SHOW_CLIENT_GROUP_CONNECTION( | ||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.show.clientgroup"), | ||||
|             ImageIcon.TOGGLE_ON, | ||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             ActionCategory.GROUP_FILTER), | ||||
| 
 | ||||
|     MONITORING_EXAM_SEARCH_CONNECTIONS( | ||||
|             new LocTextKey("sebserver.monitoring.search.action"), | ||||
|  |  | |||
|  | @ -247,26 +247,7 @@ public class ActionPane implements TemplateComposer { | |||
|                 actionsTitle.setData(RWT.CUSTOM_VARIANT, "close"); | ||||
|                 actionsTitle.setImage(WidgetFactory.ImageIcon.ACTIVE.getImage(parent.getDisplay())); | ||||
|                 actionsTitle.setText("    " + titleText); | ||||
|                 actionsTitle.addListener(SWT.MouseUp, event -> { | ||||
|                     try { | ||||
|                         final Control contentControl = composite.getChildren()[1]; | ||||
|                         if (contentControl.isVisible()) { | ||||
|                             actionsTitle.setData(RWT.CUSTOM_VARIANT, "open"); | ||||
|                             contentControl.setVisible(false); | ||||
|                             final GridData l = (GridData) contentControl.getLayoutData(); | ||||
|                             l.heightHint = 0; | ||||
|                             composite.getParent().layout(true, true); | ||||
|                         } else { | ||||
|                             actionsTitle.setData(RWT.CUSTOM_VARIANT, "close"); | ||||
|                             contentControl.setVisible(true); | ||||
|                             final GridData l = (GridData) contentControl.getLayoutData(); | ||||
|                             l.heightHint = SWT.DEFAULT; | ||||
|                             composite.getParent().layout(true, true); | ||||
|                         } | ||||
|                     } catch (final Exception e) { | ||||
|                         // just ignore | ||||
|                     } | ||||
|                 }); | ||||
|                 actionsTitle.addListener(SWT.MouseUp, event -> actionGroupExpand(composite, actionsTitle)); | ||||
|             } | ||||
| 
 | ||||
|             actionsTitle.setLayoutData(titleLayout); | ||||
|  | @ -319,6 +300,27 @@ public class ActionPane implements TemplateComposer { | |||
|         return actions; | ||||
|     } | ||||
| 
 | ||||
|     private void actionGroupExpand(final Composite composite, final Label actionsTitle) { | ||||
|         try { | ||||
|             final Control contentControl = composite.getChildren()[1]; | ||||
|             if (contentControl.isVisible()) { | ||||
|                 actionsTitle.setData(RWT.CUSTOM_VARIANT, "open"); | ||||
|                 contentControl.setVisible(false); | ||||
|                 final GridData l = (GridData) contentControl.getLayoutData(); | ||||
|                 l.heightHint = 0; | ||||
|                 composite.getParent().layout(true, true); | ||||
|             } else { | ||||
|                 actionsTitle.setData(RWT.CUSTOM_VARIANT, "close"); | ||||
|                 contentControl.setVisible(true); | ||||
|                 final GridData l = (GridData) contentControl.getLayoutData(); | ||||
|                 l.heightHint = SWT.DEFAULT; | ||||
|                 composite.getParent().layout(true, true); | ||||
|             } | ||||
|         } catch (final Exception e) { | ||||
|             // just ignore | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void clearDisposedTrees(final Map<String, Tree> actionTrees) { | ||||
|         new ArrayList<>(actionTrees.entrySet()) | ||||
|                 .forEach(entry -> { | ||||
|  |  | |||
|  | @ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.gui.content.monitoring; | |||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| import java.util.function.BooleanSupplier; | ||||
| import java.util.function.Consumer; | ||||
|  | @ -64,7 +66,7 @@ import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable; | |||
| import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringGUIUpdate; | ||||
| import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringUpdate; | ||||
| import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; | ||||
| import ch.ethz.seb.sebserver.gui.service.session.MonitoringStatus; | ||||
| import ch.ethz.seb.sebserver.gui.service.session.MonitoringFilter; | ||||
| import ch.ethz.seb.sebserver.gui.service.session.proctoring.MonitoringProctoringService; | ||||
| import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; | ||||
| 
 | ||||
|  | @ -258,6 +260,7 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
| 
 | ||||
|         if (isExamSupporter.getAsBoolean()) { | ||||
|             guiUpdates.add(createFilterActions( | ||||
|                     clientGroups, | ||||
|                     fullPageMonitoringUpdate, | ||||
|                     actionBuilder, | ||||
|                     clientTable, | ||||
|  | @ -347,13 +350,14 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|     } | ||||
| 
 | ||||
|     private FullPageMonitoringGUIUpdate createFilterActions( | ||||
|             final MonitoringStatus monitoringStatus, | ||||
|             final Collection<ClientGroup> clientGroups, | ||||
|             final MonitoringFilter monitoringStatus, | ||||
|             final PageActionBuilder actionBuilder, | ||||
|             final ClientConnectionTable clientTable, | ||||
|             final BooleanSupplier isExamSupporter) { | ||||
| 
 | ||||
|         final StatusFilterGUIUpdate statusFilterGUIUpdate = | ||||
|                 new StatusFilterGUIUpdate(this.pageService.getPolyglotPageService()); | ||||
|         final FilterGUIUpdate statusFilterGUIUpdate = | ||||
|                 new FilterGUIUpdate(this.pageService.getPolyglotPageService()); | ||||
| 
 | ||||
|         addFilterAction( | ||||
|                 monitoringStatus, | ||||
|  | @ -388,61 +392,118 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|                 ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION, | ||||
|                 ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION); | ||||
| 
 | ||||
|         if (clientGroups != null && !clientGroups.isEmpty()) { | ||||
|             clientGroups.forEach(clientGroup -> { | ||||
| 
 | ||||
|                 addClientGroupFilterAction( | ||||
|                         monitoringStatus, | ||||
|                         statusFilterGUIUpdate, | ||||
|                         actionBuilder, | ||||
|                         clientTable, | ||||
|                         clientGroup, | ||||
|                         ActionDefinition.MONITOR_EXAM_SHOW_CLIENT_GROUP_CONNECTION, | ||||
|                         ActionDefinition.MONITOR_EXAM_HIDE_CLIENT_GROUP_CONNECTION); | ||||
| 
 | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         return statusFilterGUIUpdate; | ||||
|     } | ||||
| 
 | ||||
|     private void addFilterAction( | ||||
|             final MonitoringStatus monitoringStatus, | ||||
|             final StatusFilterGUIUpdate statusFilterGUIUpdate, | ||||
|             final MonitoringFilter filter, | ||||
|             final FilterGUIUpdate filterGUIUpdate, | ||||
|             final PageActionBuilder actionBuilder, | ||||
|             final ClientConnectionTable clientTable, | ||||
|             final ConnectionStatus status, | ||||
|             final ActionDefinition showActionDef, | ||||
|             final ActionDefinition hideActionDef) { | ||||
| 
 | ||||
|         final int numOfConnections = monitoringStatus.getNumOfConnections(status); | ||||
|         if (monitoringStatus.isStatusHidden(status)) { | ||||
|             final PageAction showAction = actionBuilder.newAction(showActionDef) | ||||
|                     .withExec(showStateViewAction(monitoringStatus, clientTable, status)) | ||||
|         final int numOfConnections = filter.getNumOfConnections(status); | ||||
|         PageAction action; | ||||
|         if (filter.isStatusHidden(status)) { | ||||
|             action = actionBuilder.newAction(showActionDef) | ||||
|                     .withExec(showStateViewAction(filter, clientTable, status)) | ||||
|                     .noEventPropagation() | ||||
|                     .withSwitchAction( | ||||
|                             actionBuilder.newAction(hideActionDef) | ||||
|                                     .withExec( | ||||
|                                             hideStateViewAction(monitoringStatus, clientTable, status)) | ||||
|                                             hideStateViewAction(filter, clientTable, status)) | ||||
|                                     .noEventPropagation() | ||||
|                                     .withNameAttributes(numOfConnections) | ||||
|                                     .create()) | ||||
|                     .withNameAttributes(numOfConnections) | ||||
|                     .create(); | ||||
|             this.pageService.publishAction( | ||||
|                     showAction, | ||||
|                     treeItem -> statusFilterGUIUpdate.register(treeItem, status)); | ||||
|         } else { | ||||
|             final PageAction hideAction = actionBuilder.newAction(hideActionDef) | ||||
|                     .withExec(hideStateViewAction(monitoringStatus, clientTable, status)) | ||||
|             action = actionBuilder.newAction(hideActionDef) | ||||
|                     .withExec(hideStateViewAction(filter, clientTable, status)) | ||||
|                     .noEventPropagation() | ||||
|                     .withSwitchAction( | ||||
|                             actionBuilder.newAction(showActionDef) | ||||
|                                     .withExec( | ||||
|                                             showStateViewAction(monitoringStatus, clientTable, status)) | ||||
|                                             showStateViewAction(filter, clientTable, status)) | ||||
|                                     .noEventPropagation() | ||||
|                                     .withNameAttributes(numOfConnections) | ||||
|                                     .create()) | ||||
|                     .withNameAttributes(numOfConnections) | ||||
|                     .create(); | ||||
|             this.pageService.publishAction( | ||||
|                     hideAction, | ||||
|                     treeItem -> statusFilterGUIUpdate.register(treeItem, status)); | ||||
|         } | ||||
|         this.pageService.publishAction( | ||||
|                 action, | ||||
|                 treeItem -> filterGUIUpdate.register(treeItem, status)); | ||||
|     } | ||||
| 
 | ||||
|     private void addClientGroupFilterAction( | ||||
|             final MonitoringFilter filter, | ||||
|             final FilterGUIUpdate filterGUIUpdate, | ||||
|             final PageActionBuilder actionBuilder, | ||||
|             final ClientConnectionTable clientTable, | ||||
|             final ClientGroup clientGroup, | ||||
|             final ActionDefinition showActionDef, | ||||
|             final ActionDefinition hideActionDef) { | ||||
| 
 | ||||
|         final int numOfConnections = filter.getNumOfConnections(clientGroup.id); | ||||
|         PageAction action; | ||||
|         if (filter.isClientGroupHidden(clientGroup.id)) { | ||||
|             action = actionBuilder.newAction(showActionDef) | ||||
|                     .withExec(showClientGroupAction(filter, clientTable, clientGroup.id)) | ||||
|                     .noEventPropagation() | ||||
|                     .withSwitchAction( | ||||
|                             actionBuilder.newAction(hideActionDef) | ||||
|                                     .withExec( | ||||
|                                             hideClientGroupViewAction(filter, clientTable, clientGroup.id)) | ||||
|                                     .noEventPropagation() | ||||
|                                     .withNameAttributes(clientGroup.name, numOfConnections) | ||||
|                                     .create()) | ||||
|                     .withNameAttributes(clientGroup.name, numOfConnections) | ||||
|                     .create(); | ||||
|         } else { | ||||
|             action = actionBuilder.newAction(hideActionDef) | ||||
|                     .withExec(hideClientGroupViewAction(filter, clientTable, clientGroup.id)) | ||||
|                     .noEventPropagation() | ||||
|                     .withSwitchAction( | ||||
|                             actionBuilder.newAction(showActionDef) | ||||
|                                     .withExec( | ||||
|                                             showClientGroupAction(filter, clientTable, clientGroup.id)) | ||||
|                                     .noEventPropagation() | ||||
|                                     .withNameAttributes(clientGroup.name, numOfConnections) | ||||
|                                     .create()) | ||||
|                     .withNameAttributes(clientGroup.name, numOfConnections) | ||||
|                     .create(); | ||||
|         } | ||||
|         this.pageService.publishAction( | ||||
|                 action, | ||||
|                 treeItem -> filterGUIUpdate.register(treeItem, clientGroup.id)); | ||||
|     } | ||||
| 
 | ||||
|     /** This holds the filter action items and implements the specific GUI update for it */ | ||||
|     private class StatusFilterGUIUpdate implements FullPageMonitoringGUIUpdate { | ||||
|     private class FilterGUIUpdate implements FullPageMonitoringGUIUpdate { | ||||
| 
 | ||||
|         private final PolyglotPageService polyglotPageService; | ||||
|         private final TreeItem[] actionItemPerStateFilter = new TreeItem[ConnectionStatus.values().length]; | ||||
|         private final Map<Long, TreeItem> actionItemPerClientGroup = new HashMap<>(); | ||||
| 
 | ||||
|         public StatusFilterGUIUpdate(final PolyglotPageService polyglotPageService) { | ||||
|         public FilterGUIUpdate(final PolyglotPageService polyglotPageService) { | ||||
|             this.polyglotPageService = polyglotPageService; | ||||
|         } | ||||
| 
 | ||||
|  | @ -450,8 +511,12 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|             this.actionItemPerStateFilter[status.code] = item; | ||||
|         } | ||||
| 
 | ||||
|         void register(final TreeItem item, final Long clientGroupId) { | ||||
|             this.actionItemPerClientGroup.put(clientGroupId, item); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void update(final MonitoringStatus monitoringStatus) { | ||||
|         public void update(final MonitoringFilter monitoringStatus) { | ||||
|             final ConnectionStatus[] states = ConnectionStatus.values(); | ||||
|             for (int i = 0; i < states.length; i++) { | ||||
|                 final ConnectionStatus state = states[i]; | ||||
|  | @ -463,6 +528,18 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|                     this.polyglotPageService.injectI18n(treeItem, action.getTitle()); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (!this.actionItemPerClientGroup.isEmpty()) { | ||||
|                 this.actionItemPerClientGroup.entrySet().stream().forEach(entry -> { | ||||
|                     final int numOfConnections = monitoringStatus.getNumOfConnections(entry.getKey()); | ||||
|                     if (numOfConnections >= 0) { | ||||
|                         final TreeItem treeItem = entry.getValue(); | ||||
|                         final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY); | ||||
|                         action.setTitleArgument(1, numOfConnections); | ||||
|                         this.polyglotPageService.injectI18n(treeItem, action.getTitle()); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -472,7 +549,7 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|     } | ||||
| 
 | ||||
|     private static Function<PageAction, PageAction> showStateViewAction( | ||||
|             final MonitoringStatus monitoringStatus, | ||||
|             final MonitoringFilter monitoringStatus, | ||||
|             final ClientConnectionTable clientTable, | ||||
|             final ConnectionStatus status) { | ||||
| 
 | ||||
|  | @ -484,7 +561,7 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|     } | ||||
| 
 | ||||
|     private static Function<PageAction, PageAction> hideStateViewAction( | ||||
|             final MonitoringStatus monitoringStatus, | ||||
|             final MonitoringFilter monitoringStatus, | ||||
|             final ClientConnectionTable clientTable, | ||||
|             final ConnectionStatus status) { | ||||
| 
 | ||||
|  | @ -495,6 +572,30 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private static Function<PageAction, PageAction> showClientGroupAction( | ||||
|             final MonitoringFilter monitoringStatus, | ||||
|             final ClientConnectionTable clientTable, | ||||
|             final Long clientGroupId) { | ||||
| 
 | ||||
|         return action -> { | ||||
|             monitoringStatus.showClientGroup(clientGroupId); | ||||
|             clientTable.removeSelection(); | ||||
|             return action; | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private static Function<PageAction, PageAction> hideClientGroupViewAction( | ||||
|             final MonitoringFilter monitoringStatus, | ||||
|             final ClientConnectionTable clientTable, | ||||
|             final Long clientGroupId) { | ||||
| 
 | ||||
|         return action -> { | ||||
|             monitoringStatus.hideClientGroup(clientGroupId); | ||||
|             clientTable.removeSelection(); | ||||
|             return action; | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private Set<EntityKey> selectionForInstruction(final ClientConnectionTable clientTable) { | ||||
|         final Set<String> connectionTokens = clientTable.getConnectionTokens( | ||||
|                 cc -> cc.status.clientActiveStatus, | ||||
|  |  | |||
|  | @ -312,10 +312,10 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void update(final MonitoringStatus monitoringStatus) { | ||||
|     public void update(final MonitoringFilter monitoringStatus) { | ||||
|         final Collection<ClientConnectionData> connectionData = monitoringStatus.getConnectionData(); | ||||
|         final boolean sizeChanged = connectionData.size() != this.table.getItemCount(); | ||||
|         final boolean needsSync = monitoringStatus.statusFilterChanged() || | ||||
|         final boolean needsSync = monitoringStatus.filterChanged() || | ||||
|                 this.forceUpdateAll || | ||||
|                 sizeChanged || | ||||
|                 (this.tableMapping != null && | ||||
|  | @ -350,7 +350,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate | |||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|             monitoringStatus.resetStatusFilterChanged(); | ||||
|             monitoringStatus.resetFilterChanged(); | ||||
|             this.toDelete.clear(); | ||||
|         } | ||||
| 
 | ||||
|  | @ -624,7 +624,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate | |||
|         private String getGroupInfo() { | ||||
|             final StringBuilder sb = new StringBuilder(); | ||||
|             ClientConnectionTable.this.clientGroupMapping.keySet().stream().forEach(key -> { | ||||
|                 if (this.connectionData.groups.contains(key)) { | ||||
|                 if (this.connectionData.groups != null && this.connectionData.groups.contains(key)) { | ||||
|                     final ClientGroup clientGroup = ClientConnectionTable.this.clientGroupMapping.get(key); | ||||
|                     sb.append(WidgetFactory.getTextWithBackgroundHTML(clientGroup.name, clientGroup.color)); | ||||
|                 } | ||||
|  |  | |||
|  | @ -11,6 +11,6 @@ package ch.ethz.seb.sebserver.gui.service.session; | |||
| @FunctionalInterface | ||||
| public interface FullPageMonitoringGUIUpdate { | ||||
| 
 | ||||
|     void update(MonitoringStatus monitoringStatus); | ||||
|     void update(MonitoringFilter monitoringStatus); | ||||
| 
 | ||||
| } | ||||
|  | @ -10,7 +10,10 @@ package ch.ethz.seb.sebserver.gui.service.session; | |||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.EnumSet; | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.eclipse.swt.widgets.Composite; | ||||
|  | @ -20,6 +23,8 @@ import org.slf4j.LoggerFactory; | |||
| import ch.ethz.seb.sebserver.gbl.Constants; | ||||
| import ch.ethz.seb.sebserver.gbl.api.API; | ||||
| import ch.ethz.seb.sebserver.gbl.async.AsyncRunner; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; | ||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||
|  | @ -29,6 +34,7 @@ import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext; | |||
| import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; | ||||
| import ch.ethz.seb.sebserver.gui.service.push.UpdateErrorHandler; | ||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; | ||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroups; | ||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetMonitoringFullPageData; | ||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException; | ||||
| 
 | ||||
|  | @ -36,11 +42,12 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2Re | |||
|  * full page monitoring. | ||||
|  * | ||||
|  * This handles server push and GUI update and also implements kind of circuit breaker and error handling */ | ||||
| public class FullPageMonitoringUpdate implements MonitoringStatus { | ||||
| public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||
| 
 | ||||
|     static final Logger log = LoggerFactory.getLogger(FullPageMonitoringUpdate.class); | ||||
| 
 | ||||
|     private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER_ATTRIBUTE"; | ||||
|     private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER"; | ||||
|     private static final String USER_SESSION_GROUP_FILTER_ATTRIBUTE = "USER_SESSION_GROUP_FILTER"; | ||||
| 
 | ||||
|     private final ServerPushService serverPushService; | ||||
|     private final PageService pageService; | ||||
|  | @ -49,9 +56,13 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | |||
|     private final Collection<FullPageMonitoringGUIUpdate> guiUpdates; | ||||
| 
 | ||||
|     private ServerPushContext pushContext; | ||||
| 
 | ||||
|     private final EnumSet<ConnectionStatus> statusFilter; | ||||
|     private String statusFilterParam = ""; | ||||
|     private boolean statusFilterChanged = false; | ||||
|     private final Set<Long> clientGroupFilter; | ||||
|     private String clientGroupFilterParam = ""; | ||||
|     private boolean filterChanged = false; | ||||
| 
 | ||||
|     private boolean updateInProgress = false; | ||||
|     private MonitoringFullPageData monitoringFullPageData = null; | ||||
| 
 | ||||
|  | @ -72,7 +83,19 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | |||
|         this.guiUpdates = guiUpdates; | ||||
| 
 | ||||
|         this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); | ||||
|         loadStatusFilter(); | ||||
|         loadFilter(); | ||||
| 
 | ||||
|         final Collection<ClientGroup> clientGroups = pageService.getRestService() | ||||
|                 .getBuilder(GetClientGroups.class) | ||||
|                 .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, String.valueOf(examId)) | ||||
|                 .call() | ||||
|                 .getOr(Collections.emptyList()); | ||||
| 
 | ||||
|         if (clientGroups != null && !clientGroups.isEmpty()) { | ||||
|             this.clientGroupFilter = new HashSet<>(); | ||||
|         } else { | ||||
|             this.clientGroupFilter = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void start(final PageContext pageContext, final Composite anchor, final long pollInterval) { | ||||
|  | @ -105,13 +128,13 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean statusFilterChanged() { | ||||
|         return this.statusFilterChanged; | ||||
|     public boolean filterChanged() { | ||||
|         return this.filterChanged; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void resetStatusFilterChanged() { | ||||
|         this.statusFilterChanged = false; | ||||
|     public void resetFilterChanged() { | ||||
|         this.filterChanged = false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -122,13 +145,43 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | |||
|     @Override | ||||
|     public void hideStatus(final ConnectionStatus status) { | ||||
|         this.statusFilter.add(status); | ||||
|         saveStatusFilter(); | ||||
|         saveFilter(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void showStatus(final ConnectionStatus status) { | ||||
|         this.statusFilter.remove(status); | ||||
|         saveStatusFilter(); | ||||
|         saveFilter(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean hasClientGroupFilter() { | ||||
|         return this.clientGroupFilter != null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean isClientGroupHidden(final Long clientGroupId) { | ||||
|         return this.clientGroupFilter != null && this.clientGroupFilter.contains(clientGroupId); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void hideClientGroup(final Long clientGroupId) { | ||||
|         if (this.clientGroupFilter == null) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.clientGroupFilter.add(clientGroupId); | ||||
|         saveFilter(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void showClientGroup(final Long clientGroupId) { | ||||
|         if (this.clientGroupFilter == null) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.clientGroupFilter.remove(clientGroupId); | ||||
|         saveFilter(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -161,8 +214,15 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | |||
|     } | ||||
| 
 | ||||
|     private void updateBusinessData() { | ||||
|         this.monitoringFullPageData = this.restCallBuilder | ||||
|                 .withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam) | ||||
| 
 | ||||
|         RestCall<MonitoringFullPageData>.RestCallBuilder restCallBuilder = this.restCallBuilder | ||||
|                 .withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam); | ||||
|         if (hasClientGroupFilter()) { | ||||
|             restCallBuilder = restCallBuilder | ||||
|                     .withHeader(API.EXAM_MONITORING_CLIENT_GROUP_FILTER, this.clientGroupFilterParam); | ||||
|         } | ||||
| 
 | ||||
|         this.monitoringFullPageData = restCallBuilder | ||||
|                 .call() | ||||
|                 .get(error -> { | ||||
|                     recoverFromDisposedRestTemplate(error); | ||||
|  | @ -182,22 +242,32 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void saveStatusFilter() { | ||||
|     private void saveFilter() { | ||||
|         try { | ||||
|             this.pageService | ||||
|                     .getCurrentUser() | ||||
|                     .putAttribute( | ||||
|                             USER_SESSION_STATUS_FILTER_ATTRIBUTE, | ||||
|                             StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR)); | ||||
|             if (hasClientGroupFilter()) { | ||||
|                 this.pageService | ||||
|                         .getCurrentUser() | ||||
|                         .putAttribute( | ||||
|                                 USER_SESSION_GROUP_FILTER_ATTRIBUTE, | ||||
|                                 StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR)); | ||||
|             } | ||||
|         } catch (final Exception e) { | ||||
|             log.warn("Failed to save status filter to user session"); | ||||
|         } finally { | ||||
|             this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); | ||||
|             this.statusFilterChanged = true; | ||||
|             if (hasClientGroupFilter()) { | ||||
|                 this.clientGroupFilterParam = StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR); | ||||
|             } | ||||
|             this.filterChanged = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void loadStatusFilter() { | ||||
|     private void loadFilter() { | ||||
|         try { | ||||
|             final String attribute = this.pageService | ||||
|                     .getCurrentUser() | ||||
|  | @ -206,17 +276,33 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | |||
|             if (attribute != null) { | ||||
|                 Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR)) | ||||
|                         .forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name))); | ||||
| 
 | ||||
|             } else { | ||||
|                 this.statusFilter.add(ConnectionStatus.DISABLED); | ||||
|             } | ||||
| 
 | ||||
|             if (hasClientGroupFilter()) { | ||||
|                 final String groups = this.pageService | ||||
|                         .getCurrentUser() | ||||
|                         .getAttribute(USER_SESSION_GROUP_FILTER_ATTRIBUTE); | ||||
|                 this.statusFilter.clear(); | ||||
|                 if (groups != null) { | ||||
|                     Arrays.asList(StringUtils.split(groups, Constants.LIST_SEPARATOR)) | ||||
|                             .forEach(id -> this.clientGroupFilter.add(Long.parseLong(id))); | ||||
|                 } | ||||
|             } | ||||
|         } catch (final Exception e) { | ||||
|             log.warn("Failed to load status filter to user session"); | ||||
|             this.statusFilter.clear(); | ||||
|             this.statusFilter.add(ConnectionStatus.DISABLED); | ||||
|             if (hasClientGroupFilter()) { | ||||
|                 this.clientGroupFilter.clear(); | ||||
|             } | ||||
|         } finally { | ||||
|             this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); | ||||
|             this.statusFilterChanged = true; | ||||
|             if (hasClientGroupFilter()) { | ||||
|                 this.clientGroupFilterParam = StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR); | ||||
|             } | ||||
|             this.filterChanged = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,21 +12,22 @@ import java.util.Collection; | |||
| import java.util.Collections; | ||||
| import java.util.EnumSet; | ||||
| 
 | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; | ||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | ||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; | ||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; | ||||
| 
 | ||||
| public interface MonitoringStatus { | ||||
| public interface MonitoringFilter { | ||||
| 
 | ||||
|     EnumSet<ConnectionStatus> getStatusFilter(); | ||||
| 
 | ||||
|     String getStatusFilterParam(); | ||||
| 
 | ||||
|     boolean statusFilterChanged(); | ||||
|     boolean filterChanged(); | ||||
| 
 | ||||
|     void resetStatusFilterChanged(); | ||||
|     void resetFilterChanged(); | ||||
| 
 | ||||
|     boolean isStatusHidden(ConnectionStatus status); | ||||
| 
 | ||||
|  | @ -34,6 +35,18 @@ public interface MonitoringStatus { | |||
| 
 | ||||
|     void showStatus(ConnectionStatus status); | ||||
| 
 | ||||
|     boolean hasClientGroupFilter(); | ||||
| 
 | ||||
|     default boolean isClientGroupHidden(final ClientGroup clientGroup) { | ||||
|         return isClientGroupHidden(clientGroup.id); | ||||
|     } | ||||
| 
 | ||||
|     boolean isClientGroupHidden(Long clientGroupId); | ||||
| 
 | ||||
|     void hideClientGroup(Long clientGroupId); | ||||
| 
 | ||||
|     void showClientGroup(Long clientGroupId); | ||||
| 
 | ||||
|     MonitoringFullPageData getMonitoringFullPageData(); | ||||
| 
 | ||||
|     default MonitoringSEBConnectionData getMonitoringSEBConnectionData() { | ||||
|  | @ -63,6 +76,15 @@ public interface MonitoringStatus { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     default int getNumOfConnections(final Long clientGroupId) { | ||||
|         final MonitoringSEBConnectionData monitoringSEBConnectionData = getMonitoringSEBConnectionData(); | ||||
|         if (monitoringSEBConnectionData != null) { | ||||
|             return monitoringSEBConnectionData.getNumberOfConnection(clientGroupId); | ||||
|         } else { | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     default Collection<RemoteProctoringRoom> proctoringData() { | ||||
|         final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData(); | ||||
|         if (monitoringFullPageData != null) { | ||||
|  | @ -194,6 +194,7 @@ public class ClientGroupDAOImpl implements ClientGroupDAO { | |||
|                 .stream() | ||||
|                 .map(this::toDomainModel) | ||||
|                 .flatMap(DAOLoggingSupport::logAndSkipOnError) | ||||
|                 .sorted() | ||||
|                 .collect(Collectors.toList())); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,6 +39,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService | |||
| @WebServiceProfile | ||||
| public class ExamSessionCacheService { | ||||
| 
 | ||||
|     public static final Object CLIENT_CONECTION_CREATION_LOCK = new Object(); | ||||
| 
 | ||||
|     public static final String CACHE_NAME_RUNNING_EXAM = "RUNNING_EXAM"; | ||||
|     public static final String CACHE_NAME_ACTIVE_CLIENT_CONNECTION = "ACTIVE_CLIENT_CONNECTION"; | ||||
|     public static final String CACHE_NAME_SEB_CONFIG_EXAM = "SEB_CONFIG_EXAM"; | ||||
|  |  | |||
|  | @ -13,7 +13,9 @@ import java.io.OutputStream; | |||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.NoSuchElementException; | ||||
| import java.util.Objects; | ||||
| import java.util.Set; | ||||
|  | @ -34,6 +36,7 @@ import ch.ethz.seb.sebserver.gbl.Constants; | |||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage; | ||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; | ||||
| import ch.ethz.seb.sebserver.gbl.model.Entity; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Exam; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||
|  | @ -43,6 +46,7 @@ import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; | |||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientGroupDAO; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | ||||
|  | @ -64,6 +68,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
|     private final IndicatorDAO indicatorDAO; | ||||
|     private final ExamSessionCacheService examSessionCacheService; | ||||
|     private final ExamDAO examDAO; | ||||
|     private final ClientGroupDAO clientGroupDAO; | ||||
|     private final ExamConfigurationMapDAO examConfigurationMapDAO; | ||||
|     private final CacheManager cacheManager; | ||||
|     private final SEBRestrictionService sebRestrictionService; | ||||
|  | @ -76,6 +81,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
|     protected ExamSessionServiceImpl( | ||||
|             final ExamSessionCacheService examSessionCacheService, | ||||
|             final ExamDAO examDAO, | ||||
|             final ClientGroupDAO clientGroupDAO, | ||||
|             final ExamConfigurationMapDAO examConfigurationMapDAO, | ||||
|             final ClientConnectionDAO clientConnectionDAO, | ||||
|             final IndicatorDAO indicatorDAO, | ||||
|  | @ -87,6 +93,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
| 
 | ||||
|         this.examSessionCacheService = examSessionCacheService; | ||||
|         this.examDAO = examDAO; | ||||
|         this.clientGroupDAO = clientGroupDAO; | ||||
|         this.examConfigurationMapDAO = examConfigurationMapDAO; | ||||
|         this.clientConnectionDAO = clientConnectionDAO; | ||||
|         this.cacheManager = cacheManager; | ||||
|  | @ -350,7 +357,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
| 
 | ||||
|     @Override | ||||
|     public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) { | ||||
|         synchronized (this.examSessionCacheService) { | ||||
|         synchronized (ExamSessionCacheService.CLIENT_CONECTION_CREATION_LOCK) { | ||||
|             return this.examSessionCacheService.getClientConnection(connectionToken); | ||||
|         } | ||||
|     } | ||||
|  | @ -405,25 +412,33 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
|             for (int i = 0; i < statusMapping.length; i++) { | ||||
|                 statusMapping[i] = 0; | ||||
|             } | ||||
|             // needed to store connection numbers per client group too | ||||
|             final Collection<ClientGroup> groups = this.clientGroupDAO.allForExam(examId).getOr(null); | ||||
|             final Map<Long, Integer> clientGroupMapping = (groups != null && !groups.isEmpty()) | ||||
|                     ? new HashMap<>() | ||||
|                     : null; | ||||
| 
 | ||||
|             updateClientConnections(examId); | ||||
| 
 | ||||
|             synchronized (this.examSessionCacheService) { | ||||
|             final List<ClientConnectionData> filteredConnections = this.clientConnectionDAO | ||||
|                     .getConnectionTokens(examId) | ||||
|                     .getOrThrow() | ||||
|                     .stream() | ||||
|                         .map(token -> this.examSessionCacheService.getClientConnection(token)) | ||||
|                     .map(token -> getConnectionDataInternal(token)) | ||||
|                     .filter(Objects::nonNull) | ||||
|                     .map(c -> { | ||||
|                         statusMapping[c.clientConnection.status.code]++; | ||||
|                         processClientGroupMapping(c.groups, clientGroupMapping); | ||||
|                         return c; | ||||
|                     }) | ||||
|                     .filter(filter) | ||||
|                     .collect(Collectors.toList()); | ||||
| 
 | ||||
|                 return new MonitoringSEBConnectionData(examId, filteredConnections, statusMapping); | ||||
|             } | ||||
|             return new MonitoringSEBConnectionData( | ||||
|                     examId, | ||||
|                     filteredConnections, | ||||
|                     statusMapping, | ||||
|                     clientGroupMapping); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | @ -557,4 +572,18 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
|                 && Objects.equals(exam.name, runningExam.name); | ||||
|     } | ||||
| 
 | ||||
|     private void processClientGroupMapping(final Set<Long> groups, final Map<Long, Integer> clientGroupMapping) { | ||||
|         if (groups == null || clientGroupMapping == null) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         groups.forEach(id -> { | ||||
|             if (clientGroupMapping.containsKey(id)) { | ||||
|                 clientGroupMapping.put(id, clientGroupMapping.get(id) + 1); | ||||
|             } else { | ||||
|                 clientGroupMapping.put(id, 1); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -692,8 +692,10 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic | |||
| 
 | ||||
|     private void processPing(final String connectionToken, final long timestamp, final int pingNumber) { | ||||
| 
 | ||||
|         final ClientConnectionDataInternal activeClientConnection = | ||||
|                 this.examSessionCacheService.getClientConnection(connectionToken); | ||||
|         ClientConnectionDataInternal activeClientConnection = null; | ||||
|         synchronized (ExamSessionCacheService.CLIENT_CONECTION_CREATION_LOCK) { | ||||
|             activeClientConnection = this.examSessionCacheService.getClientConnection(connectionToken); | ||||
|         } | ||||
| 
 | ||||
|         if (activeClientConnection != null) { | ||||
|             activeClientConnection.notifyPing(timestamp, pingNumber); | ||||
|  |  | |||
|  | @ -11,7 +11,9 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; | |||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.EnumSet; | ||||
| import java.util.HashSet; | ||||
| import java.util.Objects; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.function.Predicate; | ||||
| 
 | ||||
|  | @ -248,31 +250,17 @@ public class ExamMonitoringController { | |||
|                     required = true, | ||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, | ||||
|             @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, | ||||
|             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates) { | ||||
|             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates, | ||||
|             @RequestHeader( | ||||
|                     name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, | ||||
|                     required = false) final String hiddenClientGroups) { | ||||
| 
 | ||||
|         checkPrivileges(institutionId, examId); | ||||
| 
 | ||||
|         final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class); | ||||
|         if (StringUtils.isNoneBlank(hiddenStates)) { | ||||
|             final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR); | ||||
|             for (int i = 0; i < split.length; i++) { | ||||
|                 filterStates.add(ConnectionStatus.valueOf(split[i])); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final boolean active = filterStates.contains(ConnectionStatus.ACTIVE); | ||||
|         if (active) { | ||||
|             filterStates.remove(ConnectionStatus.ACTIVE); | ||||
|         } | ||||
| 
 | ||||
|         return this.examSessionService | ||||
|                 .getMonitoringSEBConnectionsData( | ||||
|                         examId, | ||||
|                         filterStates.isEmpty() | ||||
|                                 ? Objects::nonNull | ||||
|                                 : active | ||||
|                                         ? withActiveFilter(filterStates) | ||||
|                                         : noneActiveFilter(filterStates)) | ||||
|                         createMonitoringFilter(hiddenStates, hiddenClientGroups)) | ||||
|                 .getOrThrow().connections; | ||||
|     } | ||||
| 
 | ||||
|  | @ -288,31 +276,17 @@ public class ExamMonitoringController { | |||
|                     required = true, | ||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, | ||||
|             @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, | ||||
|             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates) { | ||||
|             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates, | ||||
|             @RequestHeader( | ||||
|                     name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, | ||||
|                     required = false) final String hiddenClientGroups) { | ||||
| 
 | ||||
|         final Exam runningExam = checkPrivileges(institutionId, examId); | ||||
| 
 | ||||
|         final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class); | ||||
|         if (StringUtils.isNoneBlank(hiddenStates)) { | ||||
|             final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR); | ||||
|             for (int i = 0; i < split.length; i++) { | ||||
|                 filterStates.add(ConnectionStatus.valueOf(split[i])); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final boolean active = filterStates.contains(ConnectionStatus.ACTIVE); | ||||
|         if (active) { | ||||
|             filterStates.remove(ConnectionStatus.ACTIVE); | ||||
|         } | ||||
| 
 | ||||
|         final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService | ||||
|                 .getMonitoringSEBConnectionsData( | ||||
|                         examId, | ||||
|                         filterStates.isEmpty() | ||||
|                                 ? Objects::nonNull | ||||
|                                 : active | ||||
|                                         ? withActiveFilter(filterStates) | ||||
|                                         : noneActiveFilter(filterStates)) | ||||
|                         createMonitoringFilter(hiddenStates, hiddenClientGroups)) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) { | ||||
|  | @ -509,4 +483,46 @@ public class ExamMonitoringController { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private Predicate<ClientConnectionData> createMonitoringFilter( | ||||
|             final String hiddenStates, | ||||
|             final String hiddenClientGroups) { | ||||
| 
 | ||||
|         final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class); | ||||
|         if (StringUtils.isNotBlank(hiddenStates)) { | ||||
|             final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR); | ||||
|             for (int i = 0; i < split.length; i++) { | ||||
|                 filterStates.add(ConnectionStatus.valueOf(split[i])); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final boolean active = filterStates.contains(ConnectionStatus.ACTIVE); | ||||
|         if (active) { | ||||
|             filterStates.remove(ConnectionStatus.ACTIVE); | ||||
|         } | ||||
| 
 | ||||
|         final Predicate<ClientConnectionData> stateFilter = filterStates.isEmpty() | ||||
|                 ? Objects::nonNull | ||||
|                 : active | ||||
|                         ? withActiveFilter(filterStates) | ||||
|                         : noneActiveFilter(filterStates); | ||||
| 
 | ||||
|         Set<Long> filterClientGroups = null; | ||||
|         if (StringUtils.isNotBlank(hiddenClientGroups)) { | ||||
|             filterClientGroups = new HashSet<>(); | ||||
|             final String[] split = StringUtils.split(hiddenClientGroups, Constants.LIST_SEPARATOR); | ||||
|             for (int i = 0; i < split.length; i++) { | ||||
|                 filterClientGroups.add(Long.parseLong(split[i])); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final Set<Long> _filterClientGroups = filterClientGroups; | ||||
|         final Predicate<ClientConnectionData> filter = ccd -> { | ||||
|             if (ccd == null) { | ||||
|                 return false; | ||||
|             } | ||||
|             return stateFilter.test(ccd) && ccd.filter(_filterClientGroups); | ||||
|         }; | ||||
|         return filter; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1885,7 +1885,8 @@ sebserver.monitoring.exam.list.actions= | |||
| sebserver.monitoring.exam.action.detail.view=Back To Monitoring | ||||
| sebserver.monitoring.exam.action.list.view=Monitoring | ||||
| sebserver.monitoring.exam.action.viewroom=View {0}  ( {1} / {2} ) | ||||
| sebserver.exam.monitoring.action.category.filter=Filter | ||||
| sebserver.exam.monitoring.action.category.statefilter=State Filter | ||||
| sebserver.exam.monitoring.action.category.groupfilter=Client Group Filter | ||||
| sebserver.exam.overall.action.category.proctoring=Proctoring | ||||
| sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall | ||||
| sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall | ||||
|  | @ -1971,6 +1972,8 @@ sebserver.monitoring.exam.connection.action.hide.disabled=Hide Canceled ( {0} ) | |||
| sebserver.monitoring.exam.connection.action.show.disabled=Show Canceled ( {0} ) | ||||
| sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined ( {0} ) | ||||
| sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined ( {0} ) | ||||
| sebserver.monitoring.exam.connection.action.hide.clientgroup=Hide {0} ( {1} ) | ||||
| sebserver.monitoring.exam.connection.action.show.clientgroup=Show {0} ( {1} ) | ||||
| sebserver.monitoring.exam.connection.action.proctoring=Single Room Proctoring | ||||
| sebserver.monitoring.exam.connection.action.proctoring.examroom=Exam Room Proctoring | ||||
| sebserver.monitoring.exam.connection.action.openTownhall.confirm=You are about to open the town-hall room and force all SEB clients to join the town-hall room.<br/>Are you sure to open the town-hall? | ||||
|  | @ -1978,7 +1981,6 @@ sebserver.monitoring.exam.connection.action.closeTownhall.confirm=You are about | |||
| sebserver.monitoring.exam.connection.action.singleroom.confirm=You are about to open the single/one to one room for this participant.<br/>Are you sure you want to open the single room? | ||||
| sebserver.monitoring.exam.connection.actions.group2=  | ||||
| sebserver.monitoring.exam.connection.actions.group3=  | ||||
| 
 | ||||
| sebserver.monitoring.exam.connection.notificationlist.actions= | ||||
| sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification | ||||
| sebserver.monitoring.exam.connection.action.confirm.notification.text=Are you sure you want to confirm this pending notification?<br/><br/>Note that this will send a notification confirmation instruction to the SEB client and remove this notification from the pending list. | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti