Merge pull request #105 from SafeExamBrowser/SEBSERV-478
add issue filters to exam monitoring
This commit is contained in:
		
						commit
						474eb4f062
					
				
					 11 changed files with 317 additions and 17 deletions
				
			
		|  | @ -219,6 +219,8 @@ public final class API { | ||||||
|     public static final String EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT = "/signature"; |     public static final String EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT = "/signature"; | ||||||
|     public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states"; |     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_CLIENT_GROUP_FILTER = "hidden-client-group"; | ||||||
|  |     public static final String EXAM_MONITORING_ISSUE_FILTER = "hidden-issues"; | ||||||
|  | 
 | ||||||
|     public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams"; |     public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams"; | ||||||
|     public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = |     public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = | ||||||
|             "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; |             "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; | ||||||
|  |  | ||||||
|  | @ -57,6 +57,17 @@ public final class ClientConnection implements GrantEntity { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public enum ConnectionIssueStatus { | ||||||
|  |         ASK_GRANTED(0), | ||||||
|  |         SEB_VERSION_GRANTED(1); | ||||||
|  | 
 | ||||||
|  |         public final int code; | ||||||
|  | 
 | ||||||
|  |         ConnectionIssueStatus(final int code){ | ||||||
|  |             this.code = code; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public final static List<String> ACTIVE_STATES = Utils.immutableListOf( |     public final static List<String> ACTIVE_STATES = Utils.immutableListOf( | ||||||
|             ConnectionStatus.ACTIVE.name(), |             ConnectionStatus.ACTIVE.name(), | ||||||
|             ConnectionStatus.READY.name(), |             ConnectionStatus.READY.name(), | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||||
| import com.fasterxml.jackson.annotation.JsonProperty; | import com.fasterxml.jackson.annotation.JsonProperty; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionIssueStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringData; | import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringData; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringDataView; | import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringDataView; | ||||||
| 
 | 
 | ||||||
|  | @ -27,6 +28,7 @@ public class MonitoringSEBConnectionData { | ||||||
|     public static final String ATTR_CONNECTIONS = "cons"; |     public static final String ATTR_CONNECTIONS = "cons"; | ||||||
|     public static final String ATTR_STATUS_MAPPING = "sm"; |     public static final String ATTR_STATUS_MAPPING = "sm"; | ||||||
|     public static final String ATTR_CLIENT_GROUP_MAPPING = "cgm"; |     public static final String ATTR_CLIENT_GROUP_MAPPING = "cgm"; | ||||||
|  |     public static final String ATTR_ISSUE_MAPPING = "im"; | ||||||
| 
 | 
 | ||||||
|     @JsonProperty(ATTR_CONNECTIONS) |     @JsonProperty(ATTR_CONNECTIONS) | ||||||
|     public final Collection<? extends ClientMonitoringDataView> monitoringData; |     public final Collection<? extends ClientMonitoringDataView> monitoringData; | ||||||
|  | @ -37,25 +39,33 @@ public class MonitoringSEBConnectionData { | ||||||
|     @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) |     @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) | ||||||
|     public final Map<Long, Integer> connectionsPerClientGroup; |     public final Map<Long, Integer> connectionsPerClientGroup; | ||||||
| 
 | 
 | ||||||
|  |     @JsonProperty(ATTR_ISSUE_MAPPING) | ||||||
|  |     public final int[] connectionPerIssue; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     @JsonCreator |     @JsonCreator | ||||||
|     public MonitoringSEBConnectionData( |     public MonitoringSEBConnectionData( | ||||||
|             @JsonProperty(ATTR_CONNECTIONS) final Collection<ClientMonitoringData> connections, |             @JsonProperty(ATTR_CONNECTIONS) final Collection<ClientMonitoringData> connections, | ||||||
|             @JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus, |             @JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus, | ||||||
|  |             @JsonProperty(ATTR_ISSUE_MAPPING) final int[] connectionPerIssue, | ||||||
|             @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) final Map<Long, Integer> connectionsPerClientGroup) { |             @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) final Map<Long, Integer> connectionsPerClientGroup) { | ||||||
| 
 | 
 | ||||||
|         this.monitoringData = connections; |         this.monitoringData = connections; | ||||||
|         this.connectionsPerStatus = connectionsPerStatus; |         this.connectionsPerStatus = connectionsPerStatus; | ||||||
|  |         this.connectionPerIssue = connectionPerIssue; | ||||||
|         this.connectionsPerClientGroup = connectionsPerClientGroup; |         this.connectionsPerClientGroup = connectionsPerClientGroup; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public MonitoringSEBConnectionData( |     public MonitoringSEBConnectionData( | ||||||
|             final int[] connectionsPerStatus, |             final int[] connectionsPerStatus, | ||||||
|             final Map<Long, Integer> connectionsPerClientGroup, |             final Map<Long, Integer> connectionsPerClientGroup, | ||||||
|  |             final int[] connectionsPerIssue, | ||||||
|             final Collection<? extends ClientMonitoringDataView> connections) { |             final Collection<? extends ClientMonitoringDataView> connections) { | ||||||
| 
 | 
 | ||||||
|         this.monitoringData = connections; |  | ||||||
|         this.connectionsPerStatus = connectionsPerStatus; |         this.connectionsPerStatus = connectionsPerStatus; | ||||||
|         this.connectionsPerClientGroup = connectionsPerClientGroup; |         this.connectionsPerClientGroup = connectionsPerClientGroup; | ||||||
|  |         this.connectionPerIssue = connectionsPerIssue; | ||||||
|  |         this.monitoringData = connections; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Collection<? extends ClientMonitoringDataView> getMonitoringData() { |     public Collection<? extends ClientMonitoringDataView> getMonitoringData() { | ||||||
|  | @ -82,6 +92,14 @@ public class MonitoringSEBConnectionData { | ||||||
|         return this.connectionsPerClientGroup.get(clientGroupId); |         return this.connectionsPerClientGroup.get(clientGroupId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @JsonIgnore | ||||||
|  |     public int getNumberOfConnection(final ConnectionIssueStatus connectionIssueStatus) { | ||||||
|  |         if (this.connectionPerIssue == null || this.connectionPerIssue.length <= connectionIssueStatus.code) { | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |         return this.connectionPerIssue[connectionIssueStatus.code]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public String toString() { |     public String toString() { | ||||||
|         final StringBuilder builder = new StringBuilder(); |         final StringBuilder builder = new StringBuilder(); | ||||||
|  |  | ||||||
|  | @ -47,8 +47,9 @@ public enum ActionCategory { | ||||||
|     VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0), |     VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0), | ||||||
|     STATE_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.statefilter"), 40), |     STATE_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.statefilter"), 40), | ||||||
|     GROUP_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.groupfilter"), 50), |     GROUP_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.groupfilter"), 50), | ||||||
|     PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60), |     ISSUE_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.issuefilter"), 60), | ||||||
|     SCREEN_PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.screenproctoring"), 65), |     PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 70), | ||||||
|  |     SCREEN_PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.screenproctoring"), 75), | ||||||
| 
 | 
 | ||||||
|     FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1); |     FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1030,6 +1030,30 @@ public enum ActionDefinition { | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.GROUP_FILTER), |             ActionCategory.GROUP_FILTER), | ||||||
| 
 | 
 | ||||||
|  |     MONITOR_EXAM_HIDE_ASK_GRANTED( | ||||||
|  |             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.askgranted"), | ||||||
|  |             ImageIcon.TOGGLE_OFF, | ||||||
|  |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|  |             ActionCategory.ISSUE_FILTER), | ||||||
|  | 
 | ||||||
|  |     MONITOR_EXAM_SHOW_ASK_GRANTED( | ||||||
|  |             new LocTextKey("sebserver.monitoring.exam.connection.action.show.askgranted"), | ||||||
|  |             ImageIcon.TOGGLE_ON, | ||||||
|  |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|  |             ActionCategory.ISSUE_FILTER), | ||||||
|  | 
 | ||||||
|  |     MONITOR_EXAM_HIDE_SEB_VERSION_GRANTED( | ||||||
|  |             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.sebversiongranted"), | ||||||
|  |             ImageIcon.TOGGLE_OFF, | ||||||
|  |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|  |             ActionCategory.ISSUE_FILTER), | ||||||
|  | 
 | ||||||
|  |     MONITOR_EXAM_SHOW_SEB_VERSION_GRANTED( | ||||||
|  |             new LocTextKey("sebserver.monitoring.exam.connection.action.show.sebversiongranted"), | ||||||
|  |             ImageIcon.TOGGLE_ON, | ||||||
|  |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|  |             ActionCategory.ISSUE_FILTER), | ||||||
|  | 
 | ||||||
|     MONITORING_EXAM_SEARCH_CONNECTIONS( |     MONITORING_EXAM_SEARCH_CONNECTIONS( | ||||||
|             new LocTextKey("sebserver.monitoring.search.action"), |             new LocTextKey("sebserver.monitoring.search.action"), | ||||||
|             ImageIcon.SEARCH, |             ImageIcon.SEARCH, | ||||||
|  |  | ||||||
|  | @ -43,6 +43,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature; | import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings; | import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionIssueStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup; | import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; | import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; | ||||||
|  | @ -437,6 +438,23 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|                 ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION, |                 ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION, | ||||||
|                 ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION); |                 ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION); | ||||||
| 
 | 
 | ||||||
|  |         addIssueFilterAction( | ||||||
|  |                 monitoringStatus, | ||||||
|  |                 statusFilterGUIUpdate, | ||||||
|  |                 actionBuilder, | ||||||
|  |                 clientTable, | ||||||
|  |                 ConnectionIssueStatus.ASK_GRANTED, | ||||||
|  |                 ActionDefinition.MONITOR_EXAM_SHOW_ASK_GRANTED, | ||||||
|  |                 ActionDefinition.MONITOR_EXAM_HIDE_ASK_GRANTED); | ||||||
|  |         addIssueFilterAction( | ||||||
|  |                 monitoringStatus, | ||||||
|  |                 statusFilterGUIUpdate, | ||||||
|  |                 actionBuilder, | ||||||
|  |                 clientTable, | ||||||
|  |                 ConnectionIssueStatus.SEB_VERSION_GRANTED, | ||||||
|  |                 ActionDefinition.MONITOR_EXAM_SHOW_SEB_VERSION_GRANTED, | ||||||
|  |                 ActionDefinition.MONITOR_EXAM_HIDE_SEB_VERSION_GRANTED); | ||||||
|  | 
 | ||||||
|         if (clientGroups != null && !clientGroups.isEmpty()) { |         if (clientGroups != null && !clientGroups.isEmpty()) { | ||||||
|             clientGroups.forEach(clientGroup -> { |             clientGroups.forEach(clientGroup -> { | ||||||
| 
 | 
 | ||||||
|  | @ -455,6 +473,34 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|         return statusFilterGUIUpdate; |         return statusFilterGUIUpdate; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private void addIssueFilterAction( | ||||||
|  |             final MonitoringFilter filter, | ||||||
|  |             final FilterGUIUpdate filterGUIUpdate, | ||||||
|  |             final PageActionBuilder actionBuilder, | ||||||
|  |             final ClientConnectionTable clientTable, | ||||||
|  |             final ConnectionIssueStatus connectionIssueStatus, | ||||||
|  |             final ActionDefinition showActionDef, | ||||||
|  |             final ActionDefinition hideActionDef) { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         final int numOfConnections = filter.getNumOfConnections(connectionIssueStatus); | ||||||
|  |         final PageAction action = actionBuilder.newAction(hideActionDef) | ||||||
|  |                 .withExec(hideIssueViewAction(filter, clientTable, connectionIssueStatus)) | ||||||
|  |                 .noEventPropagation() | ||||||
|  |                 .withSwitchAction( | ||||||
|  |                         actionBuilder.newAction(showActionDef) | ||||||
|  |                                 .withExec(showIssueViewAction(filter, clientTable, connectionIssueStatus)) | ||||||
|  |                                 .noEventPropagation() | ||||||
|  |                                 .withNameAttributes(numOfConnections) | ||||||
|  |                                 .create()) | ||||||
|  |                 .withNameAttributes(numOfConnections) | ||||||
|  |                 .create(); | ||||||
|  | 
 | ||||||
|  |         this.pageService.publishAction( | ||||||
|  |                 action, | ||||||
|  |                 treeItem -> filterGUIUpdate.register(treeItem, connectionIssueStatus)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private void addFilterAction( |     private void addFilterAction( | ||||||
|             final MonitoringFilter filter, |             final MonitoringFilter filter, | ||||||
|             final FilterGUIUpdate filterGUIUpdate, |             final FilterGUIUpdate filterGUIUpdate, | ||||||
|  | @ -544,6 +590,7 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
| 
 | 
 | ||||||
|         private final PolyglotPageService polyglotPageService; |         private final PolyglotPageService polyglotPageService; | ||||||
|         private final TreeItem[] actionItemPerStateFilter = new TreeItem[ConnectionStatus.values().length]; |         private final TreeItem[] actionItemPerStateFilter = new TreeItem[ConnectionStatus.values().length]; | ||||||
|  |         private final TreeItem[] actionItemPerIssueFilter = new TreeItem[ConnectionIssueStatus.values().length]; | ||||||
|         private final Map<Long, TreeItem> actionItemPerClientGroup = new HashMap<>(); |         private final Map<Long, TreeItem> actionItemPerClientGroup = new HashMap<>(); | ||||||
| 
 | 
 | ||||||
|         public FilterGUIUpdate(final PolyglotPageService polyglotPageService) { |         public FilterGUIUpdate(final PolyglotPageService polyglotPageService) { | ||||||
|  | @ -558,6 +605,10 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|             this.actionItemPerClientGroup.put(clientGroupId, item); |             this.actionItemPerClientGroup.put(clientGroupId, item); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         void register(final TreeItem item, final ConnectionIssueStatus status) { | ||||||
|  |             this.actionItemPerIssueFilter[status.code] = item; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         @Override |         @Override | ||||||
|         public void update(final MonitoringFilter monitoringStatus) { |         public void update(final MonitoringFilter monitoringStatus) { | ||||||
|             final ConnectionStatus[] states = ConnectionStatus.values(); |             final ConnectionStatus[] states = ConnectionStatus.values(); | ||||||
|  | @ -572,6 +623,19 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             final ConnectionIssueStatus[] connectionIssueStates = ConnectionIssueStatus.values(); | ||||||
|  |             for (int i = 0; i < connectionIssueStates.length; i++) { | ||||||
|  |                 final ConnectionIssueStatus state = connectionIssueStates[i]; | ||||||
|  |                 final int numOfConnections = monitoringStatus.getNumOfConnections(state); | ||||||
|  |                 if (numOfConnections >= 0 && this.actionItemPerIssueFilter[state.code] != null) { | ||||||
|  |                     final TreeItem treeItem = this.actionItemPerIssueFilter[state.code]; | ||||||
|  |                     final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY); | ||||||
|  |                     action.setTitleArgument(0, numOfConnections); | ||||||
|  |                     this.polyglotPageService.injectI18n(treeItem, action.getTitle()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|             if (!this.actionItemPerClientGroup.isEmpty()) { |             if (!this.actionItemPerClientGroup.isEmpty()) { | ||||||
|                 this.actionItemPerClientGroup.entrySet().stream().forEach(entry -> { |                 this.actionItemPerClientGroup.entrySet().stream().forEach(entry -> { | ||||||
|                     final int numOfConnections = monitoringStatus.getNumOfConnections(entry.getKey()); |                     final int numOfConnections = monitoringStatus.getNumOfConnections(entry.getKey()); | ||||||
|  | @ -639,6 +703,32 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private static Function<PageAction, PageAction> showIssueViewAction( | ||||||
|  |             final MonitoringFilter monitoringStatus, | ||||||
|  |             final ClientConnectionTable clientTable, | ||||||
|  |             final ConnectionIssueStatus connectionIssueStatus) { | ||||||
|  | 
 | ||||||
|  |         return action -> { | ||||||
|  |             monitoringStatus.showIssue(connectionIssueStatus); | ||||||
|  |             clientTable.removeSelection(); | ||||||
|  |             return action; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static Function<PageAction, PageAction> hideIssueViewAction( | ||||||
|  |             final MonitoringFilter monitoringStatus, | ||||||
|  |             final ClientConnectionTable clientTable, | ||||||
|  |             final ConnectionIssueStatus connectionIssueStatus) { | ||||||
|  | 
 | ||||||
|  |         return action -> { | ||||||
|  |             monitoringStatus.hideIssue(connectionIssueStatus); | ||||||
|  |             clientTable.removeSelection(); | ||||||
|  |             return action; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private Set<EntityKey> selectionForInstruction(final ClientConnectionTable clientTable) { |     private Set<EntityKey> selectionForInstruction(final ClientConnectionTable clientTable) { | ||||||
|         final Set<String> connectionTokens = clientTable.getConnectionTokens( |         final Set<String> connectionTokens = clientTable.getConnectionTokens( | ||||||
|                 cc -> cc.getStatus().clientActiveStatus, |                 cc -> cc.getStatus().clientActiveStatus, | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ import java.util.EnumSet; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| 
 | 
 | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
| import org.eclipse.swt.widgets.Composite; | import org.eclipse.swt.widgets.Composite; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
|  | @ -26,6 +27,7 @@ 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.ClientGroup; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; | 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.model.session.ClientConnection.ConnectionStatus; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionIssueStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; | import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||||
| import ch.ethz.seb.sebserver.gui.service.page.PageContext; | import ch.ethz.seb.sebserver.gui.service.page.PageContext; | ||||||
|  | @ -47,6 +49,9 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
|     static final Logger log = LoggerFactory.getLogger(FullPageMonitoringUpdate.class); |     static final Logger log = LoggerFactory.getLogger(FullPageMonitoringUpdate.class); | ||||||
| 
 | 
 | ||||||
|     private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER"; |     private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER"; | ||||||
|  | 
 | ||||||
|  |     private static final String USER_SESSION_ISSUE_FILTER_ATTRIBUTE = "USER_SESSION_ISSUE_FILTER"; | ||||||
|  | 
 | ||||||
|     private static final String USER_SESSION_GROUP_FILTER_ATTRIBUTE = "USER_SESSION_GROUP_FILTER"; |     private static final String USER_SESSION_GROUP_FILTER_ATTRIBUTE = "USER_SESSION_GROUP_FILTER"; | ||||||
| 
 | 
 | ||||||
|     private final ServerPushService serverPushService; |     private final ServerPushService serverPushService; | ||||||
|  | @ -59,6 +64,10 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
| 
 | 
 | ||||||
|     private final EnumSet<ConnectionStatus> statusFilter; |     private final EnumSet<ConnectionStatus> statusFilter; | ||||||
|     private String statusFilterParam = ""; |     private String statusFilterParam = ""; | ||||||
|  | 
 | ||||||
|  |     private final EnumSet<ConnectionIssueStatus> issueFilter; | ||||||
|  |     private String issueFilterParam = ""; | ||||||
|  | 
 | ||||||
|     private final Set<Long> clientGroupFilter; |     private final Set<Long> clientGroupFilter; | ||||||
|     private String clientGroupFilterParam = ""; |     private String clientGroupFilterParam = ""; | ||||||
|     private boolean filterChanged = false; |     private boolean filterChanged = false; | ||||||
|  | @ -83,7 +92,10 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
|         this.guiUpdates = guiUpdates; |         this.guiUpdates = guiUpdates; | ||||||
| 
 | 
 | ||||||
|         this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); |         this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); | ||||||
|         loadFilter(); |         loadStatusFilter(); | ||||||
|  | 
 | ||||||
|  |         this.issueFilter = EnumSet.noneOf(ConnectionIssueStatus.class); | ||||||
|  |         loadIssueFilter(); | ||||||
| 
 | 
 | ||||||
|         final Collection<ClientGroup> clientGroups = pageService.getRestService() |         final Collection<ClientGroup> clientGroups = pageService.getRestService() | ||||||
|                 .getBuilder(GetClientGroups.class) |                 .getBuilder(GetClientGroups.class) | ||||||
|  | @ -127,6 +139,16 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
|         return this.statusFilterParam; |         return this.statusFilterParam; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public EnumSet<ConnectionIssueStatus> getIssueFilter() { | ||||||
|  |         return this.issueFilter; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public String getIssueFilterParam() { | ||||||
|  |         return this.issueFilterParam; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean filterChanged() { |     public boolean filterChanged() { | ||||||
|         return this.filterChanged; |         return this.filterChanged; | ||||||
|  | @ -184,6 +206,18 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
|         saveFilter(); |         saveFilter(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public void hideIssue(final ConnectionIssueStatus connectionIssueStatus) { | ||||||
|  |         this.issueFilter.add(connectionIssueStatus); | ||||||
|  |         saveFilter(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void showIssue(final ConnectionIssueStatus connectionIssueStatus){ | ||||||
|  |         this.issueFilter.remove(connectionIssueStatus); | ||||||
|  |         saveFilter(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public MonitoringFullPageData getMonitoringFullPageData() { |     public MonitoringFullPageData getMonitoringFullPageData() { | ||||||
|         return this.monitoringFullPageData; |         return this.monitoringFullPageData; | ||||||
|  | @ -214,9 +248,10 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void updateBusinessData() { |     private void updateBusinessData() { | ||||||
| 
 |  | ||||||
|         RestCall<MonitoringFullPageData>.RestCallBuilder restCallBuilder = this.restCallBuilder |         RestCall<MonitoringFullPageData>.RestCallBuilder restCallBuilder = this.restCallBuilder | ||||||
|                 .withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam); |                 .withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam) | ||||||
|  |                 .withHeader(API.EXAM_MONITORING_ISSUE_FILTER, this.issueFilterParam); | ||||||
|  | 
 | ||||||
|         if (hasClientGroupFilter()) { |         if (hasClientGroupFilter()) { | ||||||
|             restCallBuilder = restCallBuilder |             restCallBuilder = restCallBuilder | ||||||
|                     .withHeader(API.EXAM_MONITORING_CLIENT_GROUP_FILTER, this.clientGroupFilterParam); |                     .withHeader(API.EXAM_MONITORING_CLIENT_GROUP_FILTER, this.clientGroupFilterParam); | ||||||
|  | @ -249,6 +284,11 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
|                     .putAttribute( |                     .putAttribute( | ||||||
|                             USER_SESSION_STATUS_FILTER_ATTRIBUTE, |                             USER_SESSION_STATUS_FILTER_ATTRIBUTE, | ||||||
|                             StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR)); |                             StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR)); | ||||||
|  |             this.pageService | ||||||
|  |                     .getCurrentUser() | ||||||
|  |                     .putAttribute( | ||||||
|  |                             USER_SESSION_ISSUE_FILTER_ATTRIBUTE, | ||||||
|  |                             StringUtils.join(this.issueFilter, Constants.LIST_SEPARATOR)); | ||||||
|             if (hasClientGroupFilter()) { |             if (hasClientGroupFilter()) { | ||||||
|                 this.pageService |                 this.pageService | ||||||
|                         .getCurrentUser() |                         .getCurrentUser() | ||||||
|  | @ -260,6 +300,7 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
|             log.warn("Failed to save status filter to user session"); |             log.warn("Failed to save status filter to user session"); | ||||||
|         } finally { |         } finally { | ||||||
|             this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); |             this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); | ||||||
|  |             this.issueFilterParam = StringUtils.join(this.issueFilter, Constants.LIST_SEPARATOR); | ||||||
|             if (hasClientGroupFilter()) { |             if (hasClientGroupFilter()) { | ||||||
|                 this.clientGroupFilterParam = StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR); |                 this.clientGroupFilterParam = StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR); | ||||||
|             } |             } | ||||||
|  | @ -267,7 +308,7 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void loadFilter() { |     private void loadStatusFilter() { | ||||||
|         try { |         try { | ||||||
|             final String attribute = this.pageService |             final String attribute = this.pageService | ||||||
|                     .getCurrentUser() |                     .getCurrentUser() | ||||||
|  | @ -306,6 +347,26 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private void loadIssueFilter() { | ||||||
|  |         try { | ||||||
|  |             final String attribute = this.pageService | ||||||
|  |                     .getCurrentUser() | ||||||
|  |                     .getAttribute(USER_SESSION_ISSUE_FILTER_ATTRIBUTE); | ||||||
|  |             this.issueFilter.clear(); | ||||||
|  |             if (attribute != null) { | ||||||
|  |                 Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR)) | ||||||
|  |                         .forEach(name -> this.issueFilter.add(ConnectionIssueStatus.valueOf(name))); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } catch (final Exception e) { | ||||||
|  |             log.warn("Failed to load status filter to user session"); | ||||||
|  |             this.issueFilter.clear(); | ||||||
|  |         } finally { | ||||||
|  |             this.issueFilterParam = StringUtils.join(this.issueFilter, Constants.LIST_SEPARATOR); | ||||||
|  |             this.filterChanged = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public void recoverFromDisposedRestTemplate(final Exception error) { |     public void recoverFromDisposedRestTemplate(final Exception error) { | ||||||
|         try { |         try { | ||||||
|             if (log.isDebugEnabled()) { |             if (log.isDebugEnabled()) { | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ import java.util.EnumSet; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; | 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.model.session.ClientConnection.ConnectionStatus; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionIssueStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringData; | import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringData; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup; | import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup; | ||||||
|  | @ -26,6 +27,10 @@ public interface MonitoringFilter { | ||||||
| 
 | 
 | ||||||
|     String getStatusFilterParam(); |     String getStatusFilterParam(); | ||||||
| 
 | 
 | ||||||
|  |     EnumSet<ConnectionIssueStatus> getIssueFilter(); | ||||||
|  | 
 | ||||||
|  |     String getIssueFilterParam(); | ||||||
|  | 
 | ||||||
|     boolean filterChanged(); |     boolean filterChanged(); | ||||||
| 
 | 
 | ||||||
|     void resetFilterChanged(); |     void resetFilterChanged(); | ||||||
|  | @ -48,6 +53,10 @@ public interface MonitoringFilter { | ||||||
| 
 | 
 | ||||||
|     void showClientGroup(Long clientGroupId); |     void showClientGroup(Long clientGroupId); | ||||||
| 
 | 
 | ||||||
|  |     void hideIssue(ConnectionIssueStatus connectionIssueStatus); | ||||||
|  | 
 | ||||||
|  |     void showIssue(ConnectionIssueStatus connectionIssueStatus); | ||||||
|  | 
 | ||||||
|     MonitoringFullPageData getMonitoringFullPageData(); |     MonitoringFullPageData getMonitoringFullPageData(); | ||||||
| 
 | 
 | ||||||
|     default MonitoringSEBConnectionData getMonitoringSEBConnectionData() { |     default MonitoringSEBConnectionData getMonitoringSEBConnectionData() { | ||||||
|  | @ -87,6 +96,15 @@ public interface MonitoringFilter { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     default int getNumOfConnections(final ConnectionIssueStatus connectionIssueStatus) { | ||||||
|  |         final MonitoringSEBConnectionData monitoringSEBConnectionData = getMonitoringSEBConnectionData(); | ||||||
|  |         if (monitoringSEBConnectionData != null) { | ||||||
|  |             return monitoringSEBConnectionData.getNumberOfConnection(connectionIssueStatus); | ||||||
|  |         } else { | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     default Collection<RemoteProctoringRoom> proctoringData() { |     default Collection<RemoteProctoringRoom> proctoringData() { | ||||||
|         final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData(); |         final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData(); | ||||||
|         if (monitoringFullPageData != null) { |         if (monitoringFullPageData != null) { | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ import java.io.OutputStream; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  | import java.util.EnumSet; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | @ -44,6 +45,7 @@ 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.exam.Exam.ExamStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionIssueStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringDataView; | import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringDataView; | ||||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; | import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; | ||||||
|  | @ -437,6 +439,11 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|                     ? new HashMap<>() |                     ? new HashMap<>() | ||||||
|                     : null; |                     : null; | ||||||
| 
 | 
 | ||||||
|  |             final int[] issueMapping = new int[ConnectionIssueStatus.values().length]; | ||||||
|  |             for (int i = 0; i < issueMapping.length; i++) { | ||||||
|  |                 issueMapping[i] = 0; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             updateClientConnections(examId); |             updateClientConnections(examId); | ||||||
| 
 | 
 | ||||||
|             final List<? extends ClientMonitoringDataView> filteredConnections = this.clientConnectionDAO |             final List<? extends ClientMonitoringDataView> filteredConnections = this.clientConnectionDAO | ||||||
|  | @ -448,6 +455,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|                     .map(c -> { |                     .map(c -> { | ||||||
|                         statusMapping[c.clientConnection.status.code]++; |                         statusMapping[c.clientConnection.status.code]++; | ||||||
|                         processClientGroupMapping(c.groups, clientGroupMapping); |                         processClientGroupMapping(c.groups, clientGroupMapping); | ||||||
|  |                         processIssueMapping(c.clientConnection, issueMapping); | ||||||
|                         return c; |                         return c; | ||||||
|                     }) |                     }) | ||||||
|                     .filter(filter) |                     .filter(filter) | ||||||
|  | @ -457,6 +465,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|             return new MonitoringSEBConnectionData( |             return new MonitoringSEBConnectionData( | ||||||
|                     statusMapping, |                     statusMapping, | ||||||
|                     clientGroupMapping, |                     clientGroupMapping, | ||||||
|  |                     issueMapping, | ||||||
|                     filteredConnections); |                     filteredConnections); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | @ -633,6 +642,20 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private void processIssueMapping(final ClientConnection clientConnection, final int[] issueMapping){ | ||||||
|  |         if (clientConnection == null || issueMapping == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(BooleanUtils.isFalse(clientConnection.securityCheckGranted)){ | ||||||
|  |             issueMapping[0] += 1; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(BooleanUtils.isFalse(clientConnection.clientVersionGranted)){ | ||||||
|  |             issueMapping[1] += 1; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private final Map<String, Long> duplicateCheck = new HashMap<>(); |     private final Map<String, Long> duplicateCheck = new HashMap<>(); | ||||||
| 
 | 
 | ||||||
|     private ClientConnectionDataInternal getForTokenAndCheckDuplication( |     private ClientConnectionDataInternal getForTokenAndCheckDuplication( | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ import java.util.stream.Stream; | ||||||
| import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||||
| import javax.validation.Valid; | import javax.validation.Valid; | ||||||
| 
 | 
 | ||||||
|  | import org.apache.commons.lang3.BooleanUtils; | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  | @ -48,6 +49,7 @@ import ch.ethz.seb.sebserver.gbl.model.Page; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Exam; | import ch.ethz.seb.sebserver.gbl.model.exam.Exam; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey; | import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionIssueStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; | import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification; | import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification; | ||||||
|  | @ -265,14 +267,14 @@ public class ExamMonitoringController { | ||||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, |                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, | ||||||
|             @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, |             @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( |             @RequestHeader(name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, required = false) final String hiddenClientGroups, | ||||||
|                     name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, |             @RequestHeader(name = API.EXAM_MONITORING_ISSUE_FILTER, required = false) final String hiddenIssues){ | ||||||
|                     required = false) final String hiddenClientGroups) { | 
 | ||||||
| 
 | 
 | ||||||
|         checkPrivileges(institutionId, examId); |         checkPrivileges(institutionId, examId); | ||||||
| 
 | 
 | ||||||
|         return this.examSessionService |         return this.examSessionService | ||||||
|                 .getConnectionData(examId, createMonitoringFilter(hiddenStates, hiddenClientGroups)) |                 .getConnectionData(examId, createMonitoringFilter(hiddenStates, hiddenClientGroups, hiddenIssues)) | ||||||
|                 .getOrThrow(); |                 .getOrThrow(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -315,9 +317,9 @@ public class ExamMonitoringController { | ||||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, |                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, | ||||||
|             @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, |             @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( |             @RequestHeader(name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, required = false) final String hiddenClientGroups, | ||||||
|                     name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, |             @RequestHeader(name = API.EXAM_MONITORING_ISSUE_FILTER, required = false) final String hiddenIssues){ | ||||||
|                     required = false) final String hiddenClientGroups) { | 
 | ||||||
| 
 | 
 | ||||||
|         // TODO respond this within another Thread-pool (Executor) |         // TODO respond this within another Thread-pool (Executor) | ||||||
|         // TODO try to cache some MonitoringSEBConnectionData throughout multiple requests (for about 2 sec.) |         // TODO try to cache some MonitoringSEBConnectionData throughout multiple requests (for about 2 sec.) | ||||||
|  | @ -328,7 +330,7 @@ public class ExamMonitoringController { | ||||||
|         final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService |         final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService | ||||||
|                 .getMonitoringSEBConnectionsData( |                 .getMonitoringSEBConnectionsData( | ||||||
|                         examId, |                         examId, | ||||||
|                         createMonitoringFilter(hiddenStates, hiddenClientGroups)) |                         createMonitoringFilter(hiddenStates, hiddenClientGroups, hiddenIssues)) | ||||||
|                 .getOrThrow(); |                 .getOrThrow(); | ||||||
| 
 | 
 | ||||||
|         final boolean proctoringEnabled = this.examAdminService.isProctoringEnabled(runningExam); |         final boolean proctoringEnabled = this.examAdminService.isProctoringEnabled(runningExam); | ||||||
|  | @ -544,6 +546,20 @@ public class ExamMonitoringController { | ||||||
|         return conn -> conn != null && !filterStates.contains(conn.clientConnection.status); |         return conn -> conn != null && !filterStates.contains(conn.clientConnection.status); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private Predicate<ClientConnectionData> activeIssueFilterSebVersion(final EnumSet<ConnectionIssueStatus> filterStates) { | ||||||
|  |         if(!filterStates.contains(ConnectionIssueStatus.SEB_VERSION_GRANTED)){ | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return conn -> conn != null && BooleanUtils.isFalse(conn.clientConnection.clientVersionGranted); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Predicate<ClientConnectionData> activeIssueFilterAsk(final EnumSet<ConnectionIssueStatus> filterStates) { | ||||||
|  |         if(!filterStates.contains(ConnectionIssueStatus.ASK_GRANTED)){ | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return conn -> conn != null && BooleanUtils.isFalse(conn.clientConnection.securityCheckGranted); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** If we have a filter criteria for ACTIVE connection, we shall filter only the active connections |     /** If we have a filter criteria for ACTIVE connection, we shall filter only the active connections | ||||||
|      * that has no incident. */ |      * that has no incident. */ | ||||||
|     private Predicate<ClientConnectionData> withActiveFilter(final EnumSet<ConnectionStatus> filterStates) { |     private Predicate<ClientConnectionData> withActiveFilter(final EnumSet<ConnectionStatus> filterStates) { | ||||||
|  | @ -560,7 +576,8 @@ public class ExamMonitoringController { | ||||||
| 
 | 
 | ||||||
|     private Predicate<ClientConnectionData> createMonitoringFilter( |     private Predicate<ClientConnectionData> createMonitoringFilter( | ||||||
|             final String hiddenStates, |             final String hiddenStates, | ||||||
|             final String hiddenClientGroups) { |             final String hiddenClientGroups, | ||||||
|  |             final String hiddenIssues) { | ||||||
| 
 | 
 | ||||||
|         final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class); |         final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class); | ||||||
|         if (StringUtils.isNotBlank(hiddenStates)) { |         if (StringUtils.isNotBlank(hiddenStates)) { | ||||||
|  | @ -581,6 +598,25 @@ public class ExamMonitoringController { | ||||||
|                         ? withActiveFilter(filterStates) |                         ? withActiveFilter(filterStates) | ||||||
|                         : noneActiveFilter(filterStates); |                         : noneActiveFilter(filterStates); | ||||||
| 
 | 
 | ||||||
|  |         final EnumSet<ConnectionIssueStatus> filterIssues = EnumSet.noneOf(ConnectionIssueStatus.class); | ||||||
|  |         if (StringUtils.isNotBlank(hiddenIssues)) { | ||||||
|  |             final String[] split = StringUtils.split(hiddenIssues, Constants.LIST_SEPARATOR); | ||||||
|  |             for (final String s : split) { | ||||||
|  |                 filterIssues.add(ConnectionIssueStatus.valueOf(s)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         final Predicate<ClientConnectionData> issueFilterSebVersion = | ||||||
|  |                 filterIssues.isEmpty() | ||||||
|  |                     ? null | ||||||
|  |                     : activeIssueFilterSebVersion(filterIssues); | ||||||
|  | 
 | ||||||
|  |         final Predicate<ClientConnectionData> issueFilterAsk = | ||||||
|  |                 filterIssues.isEmpty() | ||||||
|  |                     ? null | ||||||
|  |                     : activeIssueFilterAsk(filterIssues); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|         Set<Long> filterClientGroups = null; |         Set<Long> filterClientGroups = null; | ||||||
|         if (StringUtils.isNotBlank(hiddenClientGroups)) { |         if (StringUtils.isNotBlank(hiddenClientGroups)) { | ||||||
|             filterClientGroups = new HashSet<>(); |             filterClientGroups = new HashSet<>(); | ||||||
|  | @ -595,7 +631,18 @@ public class ExamMonitoringController { | ||||||
|             if (ccd == null) { |             if (ccd == null) { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|             return stateFilter.test(ccd) && ccd.filter(_filterClientGroups); | 
 | ||||||
|  |             boolean result = stateFilter.test(ccd) && ccd.filter(_filterClientGroups); | ||||||
|  | 
 | ||||||
|  |             if (issueFilterSebVersion != null) { | ||||||
|  |                 result = result && issueFilterSebVersion.test(ccd); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (issueFilterAsk != null) { | ||||||
|  |                 result = result && issueFilterAsk.test(ccd); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return result; | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2210,6 +2210,7 @@ sebserver.monitoring.exam.action.viewroom=View {0} ( {1} / {2} ) | ||||||
| sebserver.monitoring.exam.action.viewgroup=View {0} ( {1} ) | sebserver.monitoring.exam.action.viewgroup=View {0} ( {1} ) | ||||||
| sebserver.exam.monitoring.action.category.statefilter=State Filter | sebserver.exam.monitoring.action.category.statefilter=State Filter | ||||||
| sebserver.exam.monitoring.action.category.groupfilter=Client Group Filter | sebserver.exam.monitoring.action.category.groupfilter=Client Group Filter | ||||||
|  | sebserver.exam.monitoring.action.category.issuefilter=Issue Filter | ||||||
| sebserver.exam.overall.action.category.proctoring=Live Proctoring | sebserver.exam.overall.action.category.proctoring=Live Proctoring | ||||||
| sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall | sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall | ||||||
| sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall | sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall | ||||||
|  | @ -2301,6 +2302,10 @@ 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.show.undefined=Show Undefined ( {0} ) | ||||||
| sebserver.monitoring.exam.connection.action.hide.clientgroup=Hide {0} ( {1} ) | 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.show.clientgroup=Show {0} ( {1} ) | ||||||
|  | sebserver.monitoring.exam.connection.action.hide.askgranted=Hide Ask Granted ( {0} ) | ||||||
|  | sebserver.monitoring.exam.connection.action.show.askgranted=Show Ask Granted ( {0} ) | ||||||
|  | sebserver.monitoring.exam.connection.action.hide.sebversiongranted=Hide SEB Version Granted ( {0} ) | ||||||
|  | sebserver.monitoring.exam.connection.action.show.sebversiongranted=Show SEB Version Granted ( {0} ) | ||||||
| sebserver.monitoring.exam.connection.action.proctoring=Single Room Proctoring | sebserver.monitoring.exam.connection.action.proctoring=Single Room Proctoring | ||||||
| sebserver.monitoring.exam.connection.action.proctoring.examroom=Exam 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? | 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? | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Andreas Hefti
						Andreas Hefti