SEBSERV-419 implementation
This commit is contained in:
parent
8539da1879
commit
41ce1bc268
15 changed files with 207 additions and 82 deletions
|
@ -255,7 +255,7 @@ public final class API {
|
|||
public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states";
|
||||
public static final String EXAM_MONITORING_CLIENT_GROUP_FILTER = "hidden-client-group";
|
||||
public static final String EXAM_MONITORING_ISSUE_FILTER = "hidden-issues";
|
||||
|
||||
public static final String EXAM_MONITORING_TEST_RUN_ENDPOINT = "/testrun";
|
||||
public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams";
|
||||
public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT =
|
||||
"/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}";
|
||||
|
|
|
@ -8,12 +8,7 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gbl.model.exam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
|
@ -68,6 +63,7 @@ public final class Exam implements GrantEntity {
|
|||
|
||||
public enum ExamStatus {
|
||||
UP_COMING,
|
||||
TEST_RUN,
|
||||
RUNNING,
|
||||
FINISHED,
|
||||
ARCHIVED
|
||||
|
@ -80,6 +76,16 @@ public final class Exam implements GrantEntity {
|
|||
VDI
|
||||
}
|
||||
|
||||
public static final EnumSet<ExamStatus> ACTIVE_STATES = EnumSet.of(
|
||||
ExamStatus.UP_COMING,
|
||||
ExamStatus.TEST_RUN,
|
||||
ExamStatus.RUNNING);
|
||||
|
||||
public static final List<String> ACTIVE_STATE_NAMES = Arrays.asList(
|
||||
ExamStatus.UP_COMING.name(),
|
||||
ExamStatus.TEST_RUN.name(),
|
||||
ExamStatus.RUNNING.name());
|
||||
|
||||
@JsonProperty(EXAM.ATTR_ID)
|
||||
public final Long id;
|
||||
|
||||
|
|
|
@ -298,6 +298,16 @@ public enum ActionDefinition {
|
|||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.EXAM_VIEW,
|
||||
ActionCategory.FORM),
|
||||
EXAM_TOGGLE_TEST_RUN_ON(
|
||||
new LocTextKey("sebserver.exam.action.test.run.on"),
|
||||
ImageIcon.ARCHIVE,
|
||||
PageStateDefinitionImpl.EXAM_VIEW,
|
||||
ActionCategory.FORM),
|
||||
EXAM_TOGGLE_TEST_RUN_OFF(
|
||||
new LocTextKey("sebserver.exam.action.test.run.off"),
|
||||
ImageIcon.ARCHIVE,
|
||||
PageStateDefinitionImpl.EXAM_VIEW,
|
||||
ActionCategory.FORM),
|
||||
EXAM_ARCHIVE(
|
||||
new LocTextKey("sebserver.exam.action.archive"),
|
||||
ImageIcon.ARCHIVE,
|
||||
|
|
|
@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
|||
import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.*;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ToggleTestRun;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
|
@ -44,8 +45,6 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
|||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult.ErrorType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
|
@ -68,7 +67,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.template.GetDefaultExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.template.GetExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
|
@ -195,6 +193,8 @@ public class ExamForm implements TemplateComposer {
|
|||
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow();
|
||||
|
||||
|
||||
|
||||
// new PageContext with actual EntityKey
|
||||
final EntityKey entityKey = (readonly || !newExamNoLMS) ? pageContext.getEntityKey() : null;
|
||||
final PageContext formContext = pageContext.withEntityKey(exam.getEntityKey());
|
||||
|
@ -202,7 +202,7 @@ public class ExamForm implements TemplateComposer {
|
|||
final boolean isLight = pageService.isLightSetup();
|
||||
final boolean modifyGrant = entityGrantCheck.m();
|
||||
final boolean writeGrant = entityGrantCheck.w();
|
||||
final boolean editable = modifyGrant && (exam.getStatus() == ExamStatus.UP_COMING || exam.getStatus() == ExamStatus.RUNNING);
|
||||
final boolean editable = modifyGrant && Exam.ACTIVE_STATES.contains(exam.getStatus());
|
||||
final boolean signatureKeyCheckEnabled = BooleanUtils.toBoolean(
|
||||
exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
|
||||
final boolean sebRestrictionAvailable = readonly && hasSEBRestrictionAPI(exam);
|
||||
|
@ -288,6 +288,16 @@ public class ExamForm implements TemplateComposer {
|
|||
.withExec(this.examDeletePopup.deleteWizardFunction(pageContext))
|
||||
.publishIf(() -> writeGrant && readonly)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_TOGGLE_TEST_RUN_ON)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this::toggleTestRun)
|
||||
.publishIf(() -> modifyGrant && readonly && exam.status == ExamStatus.UP_COMING)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_TOGGLE_TEST_RUN_OFF)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this::toggleTestRun)
|
||||
.publishIf(() -> modifyGrant && readonly && exam.status == ExamStatus.TEST_RUN)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_ARCHIVE)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> EXAM_ARCHIVE_CONFIRM)
|
||||
|
@ -399,6 +409,8 @@ public class ExamForm implements TemplateComposer {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private FormHandle<Exam> createReadOnlyForm(
|
||||
final PageContext formContext,
|
||||
final Composite content,
|
||||
|
@ -806,7 +818,8 @@ public class ExamForm implements TemplateComposer {
|
|||
if (pageService.isLightSetup()) {
|
||||
mapper.putIfAbsent(Domain.EXAM.ATTR_SUPPORTER, this.pageService.getCurrentUser().get().uuid);
|
||||
}
|
||||
return this.restService.getBuilder(GetQuizData.class)
|
||||
return this.restService
|
||||
.getBuilder(GetQuizData.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withQueryParam(QuizData.QUIZ_ATTR_LMS_SETUP_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
|
@ -833,4 +846,18 @@ public class ExamForm implements TemplateComposer {
|
|||
};
|
||||
}
|
||||
|
||||
private PageAction toggleTestRun(final PageAction pageAction) {
|
||||
|
||||
this.restService
|
||||
.getBuilder(ToggleTestRun.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, pageAction.getEntityKey().modelId)
|
||||
.call()
|
||||
.onError(error -> log.error(
|
||||
"Failed to toggle Test Run for exam: {}, error: {}",
|
||||
pageAction.getEntityKey(),
|
||||
error.getMessage()));
|
||||
|
||||
return pageAction;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class ToggleTestRun extends RestCall<Exam> {
|
||||
|
||||
public ToggleTestRun() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_SINGLE,
|
||||
EntityType.EXAM,
|
||||
new TypeReference<Exam>() {
|
||||
}),
|
||||
HttpMethod.POST,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_MONITORING_ENDPOINT
|
||||
+ API.EXAM_MONITORING_TEST_RUN_ENDPOINT
|
||||
+ API.MODEL_ID_VAR_PATH_SEGMENT);
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfig
|
|||
import static org.mybatis.dynamic.sql.SqlBuilder.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -37,7 +36,6 @@ import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
|||
import ch.ethz.seb.sebserver.gbl.model.EntityDependency;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
|
@ -67,10 +65,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
|||
@WebServiceProfile
|
||||
public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
||||
|
||||
private static final List<String> ACTIVE_EXAM_STATE_NAMES = Arrays.asList(
|
||||
ExamStatus.UP_COMING.name(),
|
||||
ExamStatus.RUNNING.name());
|
||||
|
||||
private final ExamRecordMapper examRecordMapper;
|
||||
private final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper;
|
||||
private final ConfigurationNodeRecordMapper configurationNodeRecordMapper;
|
||||
|
@ -440,7 +434,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
try {
|
||||
final boolean active = this.examRecordMapper.countByExample()
|
||||
.where(ExamRecordDynamicSqlSupport.id, isEqualTo(examId))
|
||||
.and(ExamRecordDynamicSqlSupport.status, isIn(ACTIVE_EXAM_STATE_NAMES))
|
||||
.and(ExamRecordDynamicSqlSupport.status, isIn(Exam.ACTIVE_STATE_NAMES))
|
||||
.build()
|
||||
.execute() >= 1;
|
||||
return active;
|
||||
|
|
|
@ -205,10 +205,18 @@ public class ExamRecordDAO {
|
|||
|
||||
final String examStatus = filterMap.getExamStatus();
|
||||
if (StringUtils.isNotBlank(examStatus)) {
|
||||
whereClause = whereClause
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.status,
|
||||
isEqualToWhenPresent(examStatus));
|
||||
if (examStatus.contains(Constants.LIST_SEPARATOR)) {
|
||||
final List<String> state_names = Arrays.asList(StringUtils.split(examStatus, Constants.LIST_SEPARATOR));
|
||||
whereClause = whereClause
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.status,
|
||||
isIn(state_names));
|
||||
} else {
|
||||
whereClause = whereClause
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.status,
|
||||
isEqualToWhenPresent(examStatus));
|
||||
}
|
||||
} else if (stateNames != null && !stateNames.isEmpty()) {
|
||||
whereClause = whereClause
|
||||
.and(
|
||||
|
@ -234,14 +242,12 @@ public class ExamRecordDAO {
|
|||
? filterMap.getSQLWildcard(QuizData.FILTER_ATTR_NAME)
|
||||
: filterMap.getSQLWildcard(Domain.EXAM.ATTR_QUIZ_NAME);
|
||||
|
||||
final List<ExamRecord> records = whereClause
|
||||
return whereClause
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.quizName,
|
||||
isLikeWhenPresent(nameCriteria))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
return records;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -532,7 +538,7 @@ public class ExamRecordDAO {
|
|||
// if up-coming but running or finished
|
||||
final SqlCriterion<String> upcoming = or(
|
||||
ExamRecordDynamicSqlSupport.status,
|
||||
isEqualTo(ExamStatus.UP_COMING.name()),
|
||||
isIn(ExamStatus.UP_COMING.name(), ExamStatus.TEST_RUN.name()),
|
||||
and(
|
||||
ExamRecordDynamicSqlSupport.quizStartTime,
|
||||
SqlBuilder.isLessThanWhenPresent(now.minus(followupTime))),
|
||||
|
|
|
@ -98,7 +98,8 @@ public class MockCourseAccessAPI implements CourseAccessAPI {
|
|||
"Starts in five minutes and ends never",
|
||||
DateTime.now(DateTimeZone.UTC).plus(Constants.MINUTE_IN_MILLIS * 5)
|
||||
.toString(Constants.DEFAULT_DATE_TIME_FORMAT),
|
||||
null,
|
||||
DateTime.now(DateTimeZone.UTC).plus(Constants.MINUTE_IN_MILLIS * 15)
|
||||
.toString(Constants.DEFAULT_DATE_TIME_FORMAT),
|
||||
"http://lms.mockup.com/api/"));
|
||||
|
||||
// if (webserviceInfo.hasProfile("dev")) {
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
@ -258,4 +257,11 @@ public interface ExamSessionService {
|
|||
return connection.clientConnection.status.clientActiveStatus;
|
||||
}
|
||||
|
||||
/** Toggles the exams test run state.
|
||||
* If the Exam is in state Up-Coming it puts it to Test-Run state
|
||||
* If the Exam is in state Test-Run it puts it back to Up-Coming
|
||||
* Every other state is ignored.
|
||||
* @param exam the Exam data
|
||||
* @return Result refer to Exam with new state or to an exception if there was one */
|
||||
Result<Exam> toggleTestRun(Exam exam);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
|
||||
|
||||
/** Handles caching for exam session and defines caching for following object:
|
||||
*
|
||||
* <p>
|
||||
* - Running exams (examId -> Exam)
|
||||
* - in-memory exam configuration (examId -> InMemorySEBConfig)
|
||||
* - active client connections (connectionToken -> ClientConnectionDataInternal)
|
||||
|
@ -122,14 +122,7 @@ public class ExamSessionCacheService {
|
|||
return false;
|
||||
}
|
||||
|
||||
switch (exam.status) {
|
||||
case RUNNING: {
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return exam.status == Exam.ExamStatus.RUNNING || exam.status == Exam.ExamStatus.TEST_RUN;
|
||||
}
|
||||
|
||||
@Cacheable(
|
||||
|
|
|
@ -265,9 +265,10 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
final FilterMap filterMap,
|
||||
final Predicate<Exam> predicate) {
|
||||
|
||||
final String runningStateNames = ExamStatus.RUNNING.name() + Constants.LIST_SEPARATOR + ExamStatus.TEST_RUN.name();
|
||||
filterMap
|
||||
.putIfAbsent(Exam.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
|
||||
.putIfAbsent(Exam.FILTER_ATTR_STATUS, ExamStatus.RUNNING.name());
|
||||
.putIfAbsent(Exam.FILTER_ATTR_STATUS, runningStateNames);
|
||||
|
||||
return this.examDAO.allMatching(filterMap, predicate)
|
||||
.map(col -> col.stream()
|
||||
|
@ -383,6 +384,25 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> toggleTestRun(final Exam exam) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
if (exam.status == ExamStatus.UP_COMING) {
|
||||
return examDAO
|
||||
.updateState(exam.id, ExamStatus.TEST_RUN, null)
|
||||
.getOrThrow();
|
||||
} else if (exam.status == ExamStatus.TEST_RUN) {
|
||||
return examDAO
|
||||
.updateState(exam.id, ExamStatus.UP_COMING, null)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<ClientConnectionData> getConnectionData(final String connectionToken) {
|
||||
|
||||
|
@ -430,9 +450,6 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
|
||||
// needed to store connection numbers per status
|
||||
final int[] statusMapping = new int[ConnectionStatus.values().length];
|
||||
for (int i = 0; i < statusMapping.length; i++) {
|
||||
statusMapping[i] = 0;
|
||||
}
|
||||
// needed to store connection numbers per client group too
|
||||
final Collection<ClientGroup> groups = this.clientGroupDAO.allForExam(examId).getOr(null);
|
||||
final Map<Long, Integer> clientGroupMapping = (groups != null && !groups.isEmpty())
|
||||
|
@ -440,10 +457,6 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
: null;
|
||||
|
||||
final int[] issueMapping = new int[ConnectionIssueStatus.values().length];
|
||||
for (int i = 0; i < issueMapping.length; i++) {
|
||||
issueMapping[i] = 0;
|
||||
}
|
||||
|
||||
updateClientConnections(examId);
|
||||
|
||||
final List<? extends ClientMonitoringDataView> filteredConnections = this.clientConnectionDAO
|
||||
|
@ -597,7 +610,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
if (this.distributedSetup &&
|
||||
currentTimeMillis - this.lastConnectionTokenCacheUpdate > this.distributedConnectionUpdate) {
|
||||
|
||||
// go trough all client connection and update the ones that not up to date
|
||||
// go through all client connection and update the ones that not up to date
|
||||
this.clientConnectionDAO.evictConnectionTokenCache(examId);
|
||||
|
||||
final Set<Long> timestamps = this.clientConnectionDAO
|
||||
|
@ -611,7 +624,6 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
|
||||
this.clientConnectionDAO.getClientConnectionsOutOfSyc(examId, timestamps)
|
||||
.getOrElse(Collections::emptySet)
|
||||
.stream()
|
||||
.forEach(this.examSessionCacheService::evictClientConnection);
|
||||
|
||||
this.lastConnectionTokenCacheUpdate = currentTimeMillis;
|
||||
|
|
|
@ -229,17 +229,6 @@ class ExamUpdateHandler implements ExamUpdateTask {
|
|||
});
|
||||
}
|
||||
|
||||
@EventListener(ExamUpdateEvent.class)
|
||||
void updateRunning(final ExamUpdateEvent event) {
|
||||
this.examDAO
|
||||
.byPK(event.examId)
|
||||
.onSuccess(exam -> updateState(
|
||||
exam,
|
||||
DateTime.now(DateTimeZone.UTC),
|
||||
this.examTimePrefix,
|
||||
this.examTimeSuffix,
|
||||
this.createUpdateId()));
|
||||
}
|
||||
|
||||
void updateState(
|
||||
final Exam exam,
|
||||
|
|
|
@ -179,7 +179,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
.map(exam -> {
|
||||
|
||||
final boolean isSPSActive = this.screenProctoringAPIBinding.isSPSActive(exam);
|
||||
final boolean isEnabling = this.proctoringSettingsDAO.isScreenProctoringEnabled(exam.id);
|
||||
final boolean isEnabling = this.isScreenProctoringEnabled(exam.id);
|
||||
|
||||
if (isEnabling && !isSPSActive) {
|
||||
// if screen proctoring has been enabled
|
||||
|
@ -219,10 +219,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
return this.examDAO.byPK(examId)
|
||||
.map(exam -> {
|
||||
|
||||
final String enabled = exam.additionalAttributes
|
||||
.get(ScreenProctoringSettings.ATTR_ENABLE_SCREEN_PROCTORING);
|
||||
|
||||
if (!BooleanUtils.toBoolean(enabled)) {
|
||||
if (!this.isScreenProctoringEnabled(exam.id)) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
|
@ -252,10 +249,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
|
||||
@Override
|
||||
public void notifyExamSaved(final Exam exam) {
|
||||
final String enabled = exam.additionalAttributes
|
||||
.get(ScreenProctoringSettings.ATTR_ENABLE_SCREEN_PROCTORING);
|
||||
|
||||
if (!BooleanUtils.toBoolean(enabled)) {
|
||||
if (!this.isScreenProctoringEnabled(exam.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -295,7 +289,8 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
@Override
|
||||
public void notifyExamStarted(final ExamStartedEvent event) {
|
||||
final Exam exam = event.exam;
|
||||
if (BooleanUtils.toBoolean(exam.additionalAttributes.get(SPSData.ATTR_SPS_ACTIVE))) {
|
||||
if (!this.isScreenProctoringEnabled(exam.id) ||
|
||||
BooleanUtils.toBoolean(exam.additionalAttributes.get(SPSData.ATTR_SPS_ACTIVE))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -305,11 +300,14 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
@Override
|
||||
public void notifyExamFinished(final ExamFinishedEvent event) {
|
||||
final Exam exam = event.exam;
|
||||
if (!BooleanUtils.toBoolean(exam.additionalAttributes.get(SPSData.ATTR_SPS_ACTIVE))) {
|
||||
if (!this.isScreenProctoringEnabled(exam.id) ||
|
||||
!BooleanUtils.toBoolean(exam.additionalAttributes.get(SPSData.ATTR_SPS_ACTIVE))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.screenProctoringAPIBinding.deactivateScreenProctoring(exam);
|
||||
if (exam.status == Exam.ExamStatus.FINISHED) {
|
||||
this.screenProctoringAPIBinding.deactivateScreenProctoring(exam);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -565,4 +563,5 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
ccRecord,
|
||||
error));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,11 +22,14 @@ import java.util.stream.Stream;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.*;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
|
@ -69,12 +72,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringRoomService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
|
||||
@WebServiceProfile
|
||||
|
@ -91,10 +88,12 @@ public class ExamMonitoringController {
|
|||
private final AuthorizationService authorization;
|
||||
private final PaginationService paginationService;
|
||||
private final SEBClientNotificationService sebClientNotificationService;
|
||||
private final RemoteProctoringRoomService examProcotringRoomService;
|
||||
private final RemoteProctoringRoomService examProctoringRoomService;
|
||||
private final ExamAdminService examAdminService;
|
||||
private final SecurityKeyService securityKeyService;
|
||||
private final ScreenProctoringService screenProctoringService;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
private final ExamDAO examDAO;
|
||||
private final Executor executor;
|
||||
|
||||
public ExamMonitoringController(
|
||||
|
@ -103,10 +102,12 @@ public class ExamMonitoringController {
|
|||
final AuthorizationService authorization,
|
||||
final PaginationService paginationService,
|
||||
final SEBClientNotificationService sebClientNotificationService,
|
||||
final RemoteProctoringRoomService examProcotringRoomService,
|
||||
final RemoteProctoringRoomService examProctoringRoomService,
|
||||
final SecurityKeyService securityKeyService,
|
||||
final ExamAdminService examAdminService,
|
||||
final ScreenProctoringService screenProctoringService,
|
||||
final ApplicationEventPublisher applicationEventPublisher,
|
||||
final ExamDAO examDAO,
|
||||
@Qualifier(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) final Executor executor) {
|
||||
|
||||
this.sebClientConnectionService = sebClientConnectionService;
|
||||
|
@ -115,10 +116,12 @@ public class ExamMonitoringController {
|
|||
this.authorization = authorization;
|
||||
this.paginationService = paginationService;
|
||||
this.sebClientNotificationService = sebClientNotificationService;
|
||||
this.examProcotringRoomService = examProcotringRoomService;
|
||||
this.examProctoringRoomService = examProctoringRoomService;
|
||||
this.examAdminService = examAdminService;
|
||||
this.securityKeyService = securityKeyService;
|
||||
this.screenProctoringService = screenProctoringService;
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
this.examDAO = examDAO;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
|
@ -230,7 +233,8 @@ public class ExamMonitoringController {
|
|||
this.authorization.checkRole(
|
||||
institutionId,
|
||||
EntityType.EXAM,
|
||||
UserRole.EXAM_SUPPORTER, UserRole.TEACHER,
|
||||
UserRole.EXAM_SUPPORTER,
|
||||
UserRole.TEACHER,
|
||||
UserRole.EXAM_ADMIN);
|
||||
|
||||
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
|
||||
|
@ -255,6 +259,41 @@ public class ExamMonitoringController {
|
|||
ExamAdministrationController.pageSort(sort));
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.EXAM_MONITORING_TEST_RUN_ENDPOINT +
|
||||
API.MODEL_ID_VAR_PATH_SEGMENT,
|
||||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public Exam toggleTestRunForExam(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long examId) {
|
||||
|
||||
// check overall privileges
|
||||
this.authorization.checkRole(
|
||||
institutionId,
|
||||
EntityType.EXAM,
|
||||
UserRole.EXAM_SUPPORTER,
|
||||
UserRole.TEACHER,
|
||||
UserRole.EXAM_ADMIN);
|
||||
|
||||
return this.examDAO.byPK(examId)
|
||||
.flatMap(authorization::checkModify)
|
||||
.flatMap(examSessionService::toggleTestRun)
|
||||
.map(exam -> {
|
||||
if (exam.status == Exam.ExamStatus.TEST_RUN) {
|
||||
applicationEventPublisher.publishEvent(new ExamStartedEvent(exam));
|
||||
} else if (exam.status == Exam.ExamStatus.UP_COMING) {
|
||||
applicationEventPublisher.publishEvent(new ExamFinishedEvent(exam));
|
||||
}
|
||||
return exam;
|
||||
})
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
|
@ -337,7 +376,7 @@ public class ExamMonitoringController {
|
|||
final boolean screenProctoringEnabled = this.examAdminService.isScreenProctoringEnabled(runningExam);
|
||||
|
||||
final Collection<RemoteProctoringRoom> proctoringData = (proctoringEnabled)
|
||||
? this.examProcotringRoomService
|
||||
? this.examProctoringRoomService
|
||||
.getProctoringCollectingRooms(examId)
|
||||
.onError(error -> log.error("Failed to get RemoteProctoringRoom for exam: {}", examId, error))
|
||||
.getOr(Collections.emptyList())
|
||||
|
|
|
@ -577,6 +577,9 @@ sebserver.exam.action.sebrestriction.disable=Release SEB Lock
|
|||
sebserver.exam.action.sebrestriction.details=SEB Restriction Details
|
||||
sebserver.exam.action.createClientToStartExam=Export Exam Connection Configuration
|
||||
sebserver.exam.action.sebrestriction.release.confirm=You are about to release the SEB restriction lock for this exam on the Assessment Tool.<br/>Are you sure you want to release the SEB restriction?
|
||||
sebserver.exam.action.test.run.on=Apply Test Run
|
||||
sebserver.exam.action.test.run.off=Disable Test Run
|
||||
|
||||
|
||||
sebserver.exam.info.pleaseSelect=At first please select an Exam from the list
|
||||
|
||||
|
@ -668,6 +671,7 @@ sebserver.exam.type.VDI=VDI (Virtual Desktop Infrastructure)
|
|||
sebserver.exam.type.VDI.tooltip=Exam type specified for Virtual Desktop Infrastructure
|
||||
|
||||
sebserver.exam.status.UP_COMING=Up Coming
|
||||
sebserver.exam.status.TEST_RUN=Test Run
|
||||
sebserver.exam.status.RUNNING=Running
|
||||
sebserver.exam.status.FINISHED=Finished
|
||||
sebserver.exam.status.ARCHIVED=Archived
|
||||
|
|
Loading…
Reference in a new issue