SEBSERV-163 finished grouping with filter
This commit is contained in:
parent
ee0034c367
commit
b5704dea95
18 changed files with 459 additions and 142 deletions
|
@ -197,6 +197,7 @@ public final class API {
|
|||
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_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_SEB_CONNECTION_TOKEN_PATH_SEGMENT =
|
||||
"/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}";
|
||||
|
|
|
@ -28,7 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.CLIENT_GROUP;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ClientGroup implements ClientGroupData {
|
||||
public class ClientGroup implements ClientGroupData, Comparable<ClientGroup> {
|
||||
|
||||
public static final String FILTER_ATTR_EXAM_ID = "examId";
|
||||
|
||||
|
@ -285,4 +285,9 @@ public class ClientGroup implements ClientGroupData {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final ClientGroup o) {
|
||||
return o == null ? -1 : this.id.compareTo(o.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -84,6 +84,29 @@ public class ClientConnectionData implements GrantEntity {
|
|||
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
|
||||
public EntityType entityType() {
|
||||
return this.clientConnection.entityType();
|
||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gbl.monitoring;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
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_STATUS_MAPPING = "statusMapping";
|
||||
public static final String ATTR_CLIENT_GROUP_MAPPING = "clientGroupMapping";
|
||||
|
||||
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID)
|
||||
public final Long examId;
|
||||
|
@ -32,16 +34,20 @@ public class MonitoringSEBConnectionData {
|
|||
public final Collection<ClientConnectionData> connections;
|
||||
@JsonProperty(ATTR_STATUS_MAPPING)
|
||||
public final int[] connectionsPerStatus;
|
||||
@JsonProperty(ATTR_CLIENT_GROUP_MAPPING)
|
||||
public final Map<Long, Integer> connectionsPerClientGroup;
|
||||
|
||||
@JsonCreator
|
||||
public MonitoringSEBConnectionData(
|
||||
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) final Long examId,
|
||||
@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.connections = connections;
|
||||
this.connectionsPerStatus = connectionsPerStatus;
|
||||
this.connectionsPerClientGroup = connectionsPerClientGroup;
|
||||
}
|
||||
|
||||
public Long getExamId() {
|
||||
|
@ -64,6 +70,14 @@ public class MonitoringSEBConnectionData {
|
|||
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
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
|
|
|
@ -42,7 +42,8 @@ public enum ActionCategory {
|
|||
LOGS_USER_ACTIVITY_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),
|
||||
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),
|
||||
|
||||
FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1);
|
||||
|
|
|
@ -882,44 +882,54 @@ public enum ActionDefinition {
|
|||
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"),
|
||||
ImageIcon.TOGGLE_OFF,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.FILTER),
|
||||
ActionCategory.STATE_FILTER),
|
||||
MONITOR_EXAM_SHOW_REQUESTED_CONNECTION(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.show.requested"),
|
||||
ImageIcon.TOGGLE_ON,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.FILTER),
|
||||
ActionCategory.STATE_FILTER),
|
||||
|
||||
MONITOR_EXAM_HIDE_ACTIVE_CONNECTION(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.active"),
|
||||
ImageIcon.TOGGLE_OFF,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.FILTER),
|
||||
ActionCategory.STATE_FILTER),
|
||||
MONITOR_EXAM_SHOW_ACTIVE_CONNECTION(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.show.active"),
|
||||
ImageIcon.TOGGLE_ON,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.FILTER),
|
||||
|
||||
ActionCategory.STATE_FILTER),
|
||||
MONITOR_EXAM_HIDE_CLOSED_CONNECTION(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.closed"),
|
||||
ImageIcon.TOGGLE_OFF,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.FILTER),
|
||||
ActionCategory.STATE_FILTER),
|
||||
MONITOR_EXAM_SHOW_CLOSED_CONNECTION(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.show.closed"),
|
||||
ImageIcon.TOGGLE_ON,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.FILTER),
|
||||
ActionCategory.STATE_FILTER),
|
||||
MONITOR_EXAM_HIDE_DISABLED_CONNECTION(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.disabled"),
|
||||
ImageIcon.TOGGLE_OFF,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.FILTER),
|
||||
ActionCategory.STATE_FILTER),
|
||||
MONITOR_EXAM_SHOW_DISABLED_CONNECTION(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.show.disabled"),
|
||||
ImageIcon.TOGGLE_ON,
|
||||
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(
|
||||
new LocTextKey("sebserver.monitoring.search.action"),
|
||||
|
|
|
@ -247,26 +247,7 @@ public class ActionPane implements TemplateComposer {
|
|||
actionsTitle.setData(RWT.CUSTOM_VARIANT, "close");
|
||||
actionsTitle.setImage(WidgetFactory.ImageIcon.ACTIVE.getImage(parent.getDisplay()));
|
||||
actionsTitle.setText(" " + titleText);
|
||||
actionsTitle.addListener(SWT.MouseUp, event -> {
|
||||
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.addListener(SWT.MouseUp, event -> actionGroupExpand(composite, actionsTitle));
|
||||
}
|
||||
|
||||
actionsTitle.setLayoutData(titleLayout);
|
||||
|
@ -319,6 +300,27 @@ public class ActionPane implements TemplateComposer {
|
|||
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) {
|
||||
new ArrayList<>(actionTrees.entrySet())
|
||||
.forEach(entry -> {
|
||||
|
|
|
@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.gui.content.monitoring;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
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.FullPageMonitoringUpdate;
|
||||
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.ProctoringGUIService;
|
||||
|
||||
|
@ -258,6 +260,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
if (isExamSupporter.getAsBoolean()) {
|
||||
guiUpdates.add(createFilterActions(
|
||||
clientGroups,
|
||||
fullPageMonitoringUpdate,
|
||||
actionBuilder,
|
||||
clientTable,
|
||||
|
@ -347,13 +350,14 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
}
|
||||
|
||||
private FullPageMonitoringGUIUpdate createFilterActions(
|
||||
final MonitoringStatus monitoringStatus,
|
||||
final Collection<ClientGroup> clientGroups,
|
||||
final MonitoringFilter monitoringStatus,
|
||||
final PageActionBuilder actionBuilder,
|
||||
final ClientConnectionTable clientTable,
|
||||
final BooleanSupplier isExamSupporter) {
|
||||
|
||||
final StatusFilterGUIUpdate statusFilterGUIUpdate =
|
||||
new StatusFilterGUIUpdate(this.pageService.getPolyglotPageService());
|
||||
final FilterGUIUpdate statusFilterGUIUpdate =
|
||||
new FilterGUIUpdate(this.pageService.getPolyglotPageService());
|
||||
|
||||
addFilterAction(
|
||||
monitoringStatus,
|
||||
|
@ -388,61 +392,118 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
ActionDefinition.MONITOR_EXAM_SHOW_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;
|
||||
}
|
||||
|
||||
private void addFilterAction(
|
||||
final MonitoringStatus monitoringStatus,
|
||||
final StatusFilterGUIUpdate statusFilterGUIUpdate,
|
||||
final MonitoringFilter filter,
|
||||
final FilterGUIUpdate filterGUIUpdate,
|
||||
final PageActionBuilder actionBuilder,
|
||||
final ClientConnectionTable clientTable,
|
||||
final ConnectionStatus status,
|
||||
final ActionDefinition showActionDef,
|
||||
final ActionDefinition hideActionDef) {
|
||||
|
||||
final int numOfConnections = monitoringStatus.getNumOfConnections(status);
|
||||
if (monitoringStatus.isStatusHidden(status)) {
|
||||
final PageAction showAction = actionBuilder.newAction(showActionDef)
|
||||
.withExec(showStateViewAction(monitoringStatus, clientTable, status))
|
||||
final int numOfConnections = filter.getNumOfConnections(status);
|
||||
PageAction action;
|
||||
if (filter.isStatusHidden(status)) {
|
||||
action = actionBuilder.newAction(showActionDef)
|
||||
.withExec(showStateViewAction(filter, clientTable, status))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(hideActionDef)
|
||||
.withExec(
|
||||
hideStateViewAction(monitoringStatus, clientTable, status))
|
||||
hideStateViewAction(filter, clientTable, status))
|
||||
.noEventPropagation()
|
||||
.withNameAttributes(numOfConnections)
|
||||
.create())
|
||||
.withNameAttributes(numOfConnections)
|
||||
.create();
|
||||
this.pageService.publishAction(
|
||||
showAction,
|
||||
treeItem -> statusFilterGUIUpdate.register(treeItem, status));
|
||||
} else {
|
||||
final PageAction hideAction = actionBuilder.newAction(hideActionDef)
|
||||
.withExec(hideStateViewAction(monitoringStatus, clientTable, status))
|
||||
action = actionBuilder.newAction(hideActionDef)
|
||||
.withExec(hideStateViewAction(filter, clientTable, status))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(showActionDef)
|
||||
.withExec(
|
||||
showStateViewAction(monitoringStatus, clientTable, status))
|
||||
showStateViewAction(filter, clientTable, status))
|
||||
.noEventPropagation()
|
||||
.withNameAttributes(numOfConnections)
|
||||
.create())
|
||||
.withNameAttributes(numOfConnections)
|
||||
.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 */
|
||||
private class StatusFilterGUIUpdate implements FullPageMonitoringGUIUpdate {
|
||||
private class FilterGUIUpdate implements FullPageMonitoringGUIUpdate {
|
||||
|
||||
private final PolyglotPageService polyglotPageService;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -450,8 +511,12 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
this.actionItemPerStateFilter[status.code] = item;
|
||||
}
|
||||
|
||||
void register(final TreeItem item, final Long clientGroupId) {
|
||||
this.actionItemPerClientGroup.put(clientGroupId, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(final MonitoringStatus monitoringStatus) {
|
||||
public void update(final MonitoringFilter monitoringStatus) {
|
||||
final ConnectionStatus[] states = ConnectionStatus.values();
|
||||
for (int i = 0; i < states.length; i++) {
|
||||
final ConnectionStatus state = states[i];
|
||||
|
@ -463,6 +528,18 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
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(
|
||||
final MonitoringStatus monitoringStatus,
|
||||
final MonitoringFilter monitoringStatus,
|
||||
final ClientConnectionTable clientTable,
|
||||
final ConnectionStatus status) {
|
||||
|
||||
|
@ -484,7 +561,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
}
|
||||
|
||||
private static Function<PageAction, PageAction> hideStateViewAction(
|
||||
final MonitoringStatus monitoringStatus,
|
||||
final MonitoringFilter monitoringStatus,
|
||||
final ClientConnectionTable clientTable,
|
||||
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) {
|
||||
final Set<String> connectionTokens = clientTable.getConnectionTokens(
|
||||
cc -> cc.status.clientActiveStatus,
|
||||
|
|
|
@ -312,10 +312,10 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
|
|||
}
|
||||
|
||||
@Override
|
||||
public void update(final MonitoringStatus monitoringStatus) {
|
||||
public void update(final MonitoringFilter monitoringStatus) {
|
||||
final Collection<ClientConnectionData> connectionData = monitoringStatus.getConnectionData();
|
||||
final boolean sizeChanged = connectionData.size() != this.table.getItemCount();
|
||||
final boolean needsSync = monitoringStatus.statusFilterChanged() ||
|
||||
final boolean needsSync = monitoringStatus.filterChanged() ||
|
||||
this.forceUpdateAll ||
|
||||
sizeChanged ||
|
||||
(this.tableMapping != null &&
|
||||
|
@ -350,7 +350,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
|
|||
}
|
||||
}
|
||||
});
|
||||
monitoringStatus.resetStatusFilterChanged();
|
||||
monitoringStatus.resetFilterChanged();
|
||||
this.toDelete.clear();
|
||||
}
|
||||
|
||||
|
@ -624,7 +624,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
|
|||
private String getGroupInfo() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
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);
|
||||
sb.append(WidgetFactory.getTextWithBackgroundHTML(clientGroup.name, clientGroup.color));
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@ package ch.ethz.seb.sebserver.gui.service.session;
|
|||
@FunctionalInterface
|
||||
public interface FullPageMonitoringGUIUpdate {
|
||||
|
||||
void update(MonitoringStatus monitoringStatus);
|
||||
void update(MonitoringFilter monitoringStatus);
|
||||
|
||||
}
|
|
@ -10,7 +10,10 @@ package ch.ethz.seb.sebserver.gui.service.session;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.api.API;
|
||||
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.monitoring.MonitoringFullPageData;
|
||||
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.UpdateErrorHandler;
|
||||
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.auth.DisposedOAuth2RestTemplateException;
|
||||
|
||||
|
@ -36,11 +42,12 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2Re
|
|||
* full page monitoring.
|
||||
*
|
||||
* 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);
|
||||
|
||||
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 PageService pageService;
|
||||
|
@ -49,9 +56,13 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
|
|||
private final Collection<FullPageMonitoringGUIUpdate> guiUpdates;
|
||||
|
||||
private ServerPushContext pushContext;
|
||||
|
||||
private final EnumSet<ConnectionStatus> statusFilter;
|
||||
private String statusFilterParam = "";
|
||||
private boolean statusFilterChanged = false;
|
||||
private final Set<Long> clientGroupFilter;
|
||||
private String clientGroupFilterParam = "";
|
||||
private boolean filterChanged = false;
|
||||
|
||||
private boolean updateInProgress = false;
|
||||
private MonitoringFullPageData monitoringFullPageData = null;
|
||||
|
||||
|
@ -72,7 +83,19 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
|
|||
this.guiUpdates = guiUpdates;
|
||||
|
||||
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) {
|
||||
|
@ -105,13 +128,13 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean statusFilterChanged() {
|
||||
return this.statusFilterChanged;
|
||||
public boolean filterChanged() {
|
||||
return this.filterChanged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetStatusFilterChanged() {
|
||||
this.statusFilterChanged = false;
|
||||
public void resetFilterChanged() {
|
||||
this.filterChanged = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -122,13 +145,43 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
|
|||
@Override
|
||||
public void hideStatus(final ConnectionStatus status) {
|
||||
this.statusFilter.add(status);
|
||||
saveStatusFilter();
|
||||
saveFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showStatus(final ConnectionStatus 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
|
||||
|
@ -161,8 +214,15 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
|
|||
}
|
||||
|
||||
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()
|
||||
.get(error -> {
|
||||
recoverFromDisposedRestTemplate(error);
|
||||
|
@ -182,22 +242,32 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
|
|||
});
|
||||
}
|
||||
|
||||
private void saveStatusFilter() {
|
||||
private void saveFilter() {
|
||||
try {
|
||||
this.pageService
|
||||
.getCurrentUser()
|
||||
.putAttribute(
|
||||
USER_SESSION_STATUS_FILTER_ATTRIBUTE,
|
||||
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) {
|
||||
log.warn("Failed to save status filter to user session");
|
||||
} finally {
|
||||
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 {
|
||||
final String attribute = this.pageService
|
||||
.getCurrentUser()
|
||||
|
@ -206,17 +276,33 @@ public class FullPageMonitoringUpdate implements MonitoringStatus {
|
|||
if (attribute != null) {
|
||||
Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR))
|
||||
.forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name)));
|
||||
|
||||
} else {
|
||||
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) {
|
||||
log.warn("Failed to load status filter to user session");
|
||||
this.statusFilter.clear();
|
||||
this.statusFilter.add(ConnectionStatus.DISABLED);
|
||||
if (hasClientGroupFilter()) {
|
||||
this.clientGroupFilter.clear();
|
||||
}
|
||||
} finally {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,21 +12,22 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
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.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.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();
|
||||
|
||||
String getStatusFilterParam();
|
||||
|
||||
boolean statusFilterChanged();
|
||||
boolean filterChanged();
|
||||
|
||||
void resetStatusFilterChanged();
|
||||
void resetFilterChanged();
|
||||
|
||||
boolean isStatusHidden(ConnectionStatus status);
|
||||
|
||||
|
@ -34,6 +35,18 @@ public interface MonitoringStatus {
|
|||
|
||||
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();
|
||||
|
||||
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() {
|
||||
final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData();
|
||||
if (monitoringFullPageData != null) {
|
|
@ -194,6 +194,7 @@ public class ClientGroupDAOImpl implements ClientGroupDAO {
|
|||
.stream()
|
||||
.map(this::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.sorted()
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService
|
|||
@WebServiceProfile
|
||||
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_ACTIVE_CLIENT_CONNECTION = "ACTIVE_CLIENT_CONNECTION";
|
||||
public static final String CACHE_NAME_SEB_CONFIG_EXAM = "SEB_CONFIG_EXAM";
|
||||
|
|
|
@ -13,7 +13,9 @@ import java.io.OutputStream;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
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.ErrorMessage;
|
||||
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.ExamStatus;
|
||||
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.util.Result;
|
||||
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.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
|
@ -64,6 +68,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
private final IndicatorDAO indicatorDAO;
|
||||
private final ExamSessionCacheService examSessionCacheService;
|
||||
private final ExamDAO examDAO;
|
||||
private final ClientGroupDAO clientGroupDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
private final CacheManager cacheManager;
|
||||
private final SEBRestrictionService sebRestrictionService;
|
||||
|
@ -76,6 +81,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
protected ExamSessionServiceImpl(
|
||||
final ExamSessionCacheService examSessionCacheService,
|
||||
final ExamDAO examDAO,
|
||||
final ClientGroupDAO clientGroupDAO,
|
||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||
final ClientConnectionDAO clientConnectionDAO,
|
||||
final IndicatorDAO indicatorDAO,
|
||||
|
@ -87,6 +93,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
|
||||
this.examSessionCacheService = examSessionCacheService;
|
||||
this.examDAO = examDAO;
|
||||
this.clientGroupDAO = clientGroupDAO;
|
||||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||
this.clientConnectionDAO = clientConnectionDAO;
|
||||
this.cacheManager = cacheManager;
|
||||
|
@ -350,7 +357,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
|
||||
@Override
|
||||
public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) {
|
||||
synchronized (this.examSessionCacheService) {
|
||||
synchronized (ExamSessionCacheService.CLIENT_CONECTION_CREATION_LOCK) {
|
||||
return this.examSessionCacheService.getClientConnection(connectionToken);
|
||||
}
|
||||
}
|
||||
|
@ -405,25 +412,33 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
for (int i = 0; i < statusMapping.length; i++) {
|
||||
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);
|
||||
|
||||
synchronized (this.examSessionCacheService) {
|
||||
final List<ClientConnectionData> filteredConnections = this.clientConnectionDAO
|
||||
.getConnectionTokens(examId)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.map(token -> this.examSessionCacheService.getClientConnection(token))
|
||||
.filter(Objects::nonNull)
|
||||
.map(c -> {
|
||||
statusMapping[c.clientConnection.status.code]++;
|
||||
return c;
|
||||
})
|
||||
.filter(filter)
|
||||
.collect(Collectors.toList());
|
||||
final List<ClientConnectionData> filteredConnections = this.clientConnectionDAO
|
||||
.getConnectionTokens(examId)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.map(token -> getConnectionDataInternal(token))
|
||||
.filter(Objects::nonNull)
|
||||
.map(c -> {
|
||||
statusMapping[c.clientConnection.status.code]++;
|
||||
processClientGroupMapping(c.groups, clientGroupMapping);
|
||||
return c;
|
||||
})
|
||||
.filter(filter)
|
||||
.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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -692,8 +692,10 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
|||
|
||||
private void processPing(final String connectionToken, final long timestamp, final int pingNumber) {
|
||||
|
||||
final ClientConnectionDataInternal activeClientConnection =
|
||||
this.examSessionCacheService.getClientConnection(connectionToken);
|
||||
ClientConnectionDataInternal activeClientConnection = null;
|
||||
synchronized (ExamSessionCacheService.CLIENT_CONECTION_CREATION_LOCK) {
|
||||
activeClientConnection = this.examSessionCacheService.getClientConnection(connectionToken);
|
||||
}
|
||||
|
||||
if (activeClientConnection != null) {
|
||||
activeClientConnection.notifyPing(timestamp, pingNumber);
|
||||
|
|
|
@ -11,7 +11,9 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
|
@ -248,31 +250,17 @@ public class ExamMonitoringController {
|
|||
required = true,
|
||||
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_STATE_FILTER, required = false) final String hiddenStates,
|
||||
@RequestHeader(
|
||||
name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER,
|
||||
required = false) final String hiddenClientGroups) {
|
||||
|
||||
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
|
||||
.getMonitoringSEBConnectionsData(
|
||||
examId,
|
||||
filterStates.isEmpty()
|
||||
? Objects::nonNull
|
||||
: active
|
||||
? withActiveFilter(filterStates)
|
||||
: noneActiveFilter(filterStates))
|
||||
createMonitoringFilter(hiddenStates, hiddenClientGroups))
|
||||
.getOrThrow().connections;
|
||||
}
|
||||
|
||||
|
@ -288,31 +276,17 @@ public class ExamMonitoringController {
|
|||
required = true,
|
||||
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_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 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
|
||||
.getMonitoringSEBConnectionsData(
|
||||
examId,
|
||||
filterStates.isEmpty()
|
||||
? Objects::nonNull
|
||||
: active
|
||||
? withActiveFilter(filterStates)
|
||||
: noneActiveFilter(filterStates))
|
||||
createMonitoringFilter(hiddenStates, hiddenClientGroups))
|
||||
.getOrThrow();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1885,7 +1885,8 @@ sebserver.monitoring.exam.list.actions=
|
|||
sebserver.monitoring.exam.action.detail.view=Back To Monitoring
|
||||
sebserver.monitoring.exam.action.list.view=Monitoring
|
||||
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.monitoring.exam.action.proctoring.openTownhall=Open 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.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.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?
|
||||
|
@ -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.actions.group2=
|
||||
sebserver.monitoring.exam.connection.actions.group3=
|
||||
|
||||
sebserver.monitoring.exam.connection.notificationlist.actions=
|
||||
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.
|
||||
|
|
Loading…
Reference in a new issue