SEBSERV-147: finished todos

This commit is contained in:
anhefti 2021-01-06 15:55:11 +01:00
parent 69f8d6cd4a
commit 433aad87df
12 changed files with 157 additions and 39 deletions

12
pom.xml
View file

@ -25,6 +25,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<!-- NOTE: There currently are two profiles, a default one to build on
Java 11 (from eclipse and command-line) and one to build still on Java 8
to support the Jenkins build on CI-Server that still no Java 11 installed -->
@ -45,6 +46,17 @@
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>${java.version}</release>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
<systemPropertyVariables>
<file.encoding>UTF-8</file.encoding>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>

View file

@ -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<EntityTable<ClientNotification>> _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<EntityTable<ClientNotification>> 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,

View file

@ -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<Long, IndicatorData> indicatorMapping;
private final RestCall<ClientConnectionData>.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<EntityTable<ClientNotification>> notificationTableSupplier,
final PageContext pageContext) {
if (this.connectionData == null) {
return;
}
@ -192,6 +205,29 @@ public class ClientConnectionDetails {
}
}
});
// update notifications
final EntityTable<ClientNotification> 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);
}
}

View file

@ -763,4 +763,19 @@ public class EntityTable<ROW> {
}
}
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();
}
}
}

View file

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

View file

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

View file

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

View file

@ -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<List<ClientNotification>> getPendingNotifications(Long clientConnectionId);
void confirmPendingNotification(ClientEvent event, String connectionToken);
Result<ClientNotification> confirmPendingNotification(
Long notificationId,
Long examId,

View file

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

View file

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

View file

@ -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<ClientNotification> confirmPendingNotification(
final Long notificationId,

View file

@ -1554,13 +1554,13 @@ sebserver.monitoring.exam.connection.eventlist.text=Text
sebserver.monitoring.exam.connection.eventlist.text.tooltip=The text of the log event<br/><br/>{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