diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEventDetailsPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEventDetailsPopup.java index 4deac17c..02c7857c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEventDetailsPopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEventDetailsPopup.java @@ -215,7 +215,9 @@ public class SEBClientEventDetailsPopup { .addField(FormBuilder.text( QuizData.QUIZ_ATTR_DESCRIPTION, FORM_DESC_TEXT_KEY, - exam.description)) + exam.description) + .asArea() + .asHTML(true)) .addField(FormBuilder.text( Domain.EXAM.ATTR_TYPE, FORM_EXAM_TYPE_TEXT_KEY, 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 16bd23ba..fc9342e5 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 @@ -61,25 +61,25 @@ final class MockupLmsAPITemplate implements LmsAPITemplate { final LmsType lmsType = lmsSetup.getLmsType(); this.mockups = new ArrayList<>(); this.mockups.add(new QuizData( - "quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1 (MOCKUP)", "Demo Quiz Mockup", + "quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1 (MOCKUP)", "

Demo Quiz Mockup

", "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", + "quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2 (MOCKUP)", "

Demo Quiz Mockup

", "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", + "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/")); this.mockups.add(new QuizData( - "quiz4", institutionId, lmsSetupId, lmsType, "Demo Quiz 4 (MOCKUP)", "Demo Quiz Mockup", + "quiz4", institutionId, lmsSetupId, lmsType, "Demo Quiz 4 (MOCKUP)", "

Demo Quiz Mockup

", "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", + "quiz5", institutionId, lmsSetupId, lmsType, "Demo Quiz 5 (MOCKUP)", "

Demo Quiz Mockup

", "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", + "quiz6", institutionId, lmsSetupId, lmsType, "Demo Quiz 6 (MOCKUP)", "

Demo Quiz Mockup

", "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", + "quiz7", institutionId, lmsSetupId, lmsType, "Demo Quiz 7 (MOCKUP)", "

Demo Quiz Mockup

", "2018-01-01T09:00:00Z", "2022-01-01T09:00:00Z", "http://lms.mockup.com/api/")); this.mockups.add(new QuizData( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java index b45ec87c..3b651926 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ClientIndicator.java @@ -14,6 +14,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; 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.IndicatorValue; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; /** A client indicator is a indicator value holder for a specific Indicator * on a running client connection. @@ -58,4 +59,6 @@ public interface ClientIndicator extends IndicatorValue { * @param event The ClientEvent instance */ void notifyValueChange(ClientEvent event); + void notifyValueChange(ClientEventRecord clientEventRecord); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java index 0efb485a..48992209 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java @@ -114,7 +114,7 @@ class ExamSessionControlTask implements DisposableBean { } @Scheduled(fixedRateString = "${sebserver.webservice.api.seb.lostping.update:5000}") - public void pingEventUpdateTask() { + public void examSessionUpdateTask() { if (!this.webserviceInfoDAO.isMaster(this.webserviceInfo.getWebserviceUUID())) { return; 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 6c03dabf..4b7646a7 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 @@ -31,6 +31,7 @@ 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.webservice.WebserviceInfo; @@ -524,34 +525,39 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic final String connectionToken, final ClientEvent event) { - final ClientConnectionDataInternal activeClientConnection = - this.examSessionCacheService.getClientConnection(connectionToken); + try { + final ClientConnectionDataInternal activeClientConnection = + this.examSessionCacheService.getClientConnection(connectionToken); - if (activeClientConnection != null) { + if (activeClientConnection != null) { - // store event - this.eventHandlingStrategy.accept(ClientEvent.toRecord( - event, - activeClientConnection.getConnectionId())); + // store event + this.eventHandlingStrategy.accept(ClientEvent.toRecord( + event, + activeClientConnection.getConnectionId())); - 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)); + 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); } - - } else { - log.warn("No active ClientConnection found for connectionToken: {}", connectionToken); + } catch (final Exception e) { + log.error("Failed to process SEB client event: ", e); } } @@ -734,6 +740,12 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic this.clientConnectionDAO.save(connection.clientConnection); this.examSessionCacheService.evictClientConnection( connection.clientConnection.connectionToken); + } else { + // update indicators + if (clientEventRecord.getType() != null && EventType.ERROR_LOG.id == clientEventRecord.getType()) { + connection.getIndicatorMapping(EventType.ERROR_LOG) + .forEach(indicator -> indicator.notifyValueChange(clientEventRecord)); + } } } }; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java index 2183d7ff..3e25ff91 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java @@ -20,6 +20,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; +import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicator { @@ -37,9 +38,18 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato @Override public void notifyValueChange(final ClientEvent event) { + valueChanged(event.text); + } + + @Override + public void notifyValueChange(final ClientEventRecord clientEventRecord) { + valueChanged(clientEventRecord.getText()); + } + + private void valueChanged(final String eventText) { if (this.tags == null || this.tags.length == 0) { this.currentValue = getValue() + 1d; - } else if (hasTag(event.text)) { + } else if (hasTag(eventText)) { this.currentValue = getValue() + 1d; } } @@ -51,7 +61,7 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato // TODO to boost performance here within a distributed setup, invent a new cache for all log count values // of the running exam. So all indicators get the values from cache and only one single SQL call // is needed for one update. - // This cache then is only valid for one (GUI) update cycle and the cache must to be flushed before + // This cache then is only valid for one (GUI) update cycle and the cache must to be flushed before final Long errors = this.clientEventRecordMapper.countByExample() .where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(this.connectionId)) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java index 6d3a89cd..2fd74d62 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java @@ -41,10 +41,22 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { @Override public void notifyValueChange(final ClientEvent event) { + valueChanged(event.text, event.getValue()); + } + + @Override + public void notifyValueChange(final ClientEventRecord clientEventRecord) { + final BigDecimal numericValue = clientEventRecord.getNumericValue(); + if (numericValue != null) { + valueChanged(clientEventRecord.getText(), numericValue.doubleValue()); + } + } + + private void valueChanged(final String text, final double value) { if (this.tags == null || this.tags.length == 0) { - this.currentValue = event.getValue(); - } else if (hasTag(event.text)) { - this.currentValue = event.getValue(); + this.currentValue = value; + } else if (hasTag(text)) { + this.currentValue = value; } } @@ -55,7 +67,7 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator { // TODO to boost performance here within a distributed setup, invent a new cache for all log count values // of the running exam. So all indicators get the values from cache and only one single SQL call // is needed for one update. - // This cache then is only valid for one (GUI) update cycle and the cache must to be flushed before + // This cache then is only valid for one (GUI) update cycle and the cache must to be flushed before final List execute = this.clientEventRecordMapper.selectByExample() .where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(this.connectionId)) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java index 9f32d668..cb0f07a4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java @@ -109,6 +109,11 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator { } + @Override + public void notifyValueChange(final ClientEventRecord clientEventRecord) { + + } + @Override public ClientEventRecord updateLogEvent(final long now) { final long value = now - (long) super.currentValue; diff --git a/src/main/resources/config/application-dev-gui.properties b/src/main/resources/config/application-dev-gui.properties index eb077efa..4aaab9d3 100644 --- a/src/main/resources/config/application-dev-gui.properties +++ b/src/main/resources/config/application-dev-gui.properties @@ -1,11 +1,11 @@ server.address=localhost -server.port=8081 +server.port=8080 sebserver.gui.http.external.scheme=http sebserver.gui.entrypoint=/gui sebserver.gui.webservice.protocol=http sebserver.gui.webservice.address=localhost -sebserver.gui.webservice.port=8081 +sebserver.gui.webservice.port=8080 sebserver.gui.webservice.apipath=/admin-api/v1 # defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page sebserver.gui.webservice.poll-interval=1000