Skip to content

Commit

Permalink
Merge branch 'development' of github.com:SafeExamBrowser/seb-server i…
Browse files Browse the repository at this point in the history
…nto SEBSP-131
  • Loading branch information
Nadim Ritter committed Jun 28, 2024
2 parents 4b0f88d + 41ce1bc commit 205e9b2
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 82 deletions.
2 changes: 1 addition & 1 deletion src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ private BatchActionType(final EntityType entityType) {
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 + "}";
Expand Down
18 changes: 12 additions & 6 deletions src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Exam.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -68,6 +63,7 @@ public final class Exam implements GrantEntity {

public enum ExamStatus {
UP_COMING,
TEST_RUN,
RUNNING,
FINISHED,
ARCHIVED
Expand All @@ -80,6 +76,16 @@ public enum ExamType {
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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
37 changes: 32 additions & 5 deletions src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand All @@ -44,8 +45,6 @@
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;
Expand All @@ -68,7 +67,6 @@
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;
Expand Down Expand Up @@ -195,14 +193,16 @@ public void compose(final PageContext pageContext) {
.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());
final EntityGrantCheck entityGrantCheck = currentUser.entityGrantCheck(exam);
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);
Expand Down Expand Up @@ -288,6 +288,16 @@ public void compose(final PageContext pageContext) {
.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)
Expand Down Expand Up @@ -399,6 +409,8 @@ public void compose(final PageContext pageContext) {
}
}



private FormHandle<Exam> createReadOnlyForm(
final PageContext formContext,
final Composite content,
Expand Down Expand Up @@ -806,7 +818,8 @@ private Result<Exam> createExamFromQuizData(final PageContext pageContext) {
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()
Expand All @@ -833,4 +846,18 @@ private Function<PageAction, PageAction> cancelModifyFunction() {
};
}

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;
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
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;
Expand All @@ -37,7 +36,6 @@
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;
Expand Down Expand Up @@ -67,10 +65,6 @@
@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;
Expand Down Expand Up @@ -440,7 +434,7 @@ private boolean isExamActive(final Long examId) {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,18 @@ public Result<Collection<ExamRecord>> allMatching(final FilterMap filterMap, fin

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(
Expand All @@ -234,14 +242,12 @@ public Result<Collection<ExamRecord>> allMatching(final FilterMap filterMap, fin
? 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;
});
}

Expand Down Expand Up @@ -532,7 +538,7 @@ public Result<Collection<ExamRecord>> allThatNeedsStatusUpdate(final long leadTi
// 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))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ public MockCourseAccessAPI(
"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")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -258,4 +257,11 @@ static boolean isActiveConnection(final ClientConnectionData connection) {
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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
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)
Expand Down Expand Up @@ -122,14 +122,7 @@ public boolean isRunning(final Exam exam) {
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(
Expand Down
Loading

0 comments on commit 205e9b2

Please sign in to comment.