diff --git a/pom.xml b/pom.xml
index 2939d6a2..47304872 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,6 +25,7 @@
UTF-8
+
@@ -45,6 +46,17 @@
maven-compiler-plugin
${java.version}
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ UTF-8
+
+ UTF-8
+
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java
index 118c5ccf..0721d85f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java
@@ -12,6 +12,7 @@ import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.Collection;
import java.util.Optional;
+import java.util.function.Supplier;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.rap.rwt.RWT;
@@ -203,25 +204,15 @@ public class MonitoringClientConnection implements TemplateComposer {
getConnectionData,
indicators);
- this.serverPushService.runServerPush(
- new ServerPushContext(
- content,
- Utils.truePredicate(),
- MonitoringRunningExam.createServerPushUpdateErrorHandler(this.pageService, pageContext)),
- this.pollInterval,
- context1 -> clientConnectionDetails.updateData(),
- context -> clientConnectionDetails.updateGUI());
-
- final PageService.PageActionBuilder actionBuilder = this.pageService
- .pageActionBuilder(
- pageContext
- .clearAttributes()
- .clearEntityKeys());
-
// NOTIFICATIONS
- final boolean hasNotification = BooleanUtils.isTrue(connectionData.pendingNotification());
- if (hasNotification) {
- // add notification table
+ final boolean hasNotifications = BooleanUtils.isTrue(connectionData.pendingNotification());
+ Supplier> _notificationTableSupplier = () -> null;
+ if (hasNotifications) {
+ final PageService.PageActionBuilder actionBuilder = this.pageService
+ .pageActionBuilder(
+ pageContext
+ .clearAttributes()
+ .clearEntityKeys());
widgetFactory.addFormSubContextHeader(
content,
@@ -281,9 +272,28 @@ public class MonitoringClientConnection implements TemplateComposer {
NOTIFICATION_LIST_NO_SELECTION_KEY)
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
+
+ _notificationTableSupplier = () -> notificationTable;
}
+ final Supplier> notificationTableSupplier = _notificationTableSupplier;
+ // server push update
+ this.serverPushService.runServerPush(
+ new ServerPushContext(
+ content,
+ Utils.truePredicate(),
+ MonitoringRunningExam.createServerPushUpdateErrorHandler(this.pageService, pageContext)),
+ this.pollInterval,
+ context -> clientConnectionDetails.updateData(),
+ context -> clientConnectionDetails.updateGUI(notificationTableSupplier, pageContext));
+
// CLIENT EVENTS
+ final PageService.PageActionBuilder actionBuilder = this.pageService
+ .pageActionBuilder(
+ pageContext
+ .clearAttributes()
+ .clearEntityKeys());
+
widgetFactory.addFormSubContextHeader(
content,
EVENT_LIST_TITLE_KEY,
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java
index 5e2c4aeb..a606370c 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java
@@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.session;
import java.util.Collection;
import java.util.Map;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.graphics.Color;
@@ -23,8 +24,11 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
+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.ClientNotification;
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
+import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle;
@@ -32,8 +36,11 @@ import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
public class ClientConnectionDetails {
@@ -50,6 +57,7 @@ public class ClientConnectionDetails {
private static final int NUMBER_OF_NONE_INDICATOR_ROWS = 3;
+ private final PageService pageService;
private final ResourceService resourceService;
private final Map indicatorMapping;
private final RestCall.RestCallBuilder restCallBuilder;
@@ -69,6 +77,7 @@ public class ClientConnectionDetails {
final Display display = pageContext.getRoot().getDisplay();
+ this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.restCallBuilder = restCallBuilder;
this.colorData = new ColorData(display);
@@ -132,9 +141,13 @@ public class ClientConnectionDetails {
.toBoolean(connectionData.missingPing);
}
this.connectionData = connectionData;
+
}
- public void updateGUI() {
+ public void updateGUI(
+ final Supplier> notificationTableSupplier,
+ final PageContext pageContext) {
+
if (this.connectionData == null) {
return;
}
@@ -192,6 +205,29 @@ public class ClientConnectionDetails {
}
}
});
+
+ // update notifications
+ final EntityTable notificationTable = notificationTableSupplier.get();
+ if (notificationTable != null && this.connectionData.clientConnection.status == ConnectionStatus.CLOSED) {
+ reloadPage(pageContext);
+ } else {
+ if (BooleanUtils.isTrue(this.connectionData.pendingNotification())) {
+ if (notificationTable == null) {
+ reloadPage(pageContext);
+ } else {
+ notificationTable.refreshPageSize();
+ }
+ }
+ }
+ }
+
+ private void reloadPage(final PageContext pageContext) {
+ final PageAction pageReloadAction = this.pageService.pageActionBuilder(pageContext)
+ .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
+ .create();
+ this.pageService.firePageEvent(
+ new ActionEvent(pageReloadAction),
+ pageContext);
}
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java
index f008446d..ee3a6bba 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java
@@ -763,4 +763,19 @@ public class EntityTable {
}
}
+ public void refreshPageSize() {
+ if (this.pageSupplier.newBuilder()
+ .withPaging(this.pageNumber, this.pageSize)
+ .withSorting(this.sortColumn, this.sortOrder)
+ .withQueryParams((this.filter != null) ? this.filter.getFilterParameter() : null)
+ .withQueryParams(this.staticQueryParams)
+ .apply(this.pageSupplierAdapter)
+ .getPage()
+ .map(page -> page.content.size())
+ .map(size -> size != this.table.getItems().length)
+ .getOr(false)) {
+ reset();
+ }
+ }
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
index d69401ce..76cbb47c 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
@@ -761,7 +761,7 @@ public class ExamDAOImpl implements ExamDAO {
entry.getValue(),
cached)
.onError(error -> log.error(
- "Failed to get quizzes form LMS Setup: {}",
+ "Failed to get quizzes from LMS Setup: {}",
entry.getKey(), error))
.getOr(Collections.emptyList())
.stream())
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java
index 13ffe912..16bd23ba 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java
@@ -65,7 +65,7 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
"2020-01-01T09:00:00Z", null, "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2 (MOCKUP)", "Demo Quiz Mockup",
- "2020-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
+ "2020-01-01T09:00:00Z", "2022-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz3", institutionId, lmsSetupId, lmsType, "Demo Quiz 3 (MOCKUP)", "Demo Quiz Mockup",
"2018-07-30T09:00:00Z", "2018-08-01T00:00:00Z", "http://lms.mockup.com/api/"));
@@ -74,13 +74,13 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
"2018-01-01T00:00:00Z", "2019-01-01T00:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz5", institutionId, lmsSetupId, lmsType, "Demo Quiz 5 (MOCKUP)", "Demo Quiz Mockup",
- "2018-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
+ "2018-01-01T09:00:00Z", "2022-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz6", institutionId, lmsSetupId, lmsType, "Demo Quiz 6 (MOCKUP)", "Demo Quiz Mockup",
- "2019-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
+ "2019-01-01T09:00:00Z", "2022-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz7", institutionId, lmsSetupId, lmsType, "Demo Quiz 7 (MOCKUP)", "Demo Quiz Mockup",
- "2018-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
+ "2018-01-01T09:00:00Z", "2022-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz10", institutionId, lmsSetupId, lmsType, "Demo Quiz 10 (MOCKUP)",
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
index d47ffb77..d2641b83 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
@@ -217,7 +217,7 @@ public class MoodleCourseAccess extends CourseAccess {
}
} else if (this.moodleCourseDataLazyLoader.isLongRunningTask()) {
// on long running tasks if we have a different fromCutTime as before
- // kick off the lazy loadung task imeditially with the new time filter
+ // kick off the lazy loading task immediately with the new time filter
if (fromCutTime > 0 && fromCutTime != this.moodleCourseDataLazyLoader.getFromCutTime()) {
this.moodleCourseDataLazyLoader.setFromCutTime(fromCutTime);
this.moodleCourseDataLazyLoader.loadAsync(restTemplate);
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientNotificationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientNotificationService.java
index 5bfacd70..a25bb140 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientNotificationService.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientNotificationService.java
@@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session;
import java.util.List;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
import ch.ethz.seb.sebserver.gbl.util.Result;
@@ -27,6 +28,8 @@ public interface SEBClientNotificationService {
Result> getPendingNotifications(Long clientConnectionId);
+ void confirmPendingNotification(ClientEvent event, String connectionToken);
+
Result confirmPendingNotification(
Long notificationId,
Long examId,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InternalClientConnectionDataFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InternalClientConnectionDataFactory.java
index 38479cb4..c93bc823 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InternalClientConnectionDataFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InternalClientConnectionDataFactory.java
@@ -12,6 +12,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
@@ -32,6 +33,17 @@ public class InternalClientConnectionDataFactory {
}
public ClientConnectionDataInternal createClientConnectionData(final ClientConnection clientConnection) {
+
+ if (clientConnection.status == ConnectionStatus.CLOSED
+ || clientConnection.status == ConnectionStatus.DISABLED) {
+
+ // dispose notification indication for closed or disabled connection
+ return new ClientConnectionDataInternal(
+ clientConnection,
+ () -> false,
+ this.clientIndicatorFactory.createFor(clientConnection));
+ }
+
return new ClientConnectionDataInternal(
clientConnection,
() -> this.sebClientNotificationService
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
index 72e717e3..3c525551 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
@@ -28,7 +28,6 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
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.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@@ -40,8 +39,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrate
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.PingHandlingStrategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException;
@Lazy
@@ -524,14 +523,22 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
event,
activeClientConnection.getConnectionId()));
- if (event.eventType == EventType.NOTIFICATION || event.eventType == EventType.NOTIFICATION_CONFIRMED) {
- // notify notification service
- this.sebClientNotificationService.notifyNewNotification(activeClientConnection.getConnectionId());
- } else {
- // update indicators
- activeClientConnection.getIndicatorMapping(event.eventType)
- .forEach(indicator -> indicator.notifyValueChange(event));
+ switch (event.eventType) {
+ case NOTIFICATION: {
+ this.sebClientNotificationService.notifyNewNotification(activeClientConnection.getConnectionId());
+ break;
+ }
+ case NOTIFICATION_CONFIRMED: {
+ this.sebClientNotificationService.confirmPendingNotification(event, connectionToken);
+ break;
+ }
+ default: {
+ // update indicators
+ activeClientConnection.getIndicatorMapping(event.eventType)
+ .forEach(indicator -> indicator.notifyValueChange(event));
+ }
}
+
} else {
log.warn("No active ClientConnection found for connectionToken: {}", connectionToken);
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java
index 6f612223..19254f92 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java
@@ -15,9 +15,12 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
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;
@@ -32,6 +35,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificati
@WebServiceProfile
public class SEBClientNotificationServiceImpl implements SEBClientNotificationService {
+ private static final Logger log = LoggerFactory.getLogger(SEBClientNotificationServiceImpl.class);
+
private static final String CONFIRM_INSTRUCTION_ATTR_ID = "id";
private static final String CONFIRM_INSTRUCTION_ATTR_TYPE = "type";
@@ -69,6 +74,24 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe
return this.clientEventDAO.getPendingNotifications(clientConnectionId);
}
+ @Override
+ public void confirmPendingNotification(final ClientEvent event, final String connectionToken) {
+ try {
+ final Long notificationId = (long) event.getValue();
+
+ this.clientEventDAO.getPendingNotification(notificationId)
+ .flatMap(notification -> this.clientEventDAO.confirmPendingNotification(
+ notificationId,
+ notification.connectionId))
+ .map(this::removeFromCache);
+
+ } catch (final Exception e) {
+ log.error(
+ "Failed to confirm pending notification from SEB Client side. Connection token: {} confirm event: {}",
+ connectionToken, event, e);
+ }
+ }
+
@Override
public Result confirmPendingNotification(
final Long notificationId,
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index 2497d733..536b6e35 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -1554,13 +1554,13 @@ sebserver.monitoring.exam.connection.eventlist.text=Text
sebserver.monitoring.exam.connection.eventlist.text.tooltip=The text of the log event
{0}
sebserver.monitoring.exam.connection.event.type.UNKNOWN=Unknown
-sebserver.monitoring.exam.connection.event.type.DEBUG_LOG=Debug
-sebserver.monitoring.exam.connection.event.type.INFO_LOG=Info
-sebserver.monitoring.exam.connection.event.type.WARN_LOG=Warn
-sebserver.monitoring.exam.connection.event.type.ERROR_LOG=Error
+sebserver.monitoring.exam.connection.event.type.DEBUG_LOG=Debug Log
+sebserver.monitoring.exam.connection.event.type.INFO_LOG=Info Log
+sebserver.monitoring.exam.connection.event.type.WARN_LOG=Warn Log
+sebserver.monitoring.exam.connection.event.type.ERROR_LOG=Error Log
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.event.type.NOTIFICATION_CONFIRMED=Notification (confirmed)
sebserver.monitoring.exam.connection.notification.type.UNKNOWN=Unknown
sebserver.monitoring.exam.connection.notification.type.LOCK_SCREEN=Lock Screen