diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 4f06b048..5f963fe7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -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 + "}"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java index 52e6c283..bd4741bb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java @@ -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 ACTIVE_STATES = Utils.immutableListOf( ConnectionStatus.ACTIVE.name(), ConnectionStatus.READY.name(), diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringSEBConnectionData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringSEBConnectionData.java index cf25d1a2..321e6dcd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringSEBConnectionData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/MonitoringSEBConnectionData.java @@ -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 monitoringData; @@ -37,25 +39,33 @@ public class MonitoringSEBConnectionData { @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) public final Map connectionsPerClientGroup; + @JsonProperty(ATTR_ISSUE_MAPPING) + public final int[] connectionPerIssue; + + @JsonCreator public MonitoringSEBConnectionData( @JsonProperty(ATTR_CONNECTIONS) final Collection connections, @JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus, + @JsonProperty(ATTR_ISSUE_MAPPING) final int[] connectionPerIssue, @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) final Map connectionsPerClientGroup) { this.monitoringData = connections; this.connectionsPerStatus = connectionsPerStatus; + this.connectionPerIssue = connectionPerIssue; this.connectionsPerClientGroup = connectionsPerClientGroup; } public MonitoringSEBConnectionData( final int[] connectionsPerStatus, final Map connectionsPerClientGroup, + final int[] connectionsPerIssue, final Collection connections) { - this.monitoringData = connections; this.connectionsPerStatus = connectionsPerStatus; this.connectionsPerClientGroup = connectionsPerClientGroup; + this.connectionPerIssue = connectionsPerIssue; + this.monitoringData = connections; } public Collection 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(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java index a4a1ffce..c1caa426 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java @@ -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); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 84ba0451..b0416cb0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -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, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java index c6252619..3c1372bb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java @@ -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 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 showIssueViewAction( + final MonitoringFilter monitoringStatus, + final ClientConnectionTable clientTable, + final ConnectionIssueStatus connectionIssueStatus) { + + return action -> { + monitoringStatus.showIssue(connectionIssueStatus); + clientTable.removeSelection(); + return action; + }; + + } + + private static Function hideIssueViewAction( + final MonitoringFilter monitoringStatus, + final ClientConnectionTable clientTable, + final ConnectionIssueStatus connectionIssueStatus) { + + return action -> { + monitoringStatus.hideIssue(connectionIssueStatus); + clientTable.removeSelection(); + return action; + }; + + } + private Set selectionForInstruction(final ClientConnectionTable clientTable) { final Set connectionTokens = clientTable.getConnectionTokens( cc -> cc.getStatus().clientActiveStatus, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/FullPageMonitoringUpdate.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/FullPageMonitoringUpdate.java index 8be452de..491d9801 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/FullPageMonitoringUpdate.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/FullPageMonitoringUpdate.java @@ -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 statusFilter; private String statusFilterParam = ""; + + private final EnumSet issueFilter; + private String issueFilterParam = ""; + private final Set 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 clientGroups = pageService.getRestService() .getBuilder(GetClientGroups.class) @@ -127,6 +139,16 @@ public class FullPageMonitoringUpdate implements MonitoringFilter { return this.statusFilterParam; } + @Override + public EnumSet 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.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()) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/MonitoringFilter.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/MonitoringFilter.java index f9d75015..c36fd9f4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/MonitoringFilter.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/MonitoringFilter.java @@ -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 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 proctoringData() { final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData(); if (monitoringFullPageData != null) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index 3165a5eb..87c3cc79 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -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 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 duplicateCheck = new HashMap<>(); private ClientConnectionDataInternal getForTokenAndCheckDuplication( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index 51c7ce58..a839a8a5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -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 activeIssueFilterSebVersion(final EnumSet filterStates) { + if(!filterStates.contains(ConnectionIssueStatus.SEB_VERSION_GRANTED)){ + return null; + } + return conn -> conn != null && BooleanUtils.isFalse(conn.clientConnection.clientVersionGranted); + } + + private Predicate activeIssueFilterAsk(final EnumSet 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 withActiveFilter(final EnumSet filterStates) { @@ -560,7 +576,8 @@ public class ExamMonitoringController { private Predicate createMonitoringFilter( final String hiddenStates, - final String hiddenClientGroups) { + final String hiddenClientGroups, + final String hiddenIssues) { final EnumSet filterStates = EnumSet.noneOf(ConnectionStatus.class); if (StringUtils.isNotBlank(hiddenStates)) { @@ -581,6 +598,25 @@ public class ExamMonitoringController { ? withActiveFilter(filterStates) : noneActiveFilter(filterStates); + final EnumSet 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 issueFilterSebVersion = + filterIssues.isEmpty() + ? null + : activeIssueFilterSebVersion(filterIssues); + + final Predicate issueFilterAsk = + filterIssues.isEmpty() + ? null + : activeIssueFilterAsk(filterIssues); + + Set 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; }; } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 0db5b37b..b183e0b7 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -2191,6 +2191,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 @@ -2282,6 +2283,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.
Are you sure to open the town-hall?