diff --git a/pom.xml b/pom.xml
index d2b02b27..99b11afc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
jar
- 1.4-rc1
+ 1.4.0-SNAPSHOT
${sebserver-version}
${sebserver-version}
UTF-8
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/RunningExamInfo.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/RunningExamInfo.java
index 4d795bf0..d12db3c1 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/RunningExamInfo.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/RunningExamInfo.java
@@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gbl.model.session;
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.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
@@ -47,7 +48,7 @@ public final class RunningExamInfo {
this.examId = exam.getModelId();
this.name = exam.name;
this.url = exam.getStartURL();
- this.lmsType = (lmsType == null) ? "" : lmsType.name();
+ this.lmsType = (lmsType == null) ? Constants.EMPTY_NOTE : lmsType.name();
}
public String getExamId() {
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateForm.java
index 263d90f3..df2c10a7 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateForm.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/ConfigTemplateForm.java
@@ -340,7 +340,7 @@ public class ConfigTemplateForm implements TemplateComposer {
.withEntityKey(entityKey)
.publishIf(() -> modifyGrant && isReadonly)
- .newAction(ActionDefinition.SEB_EXAM_CONFIG_DELETE)
+ .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_DELETE)
.withEntityKey(entityKey)
.withConfirm(() -> CONFIRM_DELETE)
.withExec(this::deleteConfiguration)
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 80b880ed..665e7b91 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
@@ -648,6 +648,9 @@ public class ExamDAOImpl implements ExamDAO {
.and(
ExamRecordDynamicSqlSupport.status,
isEqualTo(ExamStatus.RUNNING.name()))
+ .and(
+ ExamRecordDynamicSqlSupport.lmsAvailable,
+ isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
.build()
.execute());
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java
index 27d98a7a..38679835 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java
@@ -77,7 +77,7 @@ public interface ExamSessionService {
/** Use this to check if a specified Exam has currently active SEB Client connections.
*
* Active SEB Client connections are established connections that are not yet closed and
- * connection attempts that are older the a defined time interval.
+ * open connection attempts.
*
* @param examId The Exam identifier
* @return true if the given Exam has currently no active client connection, false otherwise. */
@@ -86,7 +86,7 @@ public interface ExamSessionService {
return false;
}
- return !this.getActiveConnectionTokens(examId)
+ return !this.getAllActiveConnectionTokens(examId)
.getOrThrow()
.isEmpty();
}
@@ -184,13 +184,21 @@ public interface ExamSessionService {
final Long examId,
final Predicate filter);
- /** Gets all connection tokens of active client connection that are related to a specified exam
+ /** Gets all connection tokens of client connection that are in ACTIVE state and related to a specified exam
* from persistence storage without caching involved.
*
* @param examId the exam identifier
* @return Result refer to the collection of connection tokens or to an error when happened. */
Result> getActiveConnectionTokens(Long examId);
+ /** Gets all connection tokens of client connections that are in an active state. See ClientConnection
+ * And that are related to a specified exam.
+ * There is no caching involved here, gets actual data from persistent storage
+ *
+ * @param examId the exam identifier
+ * @return Result refer to the collection of connection tokens or to an error when happened. */
+ Result> getAllActiveConnectionTokens(Long examId);
+
/** Use this to check if the current cached running exam is up to date
* and if not to flush the cache.
*
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
index b413c5c9..9144daeb 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
@@ -430,6 +430,12 @@ public class ExamSessionServiceImpl implements ExamSessionService {
.getActiveConnectionTokens(examId);
}
+ @Override
+ public Result> getAllActiveConnectionTokens(final Long examId) {
+ return this.clientConnectionDAO
+ .getAllActiveConnectionTokens(examId);
+ }
+
@EventListener
public void notifyExamFinished(final ExamFinishedEvent event) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java
index a6a042b1..8e6aa53f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java
@@ -137,6 +137,7 @@ public class ExamAPI_V1_Controller {
.getOrThrow()
.stream()
.map(this::createRunningExamInfo)
+ .filter(this::checkConsistency)
.collect(Collectors.toList());
} else {
final Exam exam = this.examSessionService.getExamDAO()
@@ -158,6 +159,18 @@ public class ExamAPI_V1_Controller {
this.executor);
}
+ private boolean checkConsistency(final RunningExamInfo info) {
+ if (StringUtils.isNotBlank(info.name) &&
+ StringUtils.isNotBlank(info.url) &&
+ StringUtils.isNotBlank(info.examId)) {
+
+ return true;
+ }
+
+ log.warn("Invalid running exam detected. Filter out exam : {}", info);
+ return false;
+ }
+
@RequestMapping(
path = API.EXAM_API_HANDSHAKE_ENDPOINT,
method = RequestMethod.PATCH,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java
index b910eb7c..32bdfac6 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java
@@ -568,7 +568,6 @@ public class ExamAdministrationController extends EntityController {
.of("Exam currently has active SEB Client connections."));
}
- // TODO double check before setSEBRestriction
return this.checkNoActiveSEBClientConnections(exam)
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict))
diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java
index fa66a89c..9188427f 100644
--- a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java
+++ b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java
@@ -95,6 +95,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
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.ClientInstruction;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
@@ -254,35 +255,6 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
// Nothing
}
-// @Test
-// @Order(0)
-// public void testUsecase00_cleanupAllExams() {
-// final RestServiceImpl restService = createRestServiceForUser(
-// "admin",
-// "admin",
-// new GetExamNames(),
-// new DeleteExam());
-//
-// final Result> call = restService
-// .getBuilder(GetExamNames.class)
-// .call();
-//
-// if (!call.hasError()) {
-// call.get().stream().forEach(key -> {
-// final Result deleted = restService
-// .getBuilder(DeleteExam.class)
-// .withURIVariable(API.PARAM_MODEL_ID, key.modelId)
-// .call();
-//
-// if (deleted.hasError()) {
-// System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%%% deletion failed: " + key);
-// } else {
-// System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%%% deleted: " + key);
-// }
-// });
-// }
-// }
-
@Test
@Order(1)
// *************************************
@@ -815,6 +787,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
// - Check if there are some quizzes from previous LMS Setup
// - Import a quiz as Exam
// - get exam page and check the exam is there
+ // - get exam page with none native sort attribute to test this
// - edit exam property and save again
public void testUsecase07_ImportExam() {
final RestServiceImpl restService = createRestServiceForUser(
@@ -922,6 +895,13 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
.filter(exam -> exam.name.equals(newExam.name))
.findFirst().isPresent());
+ final Result> examsSorted = restService
+ .getBuilder(GetExamPage.class)
+ .withQueryParam(Page.ATTR_SORT, LmsSetup.FILTER_ATTR_LMS_SETUP)
+ .call();
+
+ assertNotNull(examsSorted);
+ assertFalse(examsSorted.hasError());
}
@Test
@@ -2169,7 +2149,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
assertTrue(connections.isEmpty());
// get MonitoringFullPageData
- final Result fullPageData = restService.getBuilder(GetMonitoringFullPageData.class)
+ Result fullPageData = restService.getBuilder(GetMonitoringFullPageData.class)
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
.call();
assertNotNull(fullPageData);
@@ -2221,6 +2201,22 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
iterator.next();
final ClientConnectionData con = iterator.next();
+ fullPageData = restService.getBuilder(GetMonitoringFullPageData.class)
+ .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
+ .withHeader(API.EXAM_MONITORING_STATE_FILTER, ConnectionStatus.DISABLED.name())
+ .call();
+ assertNotNull(fullPageData);
+ assertFalse(fullPageData.hasError());
+
+ fullPageData = restService.getBuilder(GetMonitoringFullPageData.class)
+ .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
+ .withHeader(
+ API.EXAM_MONITORING_STATE_FILTER,
+ ConnectionStatus.DISABLED.name() + "," + ConnectionStatus.ACTIVE.name())
+ .call();
+ assertNotNull(fullPageData);
+ assertFalse(fullPageData.hasError());
+
// get single client connection
final Result ccCall = restService.getBuilder(GetClientConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, con.clientConnection.getModelId())
diff --git a/src/test/resources/data-test-additional.sql b/src/test/resources/data-test-additional.sql
index a1a7ae5d..377469b1 100644
--- a/src/test/resources/data-test-additional.sql
+++ b/src/test/resources/data-test-additional.sql
@@ -9,13 +9,14 @@ INSERT IGNORE INTO seb_client_configuration VALUES
INSERT IGNORE INTO additional_attributes VALUES
(1, 'SEB_CLIENT_CONFIGURATION', 2, 'vdiSetup', 'VM_WARE'),
- (2, 'SEB_CLIENT_CONFIGURATION', 2, 'vdiExecutable', 'vmware-view.exe')
+ (2, 'SEB_CLIENT_CONFIGURATION', 2, 'vdiExecutable', 'vmware-view.exe'),
+ (3, 'EXAM', 2, 'quiz_start_url', 'https://test.lms.mockup')
;
INSERT IGNORE INTO exam VALUES
- (1, 1, 1, 'quiz1', 'admin', 'admin', 'MANAGED', null, null, 'UP_COMING', 1, 0, null, 1, null, null, null, null, null, null),
- (2, 1, 1, 'quiz6', 'admin', 'admin', 'MANAGED', null, null, 'RUNNING', 1, 0, null, 1, null, null, null, null, null, null),
- (3, 1, 1, 'quiz3', 'admin', 'admin', 'MANAGED', null, null, 'FINISHED', 1, 0, null, 1, null, null, null, null, null, null)
+ (1, 1, 1, 'quiz1', 'admin', 'admin', 'MANAGED', null, null, 'UP_COMING', 1, 0, null, 1, null, null, 'quiz1', null, null, 1),
+ (2, 1, 1, 'quiz6', 'admin', 'admin', 'MANAGED', null, null, 'RUNNING', 1, 0, null, 1, null, null, 'quiz6', null, null, 1),
+ (3, 1, 1, 'quiz3', 'admin', 'admin', 'MANAGED', null, null, 'FINISHED', 1, 0, null, 1, null, null, 'quiz3', null, null, 1)
;
INSERT IGNORE INTO indicator VALUES