Merge remote-tracking branch 'origin/dev-1.4' into development

This commit is contained in:
anhefti 2022-07-04 16:04:15 +02:00
commit 40689d5781
10 changed files with 68 additions and 41 deletions

View file

@ -18,7 +18,7 @@
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
<sebserver-version>1.4-rc1</sebserver-version> <sebserver-version>1.4.0-SNAPSHOT</sebserver-version>
<build-version>${sebserver-version}</build-version> <build-version>${sebserver-version}</build-version>
<revision>${sebserver-version}</revision> <revision>${sebserver-version}</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gbl.model.session;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; 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.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
@ -47,7 +48,7 @@ public final class RunningExamInfo {
this.examId = exam.getModelId(); this.examId = exam.getModelId();
this.name = exam.name; this.name = exam.name;
this.url = exam.getStartURL(); this.url = exam.getStartURL();
this.lmsType = (lmsType == null) ? "" : lmsType.name(); this.lmsType = (lmsType == null) ? Constants.EMPTY_NOTE : lmsType.name();
} }
public String getExamId() { public String getExamId() {

View file

@ -340,7 +340,7 @@ public class ConfigTemplateForm implements TemplateComposer {
.withEntityKey(entityKey) .withEntityKey(entityKey)
.publishIf(() -> modifyGrant && isReadonly) .publishIf(() -> modifyGrant && isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_DELETE) .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_DELETE)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withConfirm(() -> CONFIRM_DELETE) .withConfirm(() -> CONFIRM_DELETE)
.withExec(this::deleteConfiguration) .withExec(this::deleteConfiguration)

View file

@ -648,6 +648,9 @@ public class ExamDAOImpl implements ExamDAO {
.and( .and(
ExamRecordDynamicSqlSupport.status, ExamRecordDynamicSqlSupport.status,
isEqualTo(ExamStatus.RUNNING.name())) isEqualTo(ExamStatus.RUNNING.name()))
.and(
ExamRecordDynamicSqlSupport.lmsAvailable,
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
.build() .build()
.execute()); .execute());
} }

View file

@ -77,7 +77,7 @@ public interface ExamSessionService {
/** Use this to check if a specified Exam has currently active SEB Client connections. /** 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 * 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 * @param examId The Exam identifier
* @return true if the given Exam has currently no active client connection, false otherwise. */ * @return true if the given Exam has currently no active client connection, false otherwise. */
@ -86,7 +86,7 @@ public interface ExamSessionService {
return false; return false;
} }
return !this.getActiveConnectionTokens(examId) return !this.getAllActiveConnectionTokens(examId)
.getOrThrow() .getOrThrow()
.isEmpty(); .isEmpty();
} }
@ -184,13 +184,21 @@ public interface ExamSessionService {
final Long examId, final Long examId,
final Predicate<ClientConnectionData> filter); final Predicate<ClientConnectionData> 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. * from persistence storage without caching involved.
* *
* @param examId the exam identifier * @param examId the exam identifier
* @return Result refer to the collection of connection tokens or to an error when happened. */ * @return Result refer to the collection of connection tokens or to an error when happened. */
Result<Collection<String>> getActiveConnectionTokens(Long examId); Result<Collection<String>> getActiveConnectionTokens(Long examId);
/** Gets all connection tokens of client connections that are in an active state. See <code>ClientConnection</code>
* 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<Collection<String>> getAllActiveConnectionTokens(Long examId);
/** Use this to check if the current cached running exam is up to date /** Use this to check if the current cached running exam is up to date
* and if not to flush the cache. * and if not to flush the cache.
* *

View file

@ -430,6 +430,12 @@ public class ExamSessionServiceImpl implements ExamSessionService {
.getActiveConnectionTokens(examId); .getActiveConnectionTokens(examId);
} }
@Override
public Result<Collection<String>> getAllActiveConnectionTokens(final Long examId) {
return this.clientConnectionDAO
.getAllActiveConnectionTokens(examId);
}
@EventListener @EventListener
public void notifyExamFinished(final ExamFinishedEvent event) { public void notifyExamFinished(final ExamFinishedEvent event) {

View file

@ -137,6 +137,7 @@ public class ExamAPI_V1_Controller {
.getOrThrow() .getOrThrow()
.stream() .stream()
.map(this::createRunningExamInfo) .map(this::createRunningExamInfo)
.filter(this::checkConsistency)
.collect(Collectors.toList()); .collect(Collectors.toList());
} else { } else {
final Exam exam = this.examSessionService.getExamDAO() final Exam exam = this.examSessionService.getExamDAO()
@ -158,6 +159,18 @@ public class ExamAPI_V1_Controller {
this.executor); 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( @RequestMapping(
path = API.EXAM_API_HANDSHAKE_ENDPOINT, path = API.EXAM_API_HANDSHAKE_ENDPOINT,
method = RequestMethod.PATCH, method = RequestMethod.PATCH,

View file

@ -568,7 +568,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
.of("Exam currently has active SEB Client connections.")); .of("Exam currently has active SEB Client connections."));
} }
// TODO double check before setSEBRestriction
return this.checkNoActiveSEBClientConnections(exam) return this.checkNoActiveSEBClientConnections(exam)
.flatMap(this.sebRestrictionService::applySEBClientRestriction) .flatMap(this.sebRestrictionService::applySEBClientRestriction)
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict)) .flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict))

View file

@ -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.TemplateAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View; 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;
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.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; 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.ClientInstruction.InstructionType;
@ -254,35 +255,6 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
// Nothing // Nothing
} }
// @Test
// @Order(0)
// public void testUsecase00_cleanupAllExams() {
// final RestServiceImpl restService = createRestServiceForUser(
// "admin",
// "admin",
// new GetExamNames(),
// new DeleteExam());
//
// final Result<List<EntityName>> call = restService
// .getBuilder(GetExamNames.class)
// .call();
//
// if (!call.hasError()) {
// call.get().stream().forEach(key -> {
// final Result<EntityProcessingReport> 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 @Test
@Order(1) @Order(1)
// ************************************* // *************************************
@ -815,6 +787,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
// - Check if there are some quizzes from previous LMS Setup // - Check if there are some quizzes from previous LMS Setup
// - Import a quiz as Exam // - Import a quiz as Exam
// - get exam page and check the exam is there // - 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 // - edit exam property and save again
public void testUsecase07_ImportExam() { public void testUsecase07_ImportExam() {
final RestServiceImpl restService = createRestServiceForUser( final RestServiceImpl restService = createRestServiceForUser(
@ -922,6 +895,13 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
.filter(exam -> exam.name.equals(newExam.name)) .filter(exam -> exam.name.equals(newExam.name))
.findFirst().isPresent()); .findFirst().isPresent());
final Result<Page<Exam>> examsSorted = restService
.getBuilder(GetExamPage.class)
.withQueryParam(Page.ATTR_SORT, LmsSetup.FILTER_ATTR_LMS_SETUP)
.call();
assertNotNull(examsSorted);
assertFalse(examsSorted.hasError());
} }
@Test @Test
@ -2169,7 +2149,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
assertTrue(connections.isEmpty()); assertTrue(connections.isEmpty());
// get MonitoringFullPageData // get MonitoringFullPageData
final Result<MonitoringFullPageData> fullPageData = restService.getBuilder(GetMonitoringFullPageData.class) Result<MonitoringFullPageData> fullPageData = restService.getBuilder(GetMonitoringFullPageData.class)
.withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId()) .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId())
.call(); .call();
assertNotNull(fullPageData); assertNotNull(fullPageData);
@ -2221,6 +2201,22 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
iterator.next(); iterator.next();
final ClientConnectionData con = 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 // get single client connection
final Result<ClientConnection> ccCall = restService.getBuilder(GetClientConnection.class) final Result<ClientConnection> ccCall = restService.getBuilder(GetClientConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, con.clientConnection.getModelId()) .withURIVariable(API.PARAM_MODEL_ID, con.clientConnection.getModelId())

View file

@ -9,13 +9,14 @@ INSERT IGNORE INTO seb_client_configuration VALUES
INSERT IGNORE INTO additional_attributes VALUES INSERT IGNORE INTO additional_attributes VALUES
(1, 'SEB_CLIENT_CONFIGURATION', 2, 'vdiSetup', 'VM_WARE'), (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 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), (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, null, null, null, null), (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, null, null, null, null) (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 INSERT IGNORE INTO indicator VALUES