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