SEBSERV-163 finished grouping with filter

This commit is contained in:
anhefti 2022-09-13 09:36:01 +02:00
parent ee0034c367
commit b5704dea95
18 changed files with 459 additions and 142 deletions

View file

@ -197,6 +197,7 @@ public final class API {
public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification"; public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification";
public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection"; public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection";
public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states"; public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states";
public static final String EXAM_MONITORING_CLIENT_GROUP_FILTER = "hidden-client-group";
public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams"; public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams";
public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT =
"/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}";

View file

@ -28,7 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.CLIENT_GROUP;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class ClientGroup implements ClientGroupData { public class ClientGroup implements ClientGroupData, Comparable<ClientGroup> {
public static final String FILTER_ATTR_EXAM_ID = "examId"; public static final String FILTER_ATTR_EXAM_ID = "examId";
@ -285,4 +285,9 @@ public class ClientGroup implements ClientGroupData {
return true; return true;
} }
@Override
public int compareTo(final ClientGroup o) {
return o == null ? -1 : this.id.compareTo(o.id);
}
} }

View file

@ -84,6 +84,29 @@ public class ClientConnectionData implements GrantEntity {
return this.groups != null && this.groups.contains(clientGroupId); return this.groups != null && this.groups.contains(clientGroupId);
} }
@JsonIgnore
public boolean containsAllClientGroup(final Set<Long> clientGroupIds) {
if (this.groups == null || clientGroupIds == null || clientGroupIds.isEmpty()) {
return false;
}
return this.groups != null && this.groups.containsAll(clientGroupIds);
}
@JsonIgnore
public boolean filter(final Set<Long> clientGroupIdsToHide) {
if (this.groups == null || clientGroupIdsToHide == null || clientGroupIdsToHide.isEmpty()) {
return true;
}
for (final Long id : this.groups) {
if (!clientGroupIdsToHide.contains(id)) {
return true;
}
}
return false;
}
@Override @Override
public EntityType entityType() { public EntityType entityType() {
return this.clientConnection.entityType(); return this.clientConnection.entityType();

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gbl.monitoring;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
@ -25,6 +26,7 @@ public class MonitoringSEBConnectionData {
public static final String ATTR_CONNECTIONS = "connections"; public static final String ATTR_CONNECTIONS = "connections";
public static final String ATTR_STATUS_MAPPING = "statusMapping"; public static final String ATTR_STATUS_MAPPING = "statusMapping";
public static final String ATTR_CLIENT_GROUP_MAPPING = "clientGroupMapping";
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID)
public final Long examId; public final Long examId;
@ -32,16 +34,20 @@ public class MonitoringSEBConnectionData {
public final Collection<ClientConnectionData> connections; public final Collection<ClientConnectionData> connections;
@JsonProperty(ATTR_STATUS_MAPPING) @JsonProperty(ATTR_STATUS_MAPPING)
public final int[] connectionsPerStatus; public final int[] connectionsPerStatus;
@JsonProperty(ATTR_CLIENT_GROUP_MAPPING)
public final Map<Long, Integer> connectionsPerClientGroup;
@JsonCreator @JsonCreator
public MonitoringSEBConnectionData( public MonitoringSEBConnectionData(
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) final Long examId, @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) final Long examId,
@JsonProperty(ATTR_CONNECTIONS) final Collection<ClientConnectionData> connections, @JsonProperty(ATTR_CONNECTIONS) final Collection<ClientConnectionData> connections,
@JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus) { @JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus,
@JsonProperty(ATTR_CLIENT_GROUP_MAPPING) final Map<Long, Integer> connectionsPerClientGroup) {
this.examId = examId; this.examId = examId;
this.connections = connections; this.connections = connections;
this.connectionsPerStatus = connectionsPerStatus; this.connectionsPerStatus = connectionsPerStatus;
this.connectionsPerClientGroup = connectionsPerClientGroup;
} }
public Long getExamId() { public Long getExamId() {
@ -64,6 +70,14 @@ public class MonitoringSEBConnectionData {
return this.connectionsPerStatus[status.code]; return this.connectionsPerStatus[status.code];
} }
@JsonIgnore
public int getNumberOfConnection(final Long clientGroupId) {
if (this.connectionsPerClientGroup == null || !this.connectionsPerClientGroup.containsKey(clientGroupId)) {
return -1;
}
return this.connectionsPerClientGroup.get(clientGroupId);
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View file

@ -42,7 +42,8 @@ public enum ActionCategory {
LOGS_USER_ACTIVITY_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1), LOGS_USER_ACTIVITY_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1),
LOGS_SEB_CLIENT_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1), LOGS_SEB_CLIENT_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1),
VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0), VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0),
FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.filter"), 50), STATE_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.statefilter"), 40),
GROUP_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.groupfilter"), 50),
PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60), PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60),
FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1); FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1);

View file

@ -882,44 +882,54 @@ public enum ActionDefinition {
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"), new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"),
ImageIcon.TOGGLE_OFF, ImageIcon.TOGGLE_OFF,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER), ActionCategory.STATE_FILTER),
MONITOR_EXAM_SHOW_REQUESTED_CONNECTION( MONITOR_EXAM_SHOW_REQUESTED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.show.requested"), new LocTextKey("sebserver.monitoring.exam.connection.action.show.requested"),
ImageIcon.TOGGLE_ON, ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER), ActionCategory.STATE_FILTER),
MONITOR_EXAM_HIDE_ACTIVE_CONNECTION( MONITOR_EXAM_HIDE_ACTIVE_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.active"), new LocTextKey("sebserver.monitoring.exam.connection.action.hide.active"),
ImageIcon.TOGGLE_OFF, ImageIcon.TOGGLE_OFF,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER), ActionCategory.STATE_FILTER),
MONITOR_EXAM_SHOW_ACTIVE_CONNECTION( MONITOR_EXAM_SHOW_ACTIVE_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.show.active"), new LocTextKey("sebserver.monitoring.exam.connection.action.show.active"),
ImageIcon.TOGGLE_ON, ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER), ActionCategory.STATE_FILTER),
MONITOR_EXAM_HIDE_CLOSED_CONNECTION( MONITOR_EXAM_HIDE_CLOSED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.closed"), new LocTextKey("sebserver.monitoring.exam.connection.action.hide.closed"),
ImageIcon.TOGGLE_OFF, ImageIcon.TOGGLE_OFF,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER), ActionCategory.STATE_FILTER),
MONITOR_EXAM_SHOW_CLOSED_CONNECTION( MONITOR_EXAM_SHOW_CLOSED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.show.closed"), new LocTextKey("sebserver.monitoring.exam.connection.action.show.closed"),
ImageIcon.TOGGLE_ON, ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER), ActionCategory.STATE_FILTER),
MONITOR_EXAM_HIDE_DISABLED_CONNECTION( MONITOR_EXAM_HIDE_DISABLED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.disabled"), new LocTextKey("sebserver.monitoring.exam.connection.action.hide.disabled"),
ImageIcon.TOGGLE_OFF, ImageIcon.TOGGLE_OFF,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER), ActionCategory.STATE_FILTER),
MONITOR_EXAM_SHOW_DISABLED_CONNECTION( MONITOR_EXAM_SHOW_DISABLED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.show.disabled"), new LocTextKey("sebserver.monitoring.exam.connection.action.show.disabled"),
ImageIcon.TOGGLE_ON, ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER), ActionCategory.STATE_FILTER),
MONITOR_EXAM_HIDE_CLIENT_GROUP_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.clientgroup"),
ImageIcon.TOGGLE_OFF,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.GROUP_FILTER),
MONITOR_EXAM_SHOW_CLIENT_GROUP_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.show.clientgroup"),
ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.GROUP_FILTER),
MONITORING_EXAM_SEARCH_CONNECTIONS( MONITORING_EXAM_SEARCH_CONNECTIONS(
new LocTextKey("sebserver.monitoring.search.action"), new LocTextKey("sebserver.monitoring.search.action"),

View file

@ -247,26 +247,7 @@ public class ActionPane implements TemplateComposer {
actionsTitle.setData(RWT.CUSTOM_VARIANT, "close"); actionsTitle.setData(RWT.CUSTOM_VARIANT, "close");
actionsTitle.setImage(WidgetFactory.ImageIcon.ACTIVE.getImage(parent.getDisplay())); actionsTitle.setImage(WidgetFactory.ImageIcon.ACTIVE.getImage(parent.getDisplay()));
actionsTitle.setText("&nbsp;&nbsp;&nbsp;&nbsp;" + titleText); actionsTitle.setText("&nbsp;&nbsp;&nbsp;&nbsp;" + titleText);
actionsTitle.addListener(SWT.MouseUp, event -> { actionsTitle.addListener(SWT.MouseUp, event -> actionGroupExpand(composite, actionsTitle));
try {
final Control contentControl = composite.getChildren()[1];
if (contentControl.isVisible()) {
actionsTitle.setData(RWT.CUSTOM_VARIANT, "open");
contentControl.setVisible(false);
final GridData l = (GridData) contentControl.getLayoutData();
l.heightHint = 0;
composite.getParent().layout(true, true);
} else {
actionsTitle.setData(RWT.CUSTOM_VARIANT, "close");
contentControl.setVisible(true);
final GridData l = (GridData) contentControl.getLayoutData();
l.heightHint = SWT.DEFAULT;
composite.getParent().layout(true, true);
}
} catch (final Exception e) {
// just ignore
}
});
} }
actionsTitle.setLayoutData(titleLayout); actionsTitle.setLayoutData(titleLayout);
@ -319,6 +300,27 @@ public class ActionPane implements TemplateComposer {
return actions; return actions;
} }
private void actionGroupExpand(final Composite composite, final Label actionsTitle) {
try {
final Control contentControl = composite.getChildren()[1];
if (contentControl.isVisible()) {
actionsTitle.setData(RWT.CUSTOM_VARIANT, "open");
contentControl.setVisible(false);
final GridData l = (GridData) contentControl.getLayoutData();
l.heightHint = 0;
composite.getParent().layout(true, true);
} else {
actionsTitle.setData(RWT.CUSTOM_VARIANT, "close");
contentControl.setVisible(true);
final GridData l = (GridData) contentControl.getLayoutData();
l.heightHint = SWT.DEFAULT;
composite.getParent().layout(true, true);
}
} catch (final Exception e) {
// just ignore
}
}
private void clearDisposedTrees(final Map<String, Tree> actionTrees) { private void clearDisposedTrees(final Map<String, Tree> actionTrees) {
new ArrayList<>(actionTrees.entrySet()) new ArrayList<>(actionTrees.entrySet())
.forEach(entry -> { .forEach(entry -> {

View file

@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.gui.content.monitoring;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -64,7 +66,7 @@ import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringGUIUpdate; import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringGUIUpdate;
import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringUpdate; import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringUpdate;
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
import ch.ethz.seb.sebserver.gui.service.session.MonitoringStatus; import ch.ethz.seb.sebserver.gui.service.session.MonitoringFilter;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.MonitoringProctoringService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.MonitoringProctoringService;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
@ -258,6 +260,7 @@ public class MonitoringRunningExam implements TemplateComposer {
if (isExamSupporter.getAsBoolean()) { if (isExamSupporter.getAsBoolean()) {
guiUpdates.add(createFilterActions( guiUpdates.add(createFilterActions(
clientGroups,
fullPageMonitoringUpdate, fullPageMonitoringUpdate,
actionBuilder, actionBuilder,
clientTable, clientTable,
@ -347,13 +350,14 @@ public class MonitoringRunningExam implements TemplateComposer {
} }
private FullPageMonitoringGUIUpdate createFilterActions( private FullPageMonitoringGUIUpdate createFilterActions(
final MonitoringStatus monitoringStatus, final Collection<ClientGroup> clientGroups,
final MonitoringFilter monitoringStatus,
final PageActionBuilder actionBuilder, final PageActionBuilder actionBuilder,
final ClientConnectionTable clientTable, final ClientConnectionTable clientTable,
final BooleanSupplier isExamSupporter) { final BooleanSupplier isExamSupporter) {
final StatusFilterGUIUpdate statusFilterGUIUpdate = final FilterGUIUpdate statusFilterGUIUpdate =
new StatusFilterGUIUpdate(this.pageService.getPolyglotPageService()); new FilterGUIUpdate(this.pageService.getPolyglotPageService());
addFilterAction( addFilterAction(
monitoringStatus, monitoringStatus,
@ -388,61 +392,118 @@ public class MonitoringRunningExam implements TemplateComposer {
ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION, ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION,
ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION); ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION);
if (clientGroups != null && !clientGroups.isEmpty()) {
clientGroups.forEach(clientGroup -> {
addClientGroupFilterAction(
monitoringStatus,
statusFilterGUIUpdate,
actionBuilder,
clientTable,
clientGroup,
ActionDefinition.MONITOR_EXAM_SHOW_CLIENT_GROUP_CONNECTION,
ActionDefinition.MONITOR_EXAM_HIDE_CLIENT_GROUP_CONNECTION);
});
}
return statusFilterGUIUpdate; return statusFilterGUIUpdate;
} }
private void addFilterAction( private void addFilterAction(
final MonitoringStatus monitoringStatus, final MonitoringFilter filter,
final StatusFilterGUIUpdate statusFilterGUIUpdate, final FilterGUIUpdate filterGUIUpdate,
final PageActionBuilder actionBuilder, final PageActionBuilder actionBuilder,
final ClientConnectionTable clientTable, final ClientConnectionTable clientTable,
final ConnectionStatus status, final ConnectionStatus status,
final ActionDefinition showActionDef, final ActionDefinition showActionDef,
final ActionDefinition hideActionDef) { final ActionDefinition hideActionDef) {
final int numOfConnections = monitoringStatus.getNumOfConnections(status); final int numOfConnections = filter.getNumOfConnections(status);
if (monitoringStatus.isStatusHidden(status)) { PageAction action;
final PageAction showAction = actionBuilder.newAction(showActionDef) if (filter.isStatusHidden(status)) {
.withExec(showStateViewAction(monitoringStatus, clientTable, status)) action = actionBuilder.newAction(showActionDef)
.withExec(showStateViewAction(filter, clientTable, status))
.noEventPropagation() .noEventPropagation()
.withSwitchAction( .withSwitchAction(
actionBuilder.newAction(hideActionDef) actionBuilder.newAction(hideActionDef)
.withExec( .withExec(
hideStateViewAction(monitoringStatus, clientTable, status)) hideStateViewAction(filter, clientTable, status))
.noEventPropagation() .noEventPropagation()
.withNameAttributes(numOfConnections) .withNameAttributes(numOfConnections)
.create()) .create())
.withNameAttributes(numOfConnections) .withNameAttributes(numOfConnections)
.create(); .create();
this.pageService.publishAction(
showAction,
treeItem -> statusFilterGUIUpdate.register(treeItem, status));
} else { } else {
final PageAction hideAction = actionBuilder.newAction(hideActionDef) action = actionBuilder.newAction(hideActionDef)
.withExec(hideStateViewAction(monitoringStatus, clientTable, status)) .withExec(hideStateViewAction(filter, clientTable, status))
.noEventPropagation() .noEventPropagation()
.withSwitchAction( .withSwitchAction(
actionBuilder.newAction(showActionDef) actionBuilder.newAction(showActionDef)
.withExec( .withExec(
showStateViewAction(monitoringStatus, clientTable, status)) showStateViewAction(filter, clientTable, status))
.noEventPropagation() .noEventPropagation()
.withNameAttributes(numOfConnections) .withNameAttributes(numOfConnections)
.create()) .create())
.withNameAttributes(numOfConnections) .withNameAttributes(numOfConnections)
.create(); .create();
this.pageService.publishAction(
hideAction,
treeItem -> statusFilterGUIUpdate.register(treeItem, status));
} }
this.pageService.publishAction(
action,
treeItem -> filterGUIUpdate.register(treeItem, status));
}
private void addClientGroupFilterAction(
final MonitoringFilter filter,
final FilterGUIUpdate filterGUIUpdate,
final PageActionBuilder actionBuilder,
final ClientConnectionTable clientTable,
final ClientGroup clientGroup,
final ActionDefinition showActionDef,
final ActionDefinition hideActionDef) {
final int numOfConnections = filter.getNumOfConnections(clientGroup.id);
PageAction action;
if (filter.isClientGroupHidden(clientGroup.id)) {
action = actionBuilder.newAction(showActionDef)
.withExec(showClientGroupAction(filter, clientTable, clientGroup.id))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(hideActionDef)
.withExec(
hideClientGroupViewAction(filter, clientTable, clientGroup.id))
.noEventPropagation()
.withNameAttributes(clientGroup.name, numOfConnections)
.create())
.withNameAttributes(clientGroup.name, numOfConnections)
.create();
} else {
action = actionBuilder.newAction(hideActionDef)
.withExec(hideClientGroupViewAction(filter, clientTable, clientGroup.id))
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(showActionDef)
.withExec(
showClientGroupAction(filter, clientTable, clientGroup.id))
.noEventPropagation()
.withNameAttributes(clientGroup.name, numOfConnections)
.create())
.withNameAttributes(clientGroup.name, numOfConnections)
.create();
}
this.pageService.publishAction(
action,
treeItem -> filterGUIUpdate.register(treeItem, clientGroup.id));
} }
/** This holds the filter action items and implements the specific GUI update for it */ /** This holds the filter action items and implements the specific GUI update for it */
private class StatusFilterGUIUpdate implements FullPageMonitoringGUIUpdate { private class FilterGUIUpdate implements FullPageMonitoringGUIUpdate {
private final PolyglotPageService polyglotPageService; private final PolyglotPageService polyglotPageService;
private final TreeItem[] actionItemPerStateFilter = new TreeItem[ConnectionStatus.values().length]; private final TreeItem[] actionItemPerStateFilter = new TreeItem[ConnectionStatus.values().length];
private final Map<Long, TreeItem> actionItemPerClientGroup = new HashMap<>();
public StatusFilterGUIUpdate(final PolyglotPageService polyglotPageService) { public FilterGUIUpdate(final PolyglotPageService polyglotPageService) {
this.polyglotPageService = polyglotPageService; this.polyglotPageService = polyglotPageService;
} }
@ -450,8 +511,12 @@ public class MonitoringRunningExam implements TemplateComposer {
this.actionItemPerStateFilter[status.code] = item; this.actionItemPerStateFilter[status.code] = item;
} }
void register(final TreeItem item, final Long clientGroupId) {
this.actionItemPerClientGroup.put(clientGroupId, item);
}
@Override @Override
public void update(final MonitoringStatus monitoringStatus) { public void update(final MonitoringFilter monitoringStatus) {
final ConnectionStatus[] states = ConnectionStatus.values(); final ConnectionStatus[] states = ConnectionStatus.values();
for (int i = 0; i < states.length; i++) { for (int i = 0; i < states.length; i++) {
final ConnectionStatus state = states[i]; final ConnectionStatus state = states[i];
@ -463,6 +528,18 @@ public class MonitoringRunningExam implements TemplateComposer {
this.polyglotPageService.injectI18n(treeItem, action.getTitle()); this.polyglotPageService.injectI18n(treeItem, action.getTitle());
} }
} }
if (!this.actionItemPerClientGroup.isEmpty()) {
this.actionItemPerClientGroup.entrySet().stream().forEach(entry -> {
final int numOfConnections = monitoringStatus.getNumOfConnections(entry.getKey());
if (numOfConnections >= 0) {
final TreeItem treeItem = entry.getValue();
final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY);
action.setTitleArgument(1, numOfConnections);
this.polyglotPageService.injectI18n(treeItem, action.getTitle());
}
});
}
} }
} }
@ -472,7 +549,7 @@ public class MonitoringRunningExam implements TemplateComposer {
} }
private static Function<PageAction, PageAction> showStateViewAction( private static Function<PageAction, PageAction> showStateViewAction(
final MonitoringStatus monitoringStatus, final MonitoringFilter monitoringStatus,
final ClientConnectionTable clientTable, final ClientConnectionTable clientTable,
final ConnectionStatus status) { final ConnectionStatus status) {
@ -484,7 +561,7 @@ public class MonitoringRunningExam implements TemplateComposer {
} }
private static Function<PageAction, PageAction> hideStateViewAction( private static Function<PageAction, PageAction> hideStateViewAction(
final MonitoringStatus monitoringStatus, final MonitoringFilter monitoringStatus,
final ClientConnectionTable clientTable, final ClientConnectionTable clientTable,
final ConnectionStatus status) { final ConnectionStatus status) {
@ -495,6 +572,30 @@ public class MonitoringRunningExam implements TemplateComposer {
}; };
} }
private static Function<PageAction, PageAction> showClientGroupAction(
final MonitoringFilter monitoringStatus,
final ClientConnectionTable clientTable,
final Long clientGroupId) {
return action -> {
monitoringStatus.showClientGroup(clientGroupId);
clientTable.removeSelection();
return action;
};
}
private static Function<PageAction, PageAction> hideClientGroupViewAction(
final MonitoringFilter monitoringStatus,
final ClientConnectionTable clientTable,
final Long clientGroupId) {
return action -> {
monitoringStatus.hideClientGroup(clientGroupId);
clientTable.removeSelection();
return action;
};
}
private Set<EntityKey> selectionForInstruction(final ClientConnectionTable clientTable) { private Set<EntityKey> selectionForInstruction(final ClientConnectionTable clientTable) {
final Set<String> connectionTokens = clientTable.getConnectionTokens( final Set<String> connectionTokens = clientTable.getConnectionTokens(
cc -> cc.status.clientActiveStatus, cc -> cc.status.clientActiveStatus,

View file

@ -312,10 +312,10 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
} }
@Override @Override
public void update(final MonitoringStatus monitoringStatus) { public void update(final MonitoringFilter monitoringStatus) {
final Collection<ClientConnectionData> connectionData = monitoringStatus.getConnectionData(); final Collection<ClientConnectionData> connectionData = monitoringStatus.getConnectionData();
final boolean sizeChanged = connectionData.size() != this.table.getItemCount(); final boolean sizeChanged = connectionData.size() != this.table.getItemCount();
final boolean needsSync = monitoringStatus.statusFilterChanged() || final boolean needsSync = monitoringStatus.filterChanged() ||
this.forceUpdateAll || this.forceUpdateAll ||
sizeChanged || sizeChanged ||
(this.tableMapping != null && (this.tableMapping != null &&
@ -350,7 +350,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
} }
} }
}); });
monitoringStatus.resetStatusFilterChanged(); monitoringStatus.resetFilterChanged();
this.toDelete.clear(); this.toDelete.clear();
} }
@ -624,7 +624,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
private String getGroupInfo() { private String getGroupInfo() {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
ClientConnectionTable.this.clientGroupMapping.keySet().stream().forEach(key -> { ClientConnectionTable.this.clientGroupMapping.keySet().stream().forEach(key -> {
if (this.connectionData.groups.contains(key)) { if (this.connectionData.groups != null && this.connectionData.groups.contains(key)) {
final ClientGroup clientGroup = ClientConnectionTable.this.clientGroupMapping.get(key); final ClientGroup clientGroup = ClientConnectionTable.this.clientGroupMapping.get(key);
sb.append(WidgetFactory.getTextWithBackgroundHTML(clientGroup.name, clientGroup.color)); sb.append(WidgetFactory.getTextWithBackgroundHTML(clientGroup.name, clientGroup.color));
} }

View file

@ -11,6 +11,6 @@ package ch.ethz.seb.sebserver.gui.service.session;
@FunctionalInterface @FunctionalInterface
public interface FullPageMonitoringGUIUpdate { public interface FullPageMonitoringGUIUpdate {
void update(MonitoringStatus monitoringStatus); void update(MonitoringFilter monitoringStatus);
} }

View file

@ -10,7 +10,10 @@ package ch.ethz.seb.sebserver.gui.service.session;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
@ -20,6 +23,8 @@ import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.async.AsyncRunner; 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.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
@ -29,6 +34,7 @@ import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
import ch.ethz.seb.sebserver.gui.service.push.UpdateErrorHandler; import ch.ethz.seb.sebserver.gui.service.push.UpdateErrorHandler;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroups;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetMonitoringFullPageData; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetMonitoringFullPageData;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException;
@ -36,11 +42,12 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2Re
* full page monitoring. * full page monitoring.
* *
* This handles server push and GUI update and also implements kind of circuit breaker and error handling */ * This handles server push and GUI update and also implements kind of circuit breaker and error handling */
public class FullPageMonitoringUpdate implements MonitoringStatus { public class FullPageMonitoringUpdate implements MonitoringFilter {
static final Logger log = LoggerFactory.getLogger(FullPageMonitoringUpdate.class); static final Logger log = LoggerFactory.getLogger(FullPageMonitoringUpdate.class);
private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER_ATTRIBUTE"; private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER";
private static final String USER_SESSION_GROUP_FILTER_ATTRIBUTE = "USER_SESSION_GROUP_FILTER";
private final ServerPushService serverPushService; private final ServerPushService serverPushService;
private final PageService pageService; private final PageService pageService;
@ -49,9 +56,13 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
private final Collection<FullPageMonitoringGUIUpdate> guiUpdates; private final Collection<FullPageMonitoringGUIUpdate> guiUpdates;
private ServerPushContext pushContext; private ServerPushContext pushContext;
private final EnumSet<ConnectionStatus> statusFilter; private final EnumSet<ConnectionStatus> statusFilter;
private String statusFilterParam = ""; private String statusFilterParam = "";
private boolean statusFilterChanged = false; private final Set<Long> clientGroupFilter;
private String clientGroupFilterParam = "";
private boolean filterChanged = false;
private boolean updateInProgress = false; private boolean updateInProgress = false;
private MonitoringFullPageData monitoringFullPageData = null; private MonitoringFullPageData monitoringFullPageData = null;
@ -72,7 +83,19 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
this.guiUpdates = guiUpdates; this.guiUpdates = guiUpdates;
this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); this.statusFilter = EnumSet.noneOf(ConnectionStatus.class);
loadStatusFilter(); loadFilter();
final Collection<ClientGroup> clientGroups = pageService.getRestService()
.getBuilder(GetClientGroups.class)
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, String.valueOf(examId))
.call()
.getOr(Collections.emptyList());
if (clientGroups != null && !clientGroups.isEmpty()) {
this.clientGroupFilter = new HashSet<>();
} else {
this.clientGroupFilter = null;
}
} }
public void start(final PageContext pageContext, final Composite anchor, final long pollInterval) { public void start(final PageContext pageContext, final Composite anchor, final long pollInterval) {
@ -105,13 +128,13 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
} }
@Override @Override
public boolean statusFilterChanged() { public boolean filterChanged() {
return this.statusFilterChanged; return this.filterChanged;
} }
@Override @Override
public void resetStatusFilterChanged() { public void resetFilterChanged() {
this.statusFilterChanged = false; this.filterChanged = false;
} }
@Override @Override
@ -122,13 +145,43 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
@Override @Override
public void hideStatus(final ConnectionStatus status) { public void hideStatus(final ConnectionStatus status) {
this.statusFilter.add(status); this.statusFilter.add(status);
saveStatusFilter(); saveFilter();
} }
@Override @Override
public void showStatus(final ConnectionStatus status) { public void showStatus(final ConnectionStatus status) {
this.statusFilter.remove(status); this.statusFilter.remove(status);
saveStatusFilter(); saveFilter();
}
@Override
public boolean hasClientGroupFilter() {
return this.clientGroupFilter != null;
}
@Override
public boolean isClientGroupHidden(final Long clientGroupId) {
return this.clientGroupFilter != null && this.clientGroupFilter.contains(clientGroupId);
}
@Override
public void hideClientGroup(final Long clientGroupId) {
if (this.clientGroupFilter == null) {
return;
}
this.clientGroupFilter.add(clientGroupId);
saveFilter();
}
@Override
public void showClientGroup(final Long clientGroupId) {
if (this.clientGroupFilter == null) {
return;
}
this.clientGroupFilter.remove(clientGroupId);
saveFilter();
} }
@Override @Override
@ -161,8 +214,15 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
} }
private void updateBusinessData() { private void updateBusinessData() {
this.monitoringFullPageData = this.restCallBuilder
.withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam) RestCall<MonitoringFullPageData>.RestCallBuilder restCallBuilder = this.restCallBuilder
.withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam);
if (hasClientGroupFilter()) {
restCallBuilder = restCallBuilder
.withHeader(API.EXAM_MONITORING_CLIENT_GROUP_FILTER, this.clientGroupFilterParam);
}
this.monitoringFullPageData = restCallBuilder
.call() .call()
.get(error -> { .get(error -> {
recoverFromDisposedRestTemplate(error); recoverFromDisposedRestTemplate(error);
@ -182,22 +242,32 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
}); });
} }
private void saveStatusFilter() { private void saveFilter() {
try { try {
this.pageService this.pageService
.getCurrentUser() .getCurrentUser()
.putAttribute( .putAttribute(
USER_SESSION_STATUS_FILTER_ATTRIBUTE, USER_SESSION_STATUS_FILTER_ATTRIBUTE,
StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR)); StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR));
if (hasClientGroupFilter()) {
this.pageService
.getCurrentUser()
.putAttribute(
USER_SESSION_GROUP_FILTER_ATTRIBUTE,
StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR));
}
} catch (final Exception e) { } catch (final Exception e) {
log.warn("Failed to save status filter to user session"); log.warn("Failed to save status filter to user session");
} finally { } finally {
this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR);
this.statusFilterChanged = true; if (hasClientGroupFilter()) {
this.clientGroupFilterParam = StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR);
}
this.filterChanged = true;
} }
} }
private void loadStatusFilter() { private void loadFilter() {
try { try {
final String attribute = this.pageService final String attribute = this.pageService
.getCurrentUser() .getCurrentUser()
@ -206,17 +276,33 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
if (attribute != null) { if (attribute != null) {
Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR)) Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR))
.forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name))); .forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name)));
} else { } else {
this.statusFilter.add(ConnectionStatus.DISABLED); this.statusFilter.add(ConnectionStatus.DISABLED);
} }
if (hasClientGroupFilter()) {
final String groups = this.pageService
.getCurrentUser()
.getAttribute(USER_SESSION_GROUP_FILTER_ATTRIBUTE);
this.statusFilter.clear();
if (groups != null) {
Arrays.asList(StringUtils.split(groups, Constants.LIST_SEPARATOR))
.forEach(id -> this.clientGroupFilter.add(Long.parseLong(id)));
}
}
} catch (final Exception e) { } catch (final Exception e) {
log.warn("Failed to load status filter to user session"); log.warn("Failed to load status filter to user session");
this.statusFilter.clear(); this.statusFilter.clear();
this.statusFilter.add(ConnectionStatus.DISABLED); this.statusFilter.add(ConnectionStatus.DISABLED);
if (hasClientGroupFilter()) {
this.clientGroupFilter.clear();
}
} finally { } finally {
this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR);
this.statusFilterChanged = true; if (hasClientGroupFilter()) {
this.clientGroupFilterParam = StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR);
}
this.filterChanged = true;
} }
} }

View file

@ -12,21 +12,22 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; 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.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData;
import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData;
import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData;
public interface MonitoringStatus { public interface MonitoringFilter {
EnumSet<ConnectionStatus> getStatusFilter(); EnumSet<ConnectionStatus> getStatusFilter();
String getStatusFilterParam(); String getStatusFilterParam();
boolean statusFilterChanged(); boolean filterChanged();
void resetStatusFilterChanged(); void resetFilterChanged();
boolean isStatusHidden(ConnectionStatus status); boolean isStatusHidden(ConnectionStatus status);
@ -34,6 +35,18 @@ public interface MonitoringStatus {
void showStatus(ConnectionStatus status); void showStatus(ConnectionStatus status);
boolean hasClientGroupFilter();
default boolean isClientGroupHidden(final ClientGroup clientGroup) {
return isClientGroupHidden(clientGroup.id);
}
boolean isClientGroupHidden(Long clientGroupId);
void hideClientGroup(Long clientGroupId);
void showClientGroup(Long clientGroupId);
MonitoringFullPageData getMonitoringFullPageData(); MonitoringFullPageData getMonitoringFullPageData();
default MonitoringSEBConnectionData getMonitoringSEBConnectionData() { default MonitoringSEBConnectionData getMonitoringSEBConnectionData() {
@ -63,6 +76,15 @@ public interface MonitoringStatus {
} }
} }
default int getNumOfConnections(final Long clientGroupId) {
final MonitoringSEBConnectionData monitoringSEBConnectionData = getMonitoringSEBConnectionData();
if (monitoringSEBConnectionData != null) {
return monitoringSEBConnectionData.getNumberOfConnection(clientGroupId);
} else {
return 0;
}
}
default Collection<RemoteProctoringRoom> proctoringData() { default Collection<RemoteProctoringRoom> proctoringData() {
final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData(); final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData();
if (monitoringFullPageData != null) { if (monitoringFullPageData != null) {

View file

@ -194,6 +194,7 @@ public class ClientGroupDAOImpl implements ClientGroupDAO {
.stream() .stream()
.map(this::toDomainModel) .map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError) .flatMap(DAOLoggingSupport::logAndSkipOnError)
.sorted()
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }

View file

@ -39,6 +39,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService
@WebServiceProfile @WebServiceProfile
public class ExamSessionCacheService { public class ExamSessionCacheService {
public static final Object CLIENT_CONECTION_CREATION_LOCK = new Object();
public static final String CACHE_NAME_RUNNING_EXAM = "RUNNING_EXAM"; public static final String CACHE_NAME_RUNNING_EXAM = "RUNNING_EXAM";
public static final String CACHE_NAME_ACTIVE_CLIENT_CONNECTION = "ACTIVE_CLIENT_CONNECTION"; public static final String CACHE_NAME_ACTIVE_CLIENT_CONNECTION = "ACTIVE_CLIENT_CONNECTION";
public static final String CACHE_NAME_SEB_CONFIG_EXAM = "SEB_CONFIG_EXAM"; public static final String CACHE_NAME_SEB_CONFIG_EXAM = "SEB_CONFIG_EXAM";

View file

@ -13,7 +13,9 @@ import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -34,6 +36,7 @@ import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
@ -43,6 +46,7 @@ import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientGroupDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
@ -64,6 +68,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
private final IndicatorDAO indicatorDAO; private final IndicatorDAO indicatorDAO;
private final ExamSessionCacheService examSessionCacheService; private final ExamSessionCacheService examSessionCacheService;
private final ExamDAO examDAO; private final ExamDAO examDAO;
private final ClientGroupDAO clientGroupDAO;
private final ExamConfigurationMapDAO examConfigurationMapDAO; private final ExamConfigurationMapDAO examConfigurationMapDAO;
private final CacheManager cacheManager; private final CacheManager cacheManager;
private final SEBRestrictionService sebRestrictionService; private final SEBRestrictionService sebRestrictionService;
@ -76,6 +81,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
protected ExamSessionServiceImpl( protected ExamSessionServiceImpl(
final ExamSessionCacheService examSessionCacheService, final ExamSessionCacheService examSessionCacheService,
final ExamDAO examDAO, final ExamDAO examDAO,
final ClientGroupDAO clientGroupDAO,
final ExamConfigurationMapDAO examConfigurationMapDAO, final ExamConfigurationMapDAO examConfigurationMapDAO,
final ClientConnectionDAO clientConnectionDAO, final ClientConnectionDAO clientConnectionDAO,
final IndicatorDAO indicatorDAO, final IndicatorDAO indicatorDAO,
@ -87,6 +93,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
this.examSessionCacheService = examSessionCacheService; this.examSessionCacheService = examSessionCacheService;
this.examDAO = examDAO; this.examDAO = examDAO;
this.clientGroupDAO = clientGroupDAO;
this.examConfigurationMapDAO = examConfigurationMapDAO; this.examConfigurationMapDAO = examConfigurationMapDAO;
this.clientConnectionDAO = clientConnectionDAO; this.clientConnectionDAO = clientConnectionDAO;
this.cacheManager = cacheManager; this.cacheManager = cacheManager;
@ -350,7 +357,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
@Override @Override
public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) { public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) {
synchronized (this.examSessionCacheService) { synchronized (ExamSessionCacheService.CLIENT_CONECTION_CREATION_LOCK) {
return this.examSessionCacheService.getClientConnection(connectionToken); return this.examSessionCacheService.getClientConnection(connectionToken);
} }
} }
@ -405,25 +412,33 @@ public class ExamSessionServiceImpl implements ExamSessionService {
for (int i = 0; i < statusMapping.length; i++) { for (int i = 0; i < statusMapping.length; i++) {
statusMapping[i] = 0; statusMapping[i] = 0;
} }
// needed to store connection numbers per client group too
final Collection<ClientGroup> groups = this.clientGroupDAO.allForExam(examId).getOr(null);
final Map<Long, Integer> clientGroupMapping = (groups != null && !groups.isEmpty())
? new HashMap<>()
: null;
updateClientConnections(examId); updateClientConnections(examId);
synchronized (this.examSessionCacheService) {
final List<ClientConnectionData> filteredConnections = this.clientConnectionDAO final List<ClientConnectionData> filteredConnections = this.clientConnectionDAO
.getConnectionTokens(examId) .getConnectionTokens(examId)
.getOrThrow() .getOrThrow()
.stream() .stream()
.map(token -> this.examSessionCacheService.getClientConnection(token)) .map(token -> getConnectionDataInternal(token))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(c -> { .map(c -> {
statusMapping[c.clientConnection.status.code]++; statusMapping[c.clientConnection.status.code]++;
processClientGroupMapping(c.groups, clientGroupMapping);
return c; return c;
}) })
.filter(filter) .filter(filter)
.collect(Collectors.toList()); .collect(Collectors.toList());
return new MonitoringSEBConnectionData(examId, filteredConnections, statusMapping); return new MonitoringSEBConnectionData(
} examId,
filteredConnections,
statusMapping,
clientGroupMapping);
}); });
} }
@ -557,4 +572,18 @@ public class ExamSessionServiceImpl implements ExamSessionService {
&& Objects.equals(exam.name, runningExam.name); && Objects.equals(exam.name, runningExam.name);
} }
private void processClientGroupMapping(final Set<Long> groups, final Map<Long, Integer> clientGroupMapping) {
if (groups == null || clientGroupMapping == null) {
return;
}
groups.forEach(id -> {
if (clientGroupMapping.containsKey(id)) {
clientGroupMapping.put(id, clientGroupMapping.get(id) + 1);
} else {
clientGroupMapping.put(id, 1);
}
});
}
} }

View file

@ -692,8 +692,10 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
private void processPing(final String connectionToken, final long timestamp, final int pingNumber) { private void processPing(final String connectionToken, final long timestamp, final int pingNumber) {
final ClientConnectionDataInternal activeClientConnection = ClientConnectionDataInternal activeClientConnection = null;
this.examSessionCacheService.getClientConnection(connectionToken); synchronized (ExamSessionCacheService.CLIENT_CONECTION_CREATION_LOCK) {
activeClientConnection = this.examSessionCacheService.getClientConnection(connectionToken);
}
if (activeClientConnection != null) { if (activeClientConnection != null) {
activeClientConnection.notifyPing(timestamp, pingNumber); activeClientConnection.notifyPing(timestamp, pingNumber);

View file

@ -11,7 +11,9 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -248,31 +250,17 @@ public class ExamMonitoringController {
required = true, required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates) { @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates,
@RequestHeader(
name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER,
required = false) final String hiddenClientGroups) {
checkPrivileges(institutionId, examId); checkPrivileges(institutionId, examId);
final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class);
if (StringUtils.isNoneBlank(hiddenStates)) {
final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR);
for (int i = 0; i < split.length; i++) {
filterStates.add(ConnectionStatus.valueOf(split[i]));
}
}
final boolean active = filterStates.contains(ConnectionStatus.ACTIVE);
if (active) {
filterStates.remove(ConnectionStatus.ACTIVE);
}
return this.examSessionService return this.examSessionService
.getMonitoringSEBConnectionsData( .getMonitoringSEBConnectionsData(
examId, examId,
filterStates.isEmpty() createMonitoringFilter(hiddenStates, hiddenClientGroups))
? Objects::nonNull
: active
? withActiveFilter(filterStates)
: noneActiveFilter(filterStates))
.getOrThrow().connections; .getOrThrow().connections;
} }
@ -288,31 +276,17 @@ public class ExamMonitoringController {
required = true, required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
@RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates) { @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates,
@RequestHeader(
name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER,
required = false) final String hiddenClientGroups) {
final Exam runningExam = checkPrivileges(institutionId, examId); final Exam runningExam = checkPrivileges(institutionId, examId);
final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class);
if (StringUtils.isNoneBlank(hiddenStates)) {
final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR);
for (int i = 0; i < split.length; i++) {
filterStates.add(ConnectionStatus.valueOf(split[i]));
}
}
final boolean active = filterStates.contains(ConnectionStatus.ACTIVE);
if (active) {
filterStates.remove(ConnectionStatus.ACTIVE);
}
final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService
.getMonitoringSEBConnectionsData( .getMonitoringSEBConnectionsData(
examId, examId,
filterStates.isEmpty() createMonitoringFilter(hiddenStates, hiddenClientGroups))
? Objects::nonNull
: active
? withActiveFilter(filterStates)
: noneActiveFilter(filterStates))
.getOrThrow(); .getOrThrow();
if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) { if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) {
@ -509,4 +483,46 @@ public class ExamMonitoringController {
}; };
} }
private Predicate<ClientConnectionData> createMonitoringFilter(
final String hiddenStates,
final String hiddenClientGroups) {
final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class);
if (StringUtils.isNotBlank(hiddenStates)) {
final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR);
for (int i = 0; i < split.length; i++) {
filterStates.add(ConnectionStatus.valueOf(split[i]));
}
}
final boolean active = filterStates.contains(ConnectionStatus.ACTIVE);
if (active) {
filterStates.remove(ConnectionStatus.ACTIVE);
}
final Predicate<ClientConnectionData> stateFilter = filterStates.isEmpty()
? Objects::nonNull
: active
? withActiveFilter(filterStates)
: noneActiveFilter(filterStates);
Set<Long> filterClientGroups = null;
if (StringUtils.isNotBlank(hiddenClientGroups)) {
filterClientGroups = new HashSet<>();
final String[] split = StringUtils.split(hiddenClientGroups, Constants.LIST_SEPARATOR);
for (int i = 0; i < split.length; i++) {
filterClientGroups.add(Long.parseLong(split[i]));
}
}
final Set<Long> _filterClientGroups = filterClientGroups;
final Predicate<ClientConnectionData> filter = ccd -> {
if (ccd == null) {
return false;
}
return stateFilter.test(ccd) && ccd.filter(_filterClientGroups);
};
return filter;
}
} }

View file

@ -1885,7 +1885,8 @@ sebserver.monitoring.exam.list.actions=
sebserver.monitoring.exam.action.detail.view=Back To Monitoring sebserver.monitoring.exam.action.detail.view=Back To Monitoring
sebserver.monitoring.exam.action.list.view=Monitoring sebserver.monitoring.exam.action.list.view=Monitoring
sebserver.monitoring.exam.action.viewroom=View {0} ( {1} / {2} ) sebserver.monitoring.exam.action.viewroom=View {0} ( {1} / {2} )
sebserver.exam.monitoring.action.category.filter=Filter sebserver.exam.monitoring.action.category.statefilter=State Filter
sebserver.exam.monitoring.action.category.groupfilter=Client Group Filter
sebserver.exam.overall.action.category.proctoring=Proctoring sebserver.exam.overall.action.category.proctoring=Proctoring
sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall
sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall
@ -1971,6 +1972,8 @@ sebserver.monitoring.exam.connection.action.hide.disabled=Hide Canceled ( {0} )
sebserver.monitoring.exam.connection.action.show.disabled=Show Canceled ( {0} ) sebserver.monitoring.exam.connection.action.show.disabled=Show Canceled ( {0} )
sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined ( {0} ) sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined ( {0} )
sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined ( {0} ) sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined ( {0} )
sebserver.monitoring.exam.connection.action.hide.clientgroup=Hide {0} ( {1} )
sebserver.monitoring.exam.connection.action.show.clientgroup=Show {0} ( {1} )
sebserver.monitoring.exam.connection.action.proctoring=Single Room Proctoring sebserver.monitoring.exam.connection.action.proctoring=Single Room Proctoring
sebserver.monitoring.exam.connection.action.proctoring.examroom=Exam Room Proctoring sebserver.monitoring.exam.connection.action.proctoring.examroom=Exam Room Proctoring
sebserver.monitoring.exam.connection.action.openTownhall.confirm=You are about to open the town-hall room and force all SEB clients to join the town-hall room.<br/>Are you sure to open the town-hall? sebserver.monitoring.exam.connection.action.openTownhall.confirm=You are about to open the town-hall room and force all SEB clients to join the town-hall room.<br/>Are you sure to open the town-hall?
@ -1978,7 +1981,6 @@ sebserver.monitoring.exam.connection.action.closeTownhall.confirm=You are about
sebserver.monitoring.exam.connection.action.singleroom.confirm=You are about to open the single/one to one room for this participant.<br/>Are you sure you want to open the single room? sebserver.monitoring.exam.connection.action.singleroom.confirm=You are about to open the single/one to one room for this participant.<br/>Are you sure you want to open the single room?
sebserver.monitoring.exam.connection.actions.group2=&nbsp; sebserver.monitoring.exam.connection.actions.group2=&nbsp;
sebserver.monitoring.exam.connection.actions.group3=&nbsp; sebserver.monitoring.exam.connection.actions.group3=&nbsp;
sebserver.monitoring.exam.connection.notificationlist.actions= sebserver.monitoring.exam.connection.notificationlist.actions=
sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification
sebserver.monitoring.exam.connection.action.confirm.notification.text=Are you sure you want to confirm this pending notification?<br/><br/>Note that this will send a notification confirmation instruction to the SEB client and remove this notification from the pending list. sebserver.monitoring.exam.connection.action.confirm.notification.text=Are you sure you want to confirm this pending notification?<br/><br/>Note that this will send a notification confirmation instruction to the SEB client and remove this notification from the pending list.