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_STATE_FILTER = "hidden-states"; | ||||
|     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_SEB_CONNECTION_TOKEN_PATH_SEGMENT = | ||||
|             "/{" + 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( | ||||
|             ConnectionStatus.ACTIVE.name(), | ||||
|             ConnectionStatus.READY.name(), | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | |||
| 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.ConnectionIssueStatus; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringData; | ||||
| 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_STATUS_MAPPING = "sm"; | ||||
|     public static final String ATTR_CLIENT_GROUP_MAPPING = "cgm"; | ||||
|     public static final String ATTR_ISSUE_MAPPING = "im"; | ||||
| 
 | ||||
|     @JsonProperty(ATTR_CONNECTIONS) | ||||
|     public final Collection<? extends ClientMonitoringDataView> monitoringData; | ||||
|  | @ -37,25 +39,33 @@ public class MonitoringSEBConnectionData { | |||
|     @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) | ||||
|     public final Map<Long, Integer> connectionsPerClientGroup; | ||||
| 
 | ||||
|     @JsonProperty(ATTR_ISSUE_MAPPING) | ||||
|     public final int[] connectionPerIssue; | ||||
| 
 | ||||
| 
 | ||||
|     @JsonCreator | ||||
|     public MonitoringSEBConnectionData( | ||||
|             @JsonProperty(ATTR_CONNECTIONS) final Collection<ClientMonitoringData> connections, | ||||
|             @JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus, | ||||
|             @JsonProperty(ATTR_ISSUE_MAPPING) final int[] connectionPerIssue, | ||||
|             @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) final Map<Long, Integer> connectionsPerClientGroup) { | ||||
| 
 | ||||
|         this.monitoringData = connections; | ||||
|         this.connectionsPerStatus = connectionsPerStatus; | ||||
|         this.connectionPerIssue = connectionPerIssue; | ||||
|         this.connectionsPerClientGroup = connectionsPerClientGroup; | ||||
|     } | ||||
| 
 | ||||
|     public MonitoringSEBConnectionData( | ||||
|             final int[] connectionsPerStatus, | ||||
|             final Map<Long, Integer> connectionsPerClientGroup, | ||||
|             final int[] connectionsPerIssue, | ||||
|             final Collection<? extends ClientMonitoringDataView> connections) { | ||||
| 
 | ||||
|         this.monitoringData = connections; | ||||
|         this.connectionsPerStatus = connectionsPerStatus; | ||||
|         this.connectionsPerClientGroup = connectionsPerClientGroup; | ||||
|         this.connectionPerIssue = connectionsPerIssue; | ||||
|         this.monitoringData = connections; | ||||
|     } | ||||
| 
 | ||||
|     public Collection<? extends ClientMonitoringDataView> getMonitoringData() { | ||||
|  | @ -82,6 +92,14 @@ public class MonitoringSEBConnectionData { | |||
|         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 | ||||
|     public String toString() { | ||||
|         final StringBuilder builder = new StringBuilder(); | ||||
|  |  | |||
|  | @ -47,8 +47,9 @@ public enum ActionCategory { | |||
|     VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0), | ||||
|     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), | ||||
|     SCREEN_PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.screenproctoring"), 65), | ||||
|     ISSUE_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.issuefilter"), 60), | ||||
|     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); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1030,6 +1030,30 @@ public enum ActionDefinition { | |||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||
|             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( | ||||
|             new LocTextKey("sebserver.monitoring.search.action"), | ||||
|             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.ScreenProctoringSettings; | ||||
| 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.ScreenProctoringGroup; | ||||
| 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_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()) { | ||||
|             clientGroups.forEach(clientGroup -> { | ||||
| 
 | ||||
|  | @ -455,6 +473,34 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|         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( | ||||
|             final MonitoringFilter filter, | ||||
|             final FilterGUIUpdate filterGUIUpdate, | ||||
|  | @ -544,6 +590,7 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
| 
 | ||||
|         private final PolyglotPageService polyglotPageService; | ||||
|         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<>(); | ||||
| 
 | ||||
|         public FilterGUIUpdate(final PolyglotPageService polyglotPageService) { | ||||
|  | @ -558,6 +605,10 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|             this.actionItemPerClientGroup.put(clientGroupId, item); | ||||
|         } | ||||
| 
 | ||||
|         void register(final TreeItem item, final ConnectionIssueStatus status) { | ||||
|             this.actionItemPerIssueFilter[status.code] = item; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void update(final MonitoringFilter monitoringStatus) { | ||||
|             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()) { | ||||
|                 this.actionItemPerClientGroup.entrySet().stream().forEach(entry -> { | ||||
|                     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) { | ||||
|         final Set<String> connectionTokens = clientTable.getConnectionTokens( | ||||
|                 cc -> cc.getStatus().clientActiveStatus, | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ import java.util.EnumSet; | |||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.eclipse.swt.widgets.Composite; | ||||
| 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.Indicator; | ||||
| 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.util.Utils; | ||||
| 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); | ||||
| 
 | ||||
|     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 final ServerPushService serverPushService; | ||||
|  | @ -59,6 +64,10 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | |||
| 
 | ||||
|     private final EnumSet<ConnectionStatus> statusFilter; | ||||
|     private String statusFilterParam = ""; | ||||
| 
 | ||||
|     private final EnumSet<ConnectionIssueStatus> issueFilter; | ||||
|     private String issueFilterParam = ""; | ||||
| 
 | ||||
|     private final Set<Long> clientGroupFilter; | ||||
|     private String clientGroupFilterParam = ""; | ||||
|     private boolean filterChanged = false; | ||||
|  | @ -83,7 +92,10 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | |||
|         this.guiUpdates = guiUpdates; | ||||
| 
 | ||||
|         this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); | ||||
|         loadFilter(); | ||||
|         loadStatusFilter(); | ||||
| 
 | ||||
|         this.issueFilter = EnumSet.noneOf(ConnectionIssueStatus.class); | ||||
|         loadIssueFilter(); | ||||
| 
 | ||||
|         final Collection<ClientGroup> clientGroups = pageService.getRestService() | ||||
|                 .getBuilder(GetClientGroups.class) | ||||
|  | @ -127,6 +139,16 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | |||
|         return this.statusFilterParam; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public EnumSet<ConnectionIssueStatus> getIssueFilter() { | ||||
|         return this.issueFilter; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getIssueFilterParam() { | ||||
|         return this.issueFilterParam; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean filterChanged() { | ||||
|         return this.filterChanged; | ||||
|  | @ -184,6 +206,18 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | |||
|         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 | ||||
|     public MonitoringFullPageData getMonitoringFullPageData() { | ||||
|         return this.monitoringFullPageData; | ||||
|  | @ -214,9 +248,10 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | |||
|     } | ||||
| 
 | ||||
|     private void updateBusinessData() { | ||||
| 
 | ||||
|         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()) { | ||||
|             restCallBuilder = restCallBuilder | ||||
|                     .withHeader(API.EXAM_MONITORING_CLIENT_GROUP_FILTER, this.clientGroupFilterParam); | ||||
|  | @ -249,6 +284,11 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | |||
|                     .putAttribute( | ||||
|                             USER_SESSION_STATUS_FILTER_ATTRIBUTE, | ||||
|                             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()) { | ||||
|                 this.pageService | ||||
|                         .getCurrentUser() | ||||
|  | @ -260,6 +300,7 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { | |||
|             log.warn("Failed to save status filter to user session"); | ||||
|         } finally { | ||||
|             this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); | ||||
|             this.issueFilterParam = StringUtils.join(this.issueFilter, Constants.LIST_SEPARATOR); | ||||
|             if (hasClientGroupFilter()) { | ||||
|                 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 { | ||||
|             final String attribute = this.pageService | ||||
|                     .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) { | ||||
|         try { | ||||
|             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.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.RemoteProctoringRoom; | ||||
| import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup; | ||||
|  | @ -26,6 +27,10 @@ public interface MonitoringFilter { | |||
| 
 | ||||
|     String getStatusFilterParam(); | ||||
| 
 | ||||
|     EnumSet<ConnectionIssueStatus> getIssueFilter(); | ||||
| 
 | ||||
|     String getIssueFilterParam(); | ||||
| 
 | ||||
|     boolean filterChanged(); | ||||
| 
 | ||||
|     void resetFilterChanged(); | ||||
|  | @ -48,6 +53,10 @@ public interface MonitoringFilter { | |||
| 
 | ||||
|     void showClientGroup(Long clientGroupId); | ||||
| 
 | ||||
|     void hideIssue(ConnectionIssueStatus connectionIssueStatus); | ||||
| 
 | ||||
|     void showIssue(ConnectionIssueStatus connectionIssueStatus); | ||||
| 
 | ||||
|     MonitoringFullPageData getMonitoringFullPageData(); | ||||
| 
 | ||||
|     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() { | ||||
|         final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData(); | ||||
|         if (monitoringFullPageData != null) { | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import java.io.OutputStream; | |||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.EnumSet; | ||||
| import java.util.HashMap; | ||||
| import java.util.HashSet; | ||||
| 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.session.ClientConnection; | ||||
| 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.ClientMonitoringDataView; | ||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; | ||||
|  | @ -437,6 +439,11 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
|                     ? new HashMap<>() | ||||
|                     : null; | ||||
| 
 | ||||
|             final int[] issueMapping = new int[ConnectionIssueStatus.values().length]; | ||||
|             for (int i = 0; i < issueMapping.length; i++) { | ||||
|                 issueMapping[i] = 0; | ||||
|             } | ||||
| 
 | ||||
|             updateClientConnections(examId); | ||||
| 
 | ||||
|             final List<? extends ClientMonitoringDataView> filteredConnections = this.clientConnectionDAO | ||||
|  | @ -448,6 +455,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
|                     .map(c -> { | ||||
|                         statusMapping[c.clientConnection.status.code]++; | ||||
|                         processClientGroupMapping(c.groups, clientGroupMapping); | ||||
|                         processIssueMapping(c.clientConnection, issueMapping); | ||||
|                         return c; | ||||
|                     }) | ||||
|                     .filter(filter) | ||||
|  | @ -457,6 +465,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
|             return new MonitoringSEBConnectionData( | ||||
|                     statusMapping, | ||||
|                     clientGroupMapping, | ||||
|                     issueMapping, | ||||
|                     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 ClientConnectionDataInternal getForTokenAndCheckDuplication( | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ import java.util.stream.Stream; | |||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.validation.Valid; | ||||
| 
 | ||||
| import org.apache.commons.lang3.BooleanUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.slf4j.Logger; | ||||
| 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.institution.SecurityKey; | ||||
| 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.ClientInstruction; | ||||
| 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, | ||||
|             @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_CLIENT_GROUP_FILTER, | ||||
|                     required = false) final String hiddenClientGroups) { | ||||
|             @RequestHeader(name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, required = false) final String hiddenClientGroups, | ||||
|             @RequestHeader(name = API.EXAM_MONITORING_ISSUE_FILTER, required = false) final String hiddenIssues){ | ||||
| 
 | ||||
| 
 | ||||
|         checkPrivileges(institutionId, examId); | ||||
| 
 | ||||
|         return this.examSessionService | ||||
|                 .getConnectionData(examId, createMonitoringFilter(hiddenStates, hiddenClientGroups)) | ||||
|                 .getConnectionData(examId, createMonitoringFilter(hiddenStates, hiddenClientGroups, hiddenIssues)) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -315,9 +317,9 @@ public class ExamMonitoringController { | |||
|                     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_CLIENT_GROUP_FILTER, | ||||
|                     required = false) final String hiddenClientGroups) { | ||||
|             @RequestHeader(name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, required = false) final String hiddenClientGroups, | ||||
|             @RequestHeader(name = API.EXAM_MONITORING_ISSUE_FILTER, required = false) final String hiddenIssues){ | ||||
| 
 | ||||
| 
 | ||||
|         // TODO respond this within another Thread-pool (Executor) | ||||
|         // 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 | ||||
|                 .getMonitoringSEBConnectionsData( | ||||
|                         examId, | ||||
|                         createMonitoringFilter(hiddenStates, hiddenClientGroups)) | ||||
|                         createMonitoringFilter(hiddenStates, hiddenClientGroups, hiddenIssues)) | ||||
|                 .getOrThrow(); | ||||
| 
 | ||||
|         final boolean proctoringEnabled = this.examAdminService.isProctoringEnabled(runningExam); | ||||
|  | @ -544,6 +546,20 @@ public class ExamMonitoringController { | |||
|         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 | ||||
|      * that has no incident. */ | ||||
|     private Predicate<ClientConnectionData> withActiveFilter(final EnumSet<ConnectionStatus> filterStates) { | ||||
|  | @ -560,7 +576,8 @@ public class ExamMonitoringController { | |||
| 
 | ||||
|     private Predicate<ClientConnectionData> createMonitoringFilter( | ||||
|             final String hiddenStates, | ||||
|             final String hiddenClientGroups) { | ||||
|             final String hiddenClientGroups, | ||||
|             final String hiddenIssues) { | ||||
| 
 | ||||
|         final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class); | ||||
|         if (StringUtils.isNotBlank(hiddenStates)) { | ||||
|  | @ -581,6 +598,25 @@ public class ExamMonitoringController { | |||
|                         ? withActiveFilter(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; | ||||
|         if (StringUtils.isNotBlank(hiddenClientGroups)) { | ||||
|             filterClientGroups = new HashSet<>(); | ||||
|  | @ -595,7 +631,18 @@ public class ExamMonitoringController { | |||
|             if (ccd == null) { | ||||
|                 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.exam.monitoring.action.category.statefilter=State 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.monitoring.exam.action.proctoring.openTownhall=Open 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.hide.clientgroup=Hide {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.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? | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Andreas Hefti
						Andreas Hefti