Merge pull request #105 from SafeExamBrowser/SEBSERV-478

add issue filters to exam monitoring
This commit is contained in:
Andreas Hefti 2024-01-25 16:03:43 +01:00 committed by GitHub
commit 474eb4f062
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 317 additions and 17 deletions

View file

@ -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 + "}";

View file

@ -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(),

View file

@ -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();

View file

@ -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);

View file

@ -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,

View file

@ -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,

View file

@ -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()) {

View file

@ -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) {

View file

@ -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(

View file

@ -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;
};
}

View file

@ -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?