SEBSERV-147 GUI implementation
This commit is contained in:
parent
8e10995f0d
commit
3052ec1f59
24 changed files with 329 additions and 69 deletions
|
@ -100,7 +100,6 @@ public final class Indicator implements Entity {
|
||||||
public final String defaultIcon;
|
public final String defaultIcon;
|
||||||
|
|
||||||
@JsonProperty(INDICATOR.ATTR_TAGS)
|
@JsonProperty(INDICATOR.ATTR_TAGS)
|
||||||
@Size(min = 3, max = 255, message = "indicator:tag:size:{min}:{max}:${validatedValue}")
|
|
||||||
public final String tags;
|
public final String tags;
|
||||||
|
|
||||||
@JsonProperty(THRESHOLD.REFERENCE_NAME)
|
@JsonProperty(THRESHOLD.REFERENCE_NAME)
|
||||||
|
|
|
@ -33,14 +33,17 @@ public class ClientConnectionData {
|
||||||
public final List<? extends IndicatorValue> indicatorValues;
|
public final List<? extends IndicatorValue> indicatorValues;
|
||||||
|
|
||||||
public final Boolean missingPing;
|
public final Boolean missingPing;
|
||||||
|
public final Boolean pendingNotification;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public ClientConnectionData(
|
public ClientConnectionData(
|
||||||
@JsonProperty(ATTR_MISSING_PING) final Boolean missingPing,
|
@JsonProperty(ATTR_MISSING_PING) final Boolean missingPing,
|
||||||
|
@JsonProperty(ATTR_PENDING_NOTIFICATION) final Boolean pendingNotification,
|
||||||
@JsonProperty(ATTR_CLIENT_CONNECTION) final ClientConnection clientConnection,
|
@JsonProperty(ATTR_CLIENT_CONNECTION) final ClientConnection clientConnection,
|
||||||
@JsonProperty(ATTR_INDICATOR_VALUE) final Collection<? extends SimpleIndicatorValue> indicatorValues) {
|
@JsonProperty(ATTR_INDICATOR_VALUE) final Collection<? extends SimpleIndicatorValue> indicatorValues) {
|
||||||
|
|
||||||
this.missingPing = missingPing;
|
this.missingPing = missingPing;
|
||||||
|
this.pendingNotification = pendingNotification;
|
||||||
this.clientConnection = clientConnection;
|
this.clientConnection = clientConnection;
|
||||||
this.indicatorValues = Utils.immutableListOf(indicatorValues);
|
this.indicatorValues = Utils.immutableListOf(indicatorValues);
|
||||||
}
|
}
|
||||||
|
@ -50,6 +53,7 @@ public class ClientConnectionData {
|
||||||
final List<? extends IndicatorValue> indicatorValues) {
|
final List<? extends IndicatorValue> indicatorValues) {
|
||||||
|
|
||||||
this.missingPing = null;
|
this.missingPing = null;
|
||||||
|
this.pendingNotification = Boolean.FALSE;
|
||||||
this.clientConnection = clientConnection;
|
this.clientConnection = clientConnection;
|
||||||
this.indicatorValues = Utils.immutableListOf(indicatorValues);
|
this.indicatorValues = Utils.immutableListOf(indicatorValues);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +65,7 @@ public class ClientConnectionData {
|
||||||
|
|
||||||
@JsonProperty(ATTR_PENDING_NOTIFICATION)
|
@JsonProperty(ATTR_PENDING_NOTIFICATION)
|
||||||
public Boolean pendingNotification() {
|
public Boolean pendingNotification() {
|
||||||
return false;
|
return this.pendingNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
|
|
@ -26,7 +26,8 @@ public final class ClientInstruction {
|
||||||
public enum InstructionType {
|
public enum InstructionType {
|
||||||
SEB_QUIT,
|
SEB_QUIT,
|
||||||
SEB_PROCTORING,
|
SEB_PROCTORING,
|
||||||
SEB_RECONFIGURE_SETTINGS
|
SEB_RECONFIGURE_SETTINGS,
|
||||||
|
NOTIFICATION_CONFIRM
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ProctoringInstructionMethod {
|
public enum ProctoringInstructionMethod {
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gbl.model.session;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
|
|
||||||
|
public class ClientNotification extends ClientEvent {
|
||||||
|
|
||||||
|
public static enum NotificationType {
|
||||||
|
UNKNOWN(null),
|
||||||
|
LOCK_SCREEN("lockscreen"),
|
||||||
|
RAISE_HAND("raisehand");
|
||||||
|
|
||||||
|
public final String typeName;
|
||||||
|
|
||||||
|
private NotificationType(final String typeName) {
|
||||||
|
this.typeName = typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NotificationType getNotificationType(final String text) {
|
||||||
|
if (StringUtils.isBlank(text)) {
|
||||||
|
return NotificationType.UNKNOWN;
|
||||||
|
}
|
||||||
|
return Arrays.asList(NotificationType.values())
|
||||||
|
.stream()
|
||||||
|
|
||||||
|
.filter(type -> type.typeName != null &&
|
||||||
|
text.startsWith(Constants.ANGLE_BRACE_OPEN + type.typeName + Constants.ANGLE_BRACE_CLOSE))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(NotificationType.UNKNOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String ATTR_NOTIFICATION_TYPE = "notificationType";
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_NOTIFICATION_TYPE)
|
||||||
|
public final NotificationType notificationType;
|
||||||
|
|
||||||
|
public ClientNotification(
|
||||||
|
final Long id,
|
||||||
|
final Long connectionId,
|
||||||
|
final EventType eventType,
|
||||||
|
final Long clientTime,
|
||||||
|
final Long serverTime,
|
||||||
|
final Double numValue,
|
||||||
|
final String text) {
|
||||||
|
|
||||||
|
super(id, connectionId, eventType, clientTime, serverTime, numValue, text);
|
||||||
|
|
||||||
|
this.notificationType = NotificationType.getNotificationType(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public ClientNotification(
|
||||||
|
@JsonProperty(Domain.CLIENT_EVENT.ATTR_ID) final Long id,
|
||||||
|
@JsonProperty(Domain.CLIENT_EVENT.ATTR_CLIENT_CONNECTION_ID) final Long connectionId,
|
||||||
|
@JsonProperty(Domain.CLIENT_EVENT.ATTR_TYPE) final EventType eventType,
|
||||||
|
@JsonProperty(ATTR_TIMESTAMP) final Long clientTime,
|
||||||
|
@JsonProperty(Domain.CLIENT_EVENT.ATTR_SERVER_TIME) final Long serverTime,
|
||||||
|
@JsonProperty(Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE) final Double numValue,
|
||||||
|
@JsonProperty(Domain.CLIENT_EVENT.ATTR_TEXT) final String text,
|
||||||
|
@JsonProperty(ATTR_NOTIFICATION_TYPE) final NotificationType notificationType) {
|
||||||
|
|
||||||
|
super(id, connectionId, eventType, clientTime, serverTime, numValue, text);
|
||||||
|
this.notificationType = notificationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationType getNotificationType() {
|
||||||
|
return this.notificationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("ClientNotification [notificationType=");
|
||||||
|
builder.append(this.notificationType);
|
||||||
|
builder.append(", id=");
|
||||||
|
builder.append(this.id);
|
||||||
|
builder.append(", connectionId=");
|
||||||
|
builder.append(this.connectionId);
|
||||||
|
builder.append(", eventType=");
|
||||||
|
builder.append(this.eventType);
|
||||||
|
builder.append(", clientTime=");
|
||||||
|
builder.append(this.clientTime);
|
||||||
|
builder.append(", serverTime=");
|
||||||
|
builder.append(this.serverTime);
|
||||||
|
builder.append(", numValue=");
|
||||||
|
builder.append(this.numValue);
|
||||||
|
builder.append(", text=");
|
||||||
|
builder.append(this.text);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData;
|
||||||
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.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
|
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||||
|
@ -88,7 +89,11 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
private static final LocTextKey NOTIFICATION_LIST_TITLE_TOOLTIP_KEY =
|
private static final LocTextKey NOTIFICATION_LIST_TITLE_TOOLTIP_KEY =
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.notificationlist.title.tooltip");
|
new LocTextKey("sebserver.monitoring.exam.connection.notificationlist.title.tooltip");
|
||||||
private static final LocTextKey NOTIFICATION_LIST_CONFIRM_TEXT_KEY =
|
private static final LocTextKey NOTIFICATION_LIST_CONFIRM_TEXT_KEY =
|
||||||
new LocTextKey("monitoring.exam.connection.action.confirm.notification.text");
|
new LocTextKey("sebserver.monitoring.exam.connection.action.confirm.notification.text");
|
||||||
|
private static final LocTextKey NOTIFICATION_LIST_NO_SELECTION_KEY =
|
||||||
|
new LocTextKey("sebserver.monitoring.exam.connection.notificationlist.pleaseSelect");
|
||||||
|
private static final LocTextKey NOTIFICATION_LIST_COLUMN_TYPE_KEY =
|
||||||
|
new LocTextKey("sebserver.monitoring.exam.connection.notificationlist.type");
|
||||||
|
|
||||||
private static final LocTextKey EVENT_LIST_TITLE_KEY =
|
private static final LocTextKey EVENT_LIST_TITLE_KEY =
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.title");
|
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.title");
|
||||||
|
@ -98,6 +103,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.empty");
|
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.empty");
|
||||||
private static final LocTextKey LIST_COLUMN_TYPE_KEY =
|
private static final LocTextKey LIST_COLUMN_TYPE_KEY =
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.type");
|
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.type");
|
||||||
|
|
||||||
private static final LocTextKey LIST_COLUMN_CLIENT_TIME_KEY =
|
private static final LocTextKey LIST_COLUMN_CLIENT_TIME_KEY =
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.clienttime");
|
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.clienttime");
|
||||||
private static final LocTextKey LIST_COLUMN_SERVER_TIME_KEY =
|
private static final LocTextKey LIST_COLUMN_SERVER_TIME_KEY =
|
||||||
|
@ -212,6 +218,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
.clearAttributes()
|
.clearAttributes()
|
||||||
.clearEntityKeys());
|
.clearEntityKeys());
|
||||||
|
|
||||||
|
// NOTIFICATIONS
|
||||||
final boolean hasNotification = BooleanUtils.isTrue(connectionData.pendingNotification());
|
final boolean hasNotification = BooleanUtils.isTrue(connectionData.pendingNotification());
|
||||||
if (hasNotification) {
|
if (hasNotification) {
|
||||||
// add notification table
|
// add notification table
|
||||||
|
@ -221,7 +228,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
NOTIFICATION_LIST_TITLE_KEY,
|
NOTIFICATION_LIST_TITLE_KEY,
|
||||||
NOTIFICATION_LIST_TITLE_TOOLTIP_KEY);
|
NOTIFICATION_LIST_TITLE_TOOLTIP_KEY);
|
||||||
|
|
||||||
final EntityTable<ClientEvent> notificationTable = this.pageService.remoteListTableBuilder(
|
final EntityTable<ClientNotification> notificationTable = this.pageService.remoteListTableBuilder(
|
||||||
restService.getRestCall(GetPendingClientNotifications.class),
|
restService.getRestCall(GetPendingClientNotifications.class),
|
||||||
EntityType.CLIENT_EVENT)
|
EntityType.CLIENT_EVENT)
|
||||||
.withRestCallAdapter(builder -> builder.withURIVariable(
|
.withRestCallAdapter(builder -> builder.withURIVariable(
|
||||||
|
@ -230,22 +237,22 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
.withURIVariable(
|
.withURIVariable(
|
||||||
API.EXAM_API_SEB_CONNECTION_TOKEN,
|
API.EXAM_API_SEB_CONNECTION_TOKEN,
|
||||||
connectionToken))
|
connectionToken))
|
||||||
.withPaging(5)
|
.withPaging(-1)
|
||||||
.withColumn(new ColumnDefinition<ClientEvent>(
|
.withColumn(new ColumnDefinition<ClientNotification>(
|
||||||
Domain.CLIENT_EVENT.ATTR_TYPE,
|
ClientNotification.ATTR_NOTIFICATION_TYPE,
|
||||||
LIST_COLUMN_TYPE_KEY,
|
NOTIFICATION_LIST_COLUMN_TYPE_KEY,
|
||||||
this.resourceService::getEventTypeName)
|
this.resourceService::getNotificationTypeName)
|
||||||
.sortable()
|
.sortable()
|
||||||
.widthProportion(2))
|
.widthProportion(2))
|
||||||
|
|
||||||
.withColumn(new ColumnDefinition<>(
|
.withColumn(new ColumnDefinition<ClientNotification>(
|
||||||
Domain.CLIENT_EVENT.ATTR_TEXT,
|
Domain.CLIENT_EVENT.ATTR_TEXT,
|
||||||
LIST_COLUMN_TEXT_KEY,
|
LIST_COLUMN_TEXT_KEY,
|
||||||
ClientEvent::getText)
|
ClientEvent::getText)
|
||||||
.sortable()
|
.sortable()
|
||||||
.withCellTooltip()
|
.withCellTooltip()
|
||||||
.widthProportion(4))
|
.widthProportion(4))
|
||||||
.withColumn(new ColumnDefinition<>(
|
.withColumn(new ColumnDefinition<ClientNotification>(
|
||||||
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
|
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
|
||||||
new LocTextKey(LIST_COLUMN_SERVER_TIME_KEY.name,
|
new LocTextKey(LIST_COLUMN_SERVER_TIME_KEY.name,
|
||||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||||
|
@ -268,10 +275,15 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_CONFIRM_NOTIFICATION)
|
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_CONFIRM_NOTIFICATION)
|
||||||
.withParentEntityKey(parentEntityKey)
|
.withParentEntityKey(parentEntityKey)
|
||||||
.withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY)
|
.withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY)
|
||||||
.withExec(action -> this.confirmNotification(action, connectionData))
|
.withSelect(
|
||||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER));
|
() -> notificationTable.getSelection(),
|
||||||
|
action -> this.confirmNotification(action, connectionData),
|
||||||
|
NOTIFICATION_LIST_NO_SELECTION_KEY)
|
||||||
|
|
||||||
|
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CLIENT EVENTS
|
||||||
widgetFactory.addFormSubContextHeader(
|
widgetFactory.addFormSubContextHeader(
|
||||||
content,
|
content,
|
||||||
EVENT_LIST_TITLE_KEY,
|
EVENT_LIST_TITLE_KEY,
|
||||||
|
@ -401,7 +413,13 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
.call()
|
.call()
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
return pageAction;
|
return pageAction
|
||||||
|
.withEntityKey(
|
||||||
|
new EntityKey(connectionData.getConnectionId(),
|
||||||
|
EntityType.CLIENT_CONNECTION))
|
||||||
|
.withAttribute(
|
||||||
|
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
|
||||||
|
connectionData.clientConnection.connectionToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PageAction openExamCollectionProctorScreen(
|
private PageAction openExamCollectionProctorScreen(
|
||||||
|
|
|
@ -25,6 +25,8 @@ public enum ActionCategory {
|
||||||
SEB_CONFIG_TEMPLATE_LIST(new LocTextKey("sebserver.configtemplate.list.actions"), 1),
|
SEB_CONFIG_TEMPLATE_LIST(new LocTextKey("sebserver.configtemplate.list.actions"), 1),
|
||||||
SEB_CONFIG_TEMPLATE_ATTRIBUTE_LIST(new LocTextKey("sebserver.configtemplate.attr.list.actions"), 1),
|
SEB_CONFIG_TEMPLATE_ATTRIBUTE_LIST(new LocTextKey("sebserver.configtemplate.attr.list.actions"), 1),
|
||||||
RUNNING_EXAM_LIST(new LocTextKey("sebserver.monitoring.exam.list.actions"), 1),
|
RUNNING_EXAM_LIST(new LocTextKey("sebserver.monitoring.exam.list.actions"), 1),
|
||||||
|
EXAM_MONITORING_NOTIFICATION_LIST(new LocTextKey("sebserver.monitoring.exam.connection.notificationlist.actions"),
|
||||||
|
1),
|
||||||
CLIENT_EVENT_LIST(new LocTextKey("sebserver.monitoring.exam.connection.list.actions"), 1),
|
CLIENT_EVENT_LIST(new LocTextKey("sebserver.monitoring.exam.connection.list.actions"), 1),
|
||||||
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),
|
||||||
|
|
|
@ -644,7 +644,7 @@ public enum ActionDefinition {
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.action.confirm.notification"),
|
new LocTextKey("sebserver.monitoring.exam.connection.action.confirm.notification"),
|
||||||
ImageIcon.YES,
|
ImageIcon.YES,
|
||||||
PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION,
|
PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION,
|
||||||
ActionCategory.FORM),
|
ActionCategory.EXAM_MONITORING_NOTIFICATION_LIST),
|
||||||
|
|
||||||
MONITOR_EXAM_QUIT_SELECTED(
|
MONITOR_EXAM_QUIT_SELECTED(
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected"),
|
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected"),
|
||||||
|
|
|
@ -52,6 +52,8 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus
|
||||||
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.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification.NotificationType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
|
||||||
|
@ -118,6 +120,8 @@ public class ResourceService {
|
||||||
AttributeType.INLINE_TABLE);
|
AttributeType.INLINE_TABLE);
|
||||||
|
|
||||||
public static final String CLIENT_EVENT_TYPE_PREFIX = "sebserver.monitoring.exam.connection.event.type.";
|
public static final String CLIENT_EVENT_TYPE_PREFIX = "sebserver.monitoring.exam.connection.event.type.";
|
||||||
|
public static final String CLIENT_NOTIFICATION_TYPE_PREFIX =
|
||||||
|
"sebserver.monitoring.exam.connection.notification.type.";
|
||||||
public static final String USER_ACTIVITY_TYPE_PREFIX = "sebserver.overall.types.activityType.";
|
public static final String USER_ACTIVITY_TYPE_PREFIX = "sebserver.overall.types.activityType.";
|
||||||
public static final String ENTITY_TYPE_PREFIX = "sebserver.overall.types.entityType.";
|
public static final String ENTITY_TYPE_PREFIX = "sebserver.overall.types.entityType.";
|
||||||
public static final String SEB_CONNECTION_STATUS_KEY_PREFIX = "sebserver.monitoring.exam.connection.status.";
|
public static final String SEB_CONNECTION_STATUS_KEY_PREFIX = "sebserver.monitoring.exam.connection.status.";
|
||||||
|
@ -200,6 +204,22 @@ public class ResourceService {
|
||||||
return this.i18nSupport.getText(CLIENT_EVENT_TYPE_PREFIX + eventType.name(), eventType.name());
|
return this.i18nSupport.getText(CLIENT_EVENT_TYPE_PREFIX + eventType.name(), eventType.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNotificationTypeName(final ClientNotification notification) {
|
||||||
|
if (notification == null) {
|
||||||
|
return getEventTypeName(EventType.UNKNOWN);
|
||||||
|
}
|
||||||
|
return getNotificationTypeName(notification.getNotificationType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotificationTypeName(final NotificationType notificationType) {
|
||||||
|
if (notificationType == null) {
|
||||||
|
return Constants.EMPTY_NOTE;
|
||||||
|
}
|
||||||
|
return this.i18nSupport.getText(
|
||||||
|
CLIENT_NOTIFICATION_TYPE_PREFIX + notificationType.name(),
|
||||||
|
notificationType.name());
|
||||||
|
}
|
||||||
|
|
||||||
public List<Tuple<String>> indicatorTypeResources() {
|
public List<Tuple<String>> indicatorTypeResources() {
|
||||||
return Arrays.stream(IndicatorType.values())
|
return Arrays.stream(IndicatorType.values())
|
||||||
.map(type -> new Tuple3<>(
|
.map(type -> new Tuple3<>(
|
||||||
|
|
|
@ -339,6 +339,7 @@ public class PageContextImpl implements PageContext {
|
||||||
this.i18nSupport);
|
this.i18nSupport);
|
||||||
messageBox.setMarkupEnabled(true);
|
messageBox.setMarkupEnabled(true);
|
||||||
messageBox.open(null);
|
messageBox.open(null);
|
||||||
|
log.error("Unexpected error on GUI: ", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class ConfirmPendingClientNotification extends RestCall<Void> {
|
||||||
new TypeReference<Void>() {
|
new TypeReference<Void>() {
|
||||||
}),
|
}),
|
||||||
HttpMethod.POST,
|
HttpMethod.POST,
|
||||||
MediaType.APPLICATION_JSON_UTF8,
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
API.EXAM_MONITORING_ENDPOINT
|
API.EXAM_MONITORING_ENDPOINT
|
||||||
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||||
+ API.EXAM_MONITORING_NOTIFICATION_ENDPOINT
|
+ API.EXAM_MONITORING_NOTIFICATION_ENDPOINT
|
||||||
|
|
|
@ -19,20 +19,20 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
@GuiProfile
|
@GuiProfile
|
||||||
public class GetPendingClientNotifications extends RestCall<Collection<ClientEvent>> {
|
public class GetPendingClientNotifications extends RestCall<Collection<ClientNotification>> {
|
||||||
|
|
||||||
public GetPendingClientNotifications() {
|
public GetPendingClientNotifications() {
|
||||||
super(new TypeKey<>(
|
super(new TypeKey<>(
|
||||||
CallType.GET_LIST,
|
CallType.GET_LIST,
|
||||||
EntityType.CLIENT_EVENT,
|
EntityType.CLIENT_EVENT,
|
||||||
new TypeReference<Collection<ClientEvent>>() {
|
new TypeReference<Collection<ClientNotification>>() {
|
||||||
}),
|
}),
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
MediaType.APPLICATION_FORM_URLENCODED,
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
|
|
|
@ -513,13 +513,9 @@ public final class ClientConnectionTable {
|
||||||
if (list != null && list.size() > 1) {
|
if (list != null && list.size() > 1) {
|
||||||
tableItem.setBackground(0, ClientConnectionTable.this.colorData.color3);
|
tableItem.setBackground(0, ClientConnectionTable.this.colorData.color3);
|
||||||
tableItem.setForeground(0, ClientConnectionTable.this.lightFontColor);
|
tableItem.setForeground(0, ClientConnectionTable.this.lightFontColor);
|
||||||
tableItem.setImage(1,
|
|
||||||
WidgetFactory.ImageIcon.ADD.getImage(ClientConnectionTable.this.table.getDisplay()));
|
|
||||||
} else {
|
} else {
|
||||||
tableItem.setBackground(0, null);
|
tableItem.setBackground(0, null);
|
||||||
tableItem.setForeground(0, ClientConnectionTable.this.darkFontColor);
|
tableItem.setForeground(0, ClientConnectionTable.this.darkFontColor);
|
||||||
tableItem.setImage(0, null);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,7 +218,7 @@ public class EntityTable<ROW> {
|
||||||
|
|
||||||
this.table.addListener(SWT.Selection, event -> this.notifySelectionChange());
|
this.table.addListener(SWT.Selection, event -> this.notifySelectionChange());
|
||||||
|
|
||||||
this.navigator = new TableNavigator(this);
|
this.navigator = (pageSize > 0) ? new TableNavigator(this) : new TableNavigator();
|
||||||
|
|
||||||
createTableColumns();
|
createTableColumns();
|
||||||
this.pageNumber = initCurrentPageFromUserAttr();
|
this.pageNumber = initCurrentPageFromUserAttr();
|
||||||
|
|
|
@ -108,6 +108,10 @@ public class RemoteListPageSupplier<T> implements PageSupplier<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final int numOfPages = list.size() / this.pageSize;
|
final int numOfPages = list.size() / this.pageSize;
|
||||||
|
if (numOfPages <= 0) {
|
||||||
|
return new Page<>(1, 1, this.column, list);
|
||||||
|
}
|
||||||
|
|
||||||
final List<T> subList = list.subList(this.pageNumber * this.pageSize,
|
final List<T> subList = list.subList(this.pageNumber * this.pageSize,
|
||||||
this.pageNumber * this.pageSize + this.pageSize);
|
this.pageNumber * this.pageSize + this.pageSize);
|
||||||
return new Page<>(numOfPages, this.pageNumber, this.column, subList);
|
return new Page<>(numOfPages, this.pageNumber, this.column, subList);
|
||||||
|
|
|
@ -102,6 +102,10 @@ public class StaticListPageSupplier<T> implements PageSupplier<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final int numOfPages = this.list.size() / this.pageSize;
|
final int numOfPages = this.list.size() / this.pageSize;
|
||||||
|
|
||||||
|
if (numOfPages <= 0) {
|
||||||
|
return new Page<>(1, 1, this.column, this.list);
|
||||||
|
}
|
||||||
final List<T> subList = this.list.subList(this.pageNumber * this.pageSize,
|
final List<T> subList = this.list.subList(this.pageNumber * this.pageSize,
|
||||||
this.pageNumber * this.pageSize + this.pageSize);
|
this.pageNumber * this.pageSize + this.pageSize);
|
||||||
return new Page<>(numOfPages, this.pageNumber, this.column, subList);
|
return new Page<>(numOfPages, this.pageNumber, this.column, subList);
|
||||||
|
|
|
@ -26,6 +26,11 @@ public class TableNavigator {
|
||||||
private final Composite composite;
|
private final Composite composite;
|
||||||
private final EntityTable<?> entityTable;
|
private final EntityTable<?> entityTable;
|
||||||
|
|
||||||
|
TableNavigator() {
|
||||||
|
this.composite = null;
|
||||||
|
this.entityTable = null;
|
||||||
|
}
|
||||||
|
|
||||||
TableNavigator(final EntityTable<?> entityTable) {
|
TableNavigator(final EntityTable<?> entityTable) {
|
||||||
this.composite = new Composite(entityTable.composite, SWT.NONE);
|
this.composite = new Composite(entityTable.composite, SWT.NONE);
|
||||||
final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, true);
|
final GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, true, true);
|
||||||
|
@ -37,6 +42,10 @@ public class TableNavigator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<?> update(final Page<?> pageData) {
|
public Page<?> update(final Page<?> pageData) {
|
||||||
|
if (this.composite == null) {
|
||||||
|
return pageData;
|
||||||
|
}
|
||||||
|
|
||||||
// clear all
|
// clear all
|
||||||
PageService.clearComposite(this.composite);
|
PageService.clearComposite(this.composite);
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
|
@ -27,8 +28,10 @@ public interface ClientEventDAO extends EntityDAO<ClientEvent, ClientEvent> {
|
||||||
FilterMap filterMap,
|
FilterMap filterMap,
|
||||||
Predicate<ExtendedClientEvent> predicate);
|
Predicate<ExtendedClientEvent> predicate);
|
||||||
|
|
||||||
Result<List<ClientEvent>> getPendingNotifications(Long clientConnectionId);
|
Result<ClientNotification> getPendingNotification(Long notificationId);
|
||||||
|
|
||||||
Result<ClientEvent> confirmPendingNotification(Long notificationId, Long clientConnectionId);
|
Result<List<ClientNotification>> getPendingNotifications(Long clientConnectionId);
|
||||||
|
|
||||||
|
Result<ClientNotification> confirmPendingNotification(Long notificationId, Long clientConnectionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||||
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;
|
||||||
|
@ -187,21 +188,31 @@ public class ClientEventDAOImpl implements ClientEventDAO {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<List<ClientEvent>> getPendingNotifications(final Long clientConnectionId) {
|
public Result<ClientNotification> getPendingNotification(final Long notificationId) {
|
||||||
return Result.tryCatch(() -> this.clientEventRecordMapper.selectByExample()
|
return Result.tryCatch(() -> this.clientEventRecordMapper
|
||||||
|
.selectByPrimaryKey(notificationId))
|
||||||
|
.flatMap(ClientEventDAOImpl::toClientNotificationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<List<ClientNotification>> getPendingNotifications(final Long clientConnectionId) {
|
||||||
|
return Result.tryCatch(() -> this.clientEventRecordMapper
|
||||||
|
.selectByExample()
|
||||||
.where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(clientConnectionId))
|
.where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(clientConnectionId))
|
||||||
.and(ClientEventRecordDynamicSqlSupport.type, isEqualTo(EventType.NOTIFICATION.id))
|
.and(ClientEventRecordDynamicSqlSupport.type, isEqualTo(EventType.NOTIFICATION.id))
|
||||||
.build()
|
.build()
|
||||||
.execute()
|
.execute()
|
||||||
.stream()
|
.stream()
|
||||||
.map(ClientEventDAOImpl::toDomainModel)
|
.map(ClientEventDAOImpl::toClientNotificationModel)
|
||||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<ClientEvent> confirmPendingNotification(final Long notificationId, final Long clientConnectionId) {
|
public Result<ClientNotification> confirmPendingNotification(final Long notificationId,
|
||||||
|
final Long clientConnectionId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
final Long pk = this.clientEventRecordMapper.selectIdsByExample()
|
final Long pk = this.clientEventRecordMapper.selectIdsByExample()
|
||||||
.where(ClientEventRecordDynamicSqlSupport.id, isEqualTo(notificationId))
|
.where(ClientEventRecordDynamicSqlSupport.id, isEqualTo(notificationId))
|
||||||
|
@ -218,7 +229,7 @@ public class ClientEventDAOImpl implements ClientEventDAO {
|
||||||
|
|
||||||
return this.clientEventRecordMapper.selectByPrimaryKey(pk);
|
return this.clientEventRecordMapper.selectByPrimaryKey(pk);
|
||||||
})
|
})
|
||||||
.flatMap(ClientEventDAOImpl::toDomainModel)
|
.flatMap(ClientEventDAOImpl::toClientNotificationModel)
|
||||||
.onError(TransactionHandler::rollback);
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,6 +297,22 @@ public class ClientEventDAOImpl implements ClientEventDAO {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Result<ClientNotification> toClientNotificationModel(final ClientEventRecord record) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
final Integer type = record.getType();
|
||||||
|
final BigDecimal numericValue = record.getNumericValue();
|
||||||
|
return new ClientNotification(
|
||||||
|
record.getId(),
|
||||||
|
record.getClientConnectionId(),
|
||||||
|
(type != null) ? EventType.byId(type) : EventType.UNKNOWN,
|
||||||
|
record.getClientTime(),
|
||||||
|
record.getServerTime(),
|
||||||
|
(numericValue != null) ? numericValue.doubleValue() : null,
|
||||||
|
record.getText());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static Result<ClientEvent> toDomainModel(final ClientEventRecord record) {
|
private static Result<ClientEvent> toDomainModel(final ClientEventRecord record) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,8 @@ public interface SEBClientInstructionService {
|
||||||
*
|
*
|
||||||
* @param clientInstruction the ClientInstruction instance to register
|
* @param clientInstruction the ClientInstruction instance to register
|
||||||
* @return A Result refer to a void marker or to an error if happened */
|
* @return A Result refer to a void marker or to an error if happened */
|
||||||
default Result<Void> registerInstruction(final ClientInstruction clientInstructionn) {
|
default Result<Void> registerInstruction(final ClientInstruction clientInstruction) {
|
||||||
return registerInstruction(clientInstructionn, false);
|
return registerInstruction(clientInstruction, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Used to register a SEB client instruction for one or more active client connections
|
/** Used to register a SEB client instruction for one or more active client connections
|
||||||
|
|
|
@ -10,39 +10,28 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.cache.annotation.CacheEvict;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
/** Service to maintain SEB Client notifications. */
|
/** Service to maintain SEB Client notifications. */
|
||||||
public interface SEBClientNotificationService {
|
public interface SEBClientNotificationService {
|
||||||
|
|
||||||
public static final String CACHE_CLIENT_NOTIFICATION = "LIENT_NOTIFICATION_CACHE";
|
public static final String CACHE_CLIENT_NOTIFICATION = "CLIENT_NOTIFICATION_CACHE";
|
||||||
|
|
||||||
/** Indicates whether the client connection with the specified identifier has any
|
/** Indicates whether the client connection with the specified identifier has any
|
||||||
* pending notification or not. Pending means a non-confirmed notification
|
* pending notification or not. Pending means a non-confirmed notification
|
||||||
*
|
*
|
||||||
* @param clientConnectionId the client connection identifier
|
* @param clientConnectionId the client connection identifier
|
||||||
* @return true if there is any pending notification for the specified client connection */
|
* @return true if there is any pending notification for the specified client connection */
|
||||||
@Cacheable(
|
|
||||||
cacheNames = CACHE_CLIENT_NOTIFICATION,
|
|
||||||
key = "#clientConnectionId",
|
|
||||||
condition = "#result != null && #result")
|
|
||||||
Boolean hasAnyPendingNotification(Long clientConnectionId);
|
Boolean hasAnyPendingNotification(Long clientConnectionId);
|
||||||
|
|
||||||
Result<List<ClientEvent>> getPendingNotifications(Long clientConnectionId);
|
Result<List<ClientNotification>> getPendingNotifications(Long clientConnectionId);
|
||||||
|
|
||||||
@CacheEvict(
|
Result<ClientNotification> confirmPendingNotification(
|
||||||
cacheNames = CACHE_CLIENT_NOTIFICATION,
|
Long notificationId,
|
||||||
key = "#clientConnectionId")
|
Long examId,
|
||||||
Result<ClientEvent> confirmPendingNotification(Long notificationId, final Long clientConnectionId);
|
String connectionToken);
|
||||||
|
|
||||||
@CacheEvict(
|
void notifyNewNotification(final Long clientConnectionId);
|
||||||
cacheNames = CACHE_CLIENT_NOTIFICATION,
|
|
||||||
key = "#clientConnectionId")
|
|
||||||
default void notifyNewNotification(final Long clientConnectionId) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,22 @@
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
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.ClientEventDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -25,27 +32,85 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificati
|
||||||
@WebServiceProfile
|
@WebServiceProfile
|
||||||
public class SEBClientNotificationServiceImpl implements SEBClientNotificationService {
|
public class SEBClientNotificationServiceImpl implements SEBClientNotificationService {
|
||||||
|
|
||||||
private final ClientEventDAO clientEventDAO;
|
private static final String CONFIRM_INSTRUCTION_ATTR_ID = "id";
|
||||||
|
private static final String CONFIRM_INSTRUCTION_ATTR_TYPE = "type";
|
||||||
|
|
||||||
|
private final ClientEventDAO clientEventDAO;
|
||||||
|
private final SEBClientInstructionService sebClientInstructionService;
|
||||||
|
private final Set<Long> pendingNotifications;
|
||||||
|
|
||||||
|
public SEBClientNotificationServiceImpl(
|
||||||
|
final ClientEventDAO clientEventDAO,
|
||||||
|
final SEBClientInstructionService sebClientInstructionService) {
|
||||||
|
|
||||||
public SEBClientNotificationServiceImpl(final ClientEventDAO clientEventDAO) {
|
|
||||||
this.clientEventDAO = clientEventDAO;
|
this.clientEventDAO = clientEventDAO;
|
||||||
|
this.sebClientInstructionService = sebClientInstructionService;
|
||||||
|
this.pendingNotifications = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean hasAnyPendingNotification(final Long clientConnectionId) {
|
public Boolean hasAnyPendingNotification(final Long clientConnectionId) {
|
||||||
return !getPendingNotifications(clientConnectionId)
|
if (this.pendingNotifications.contains(clientConnectionId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean hasAnyPendingNotification = !getPendingNotifications(clientConnectionId)
|
||||||
.getOr(Collections.emptyList())
|
.getOr(Collections.emptyList())
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
|
if (hasAnyPendingNotification) {
|
||||||
|
this.pendingNotifications.add(clientConnectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasAnyPendingNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<List<ClientEvent>> getPendingNotifications(final Long clientConnectionId) {
|
public Result<List<ClientNotification>> getPendingNotifications(final Long clientConnectionId) {
|
||||||
return this.clientEventDAO.getPendingNotifications(clientConnectionId);
|
return this.clientEventDAO.getPendingNotifications(clientConnectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<ClientEvent> confirmPendingNotification(final Long notificatioId, final Long clientConnectionId) {
|
public Result<ClientNotification> confirmPendingNotification(
|
||||||
return this.clientEventDAO.confirmPendingNotification(notificatioId, clientConnectionId);
|
final Long notificationId,
|
||||||
|
final Long examId,
|
||||||
|
final String connectionToken) {
|
||||||
|
|
||||||
|
return this.clientEventDAO.getPendingNotification(notificationId)
|
||||||
|
.map(notification -> this.confirmClientSide(notification, examId, connectionToken))
|
||||||
|
.flatMap(notification -> this.clientEventDAO.confirmPendingNotification(
|
||||||
|
notificationId,
|
||||||
|
notification.connectionId))
|
||||||
|
.map(this::removeFromCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyNewNotification(final Long clientConnectionId) {
|
||||||
|
this.pendingNotifications.add(clientConnectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientNotification confirmClientSide(
|
||||||
|
final ClientNotification notification,
|
||||||
|
final Long examId,
|
||||||
|
final String connectionToken) {
|
||||||
|
|
||||||
|
// create and send confirming SEB instruction to confirm the notification on client side
|
||||||
|
final Map<String, String> attributes = new HashMap<>();
|
||||||
|
attributes.put(CONFIRM_INSTRUCTION_ATTR_TYPE, notification.getNotificationType().typeName);
|
||||||
|
attributes.put(CONFIRM_INSTRUCTION_ATTR_ID, String.valueOf((int) notification.getValue()));
|
||||||
|
final ClientInstruction clientInstruction = new ClientInstruction(
|
||||||
|
null,
|
||||||
|
examId,
|
||||||
|
InstructionType.NOTIFICATION_CONFIRM,
|
||||||
|
connectionToken,
|
||||||
|
attributes);
|
||||||
|
this.sebClientInstructionService.registerInstruction(clientInstruction);
|
||||||
|
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientNotification removeFromCache(final ClientNotification notification) {
|
||||||
|
this.pendingNotifications.remove(notification.connectionId);
|
||||||
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,8 +41,8 @@ import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||||
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.session.ClientConnection.ConnectionStatus;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
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.ClientEvent;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||||
|
@ -252,8 +252,9 @@ public class ExamMonitoringController {
|
||||||
API.EXAM_MONITORING_NOTIFICATION_ENDPOINT +
|
API.EXAM_MONITORING_NOTIFICATION_ENDPOINT +
|
||||||
API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
|
API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
|
||||||
method = RequestMethod.GET,
|
method = RequestMethod.GET,
|
||||||
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
public Collection<ClientEvent> pendingNotifications(
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
|
public Collection<ClientNotification> pendingNotifications(
|
||||||
@RequestParam(
|
@RequestParam(
|
||||||
name = API.PARAM_INSTITUTION_ID,
|
name = API.PARAM_INSTITUTION_ID,
|
||||||
required = true,
|
required = true,
|
||||||
|
@ -276,7 +277,7 @@ public class ExamMonitoringController {
|
||||||
API.MODEL_ID_VAR_PATH_SEGMENT +
|
API.MODEL_ID_VAR_PATH_SEGMENT +
|
||||||
API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
|
API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
|
||||||
method = RequestMethod.POST,
|
method = RequestMethod.POST,
|
||||||
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||||
public void confirmNotification(
|
public void confirmNotification(
|
||||||
@RequestParam(
|
@RequestParam(
|
||||||
name = API.PARAM_INSTITUTION_ID,
|
name = API.PARAM_INSTITUTION_ID,
|
||||||
|
@ -286,13 +287,10 @@ public class ExamMonitoringController {
|
||||||
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long notificationId,
|
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long notificationId,
|
||||||
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
|
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
|
||||||
|
|
||||||
final ClientConnectionData connection = getConnectionDataForSingleConnection(
|
|
||||||
institutionId,
|
|
||||||
examId,
|
|
||||||
connectionToken);
|
|
||||||
this.sebClientNotificationService.confirmPendingNotification(
|
this.sebClientNotificationService.confirmPendingNotification(
|
||||||
notificationId,
|
notificationId,
|
||||||
connection.getConnectionId())
|
examId,
|
||||||
|
connectionToken)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1522,9 +1522,12 @@ sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined
|
||||||
sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined
|
sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined
|
||||||
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.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.
|
||||||
|
|
||||||
|
sebserver.monitoring.exam.connection.notificationlist.pleaseSelect=At first please select a notification form the Pending Notification list
|
||||||
sebserver.monitoring.exam.connection.notificationlist.title=Pending Notification
|
sebserver.monitoring.exam.connection.notificationlist.title=Pending Notification
|
||||||
sebserver.monitoring.exam.connection.notificationlist.title.tooltip=All pending notifications sent by the SEB Client
|
sebserver.monitoring.exam.connection.notificationlist.title.tooltip=All pending notifications sent by the SEB Client
|
||||||
|
|
||||||
|
@ -1549,6 +1552,13 @@ sebserver.monitoring.exam.connection.event.type.INFO_LOG=Info
|
||||||
sebserver.monitoring.exam.connection.event.type.WARN_LOG=Warn
|
sebserver.monitoring.exam.connection.event.type.WARN_LOG=Warn
|
||||||
sebserver.monitoring.exam.connection.event.type.ERROR_LOG=Error
|
sebserver.monitoring.exam.connection.event.type.ERROR_LOG=Error
|
||||||
sebserver.monitoring.exam.connection.event.type.LAST_PING=Last Ping
|
sebserver.monitoring.exam.connection.event.type.LAST_PING=Last Ping
|
||||||
|
sebserver.monitoring.exam.connection.event.type.NOTIFICATION=Notification (pending)
|
||||||
|
sebserver.monitoring.exam.connection.event.type.NOTIFICATION_CONFIRM=Notification (confirmed)
|
||||||
|
|
||||||
|
sebserver.monitoring.exam.connection.notification.type.UNKNOWN=Unknown
|
||||||
|
sebserver.monitoring.exam.connection.notification.type.LOCK_SCREEN=Lock Screen
|
||||||
|
sebserver.monitoring.exam.connection.notification.type.RAISE_HAND=Raise Hand
|
||||||
|
sebserver.monitoring.exam.connection.notificationlist.type=Notification Type
|
||||||
|
|
||||||
sebserver.monitoring.exam.connection.status.UNDEFINED=Undefined
|
sebserver.monitoring.exam.connection.status.UNDEFINED=Undefined
|
||||||
sebserver.monitoring.exam.connection.status.CONNECTION_REQUESTED=Connection Requested
|
sebserver.monitoring.exam.connection.status.CONNECTION_REQUESTED=Connection Requested
|
||||||
|
|
|
@ -244,6 +244,7 @@ public class ModelObjectJSONGenerator {
|
||||||
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));
|
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));
|
||||||
|
|
||||||
domainObject = new ClientConnectionData(
|
domainObject = new ClientConnectionData(
|
||||||
|
false,
|
||||||
false,
|
false,
|
||||||
new ClientConnection(
|
new ClientConnection(
|
||||||
1L, 1L, 1L, ConnectionStatus.ACTIVE, UUID.randomUUID().toString(),
|
1L, 1L, 1L, ConnectionStatus.ACTIVE, UUID.randomUUID().toString(),
|
||||||
|
|
Loading…
Add table
Reference in a new issue