improved LMS fail handling on exams
1. if LMS is not available the exams gets a state override and is not running 2. if LMS lms is available but the course id is invalid, the exams gets a state override and is not running
This commit is contained in:
parent
527c005702
commit
2f8913f129
17 changed files with 150 additions and 91 deletions
|
@ -52,7 +52,10 @@ public class APIMessage implements Serializable {
|
||||||
EXAM_CONSISTENCY_VALIDATION_CONFIG("1401", HttpStatus.OK, "No SEB Exam Configuration defined for the Exam"),
|
EXAM_CONSISTENCY_VALIDATION_CONFIG("1401", HttpStatus.OK, "No SEB Exam Configuration defined for the Exam"),
|
||||||
EXAM_CONSISTENCY_VALIDATION_SEB_RESTRICTION("1402", HttpStatus.OK,
|
EXAM_CONSISTENCY_VALIDATION_SEB_RESTRICTION("1402", HttpStatus.OK,
|
||||||
"SEB restriction API available but Exam not restricted on LMS side yet"),
|
"SEB restriction API available but Exam not restricted on LMS side yet"),
|
||||||
EXAM_CONSISTENCY_VALIDATION_INDICATOR("1403", HttpStatus.OK, "No Indicator defined for the Exam");
|
EXAM_CONSISTENCY_VALIDATION_INDICATOR("1403", HttpStatus.OK, "No Indicator defined for the Exam"),
|
||||||
|
EXAM_CONSISTENCY_VALIDATION_LMS_CONNECTION("1404", HttpStatus.OK, "No Connection To LMS"),
|
||||||
|
EXAM_CONSISTENCY_VALIDATION_INVALID_ID_REFERENCE("1405", HttpStatus.OK,
|
||||||
|
"There seems to be an invalid exam - course identifier reference. The course cannot be found");
|
||||||
|
|
||||||
public final String messageCode;
|
public final String messageCode;
|
||||||
public final HttpStatus httpStatus;
|
public final HttpStatus httpStatus;
|
||||||
|
|
|
@ -59,7 +59,9 @@ public final class Exam implements GrantEntity {
|
||||||
public enum ExamStatus {
|
public enum ExamStatus {
|
||||||
UP_COMING,
|
UP_COMING,
|
||||||
RUNNING,
|
RUNNING,
|
||||||
FINISHED
|
FINISHED,
|
||||||
|
CORRUPT_NO_LMS_CONNECTION,
|
||||||
|
CORRUPT_INVALID_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ExamType {
|
public enum ExamType {
|
||||||
|
|
|
@ -82,6 +82,10 @@ public final class LmsSetupTestResult {
|
||||||
.anyMatch(error -> error.errorType == type);
|
.anyMatch(error -> error.errorType == type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasAnyError() {
|
||||||
|
return !this.errors.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
|
|
@ -35,11 +35,8 @@ 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.ExamStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
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.model.institution.LmsSetupTestResult.ErrorType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||||
|
@ -62,7 +59,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRest
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
|
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
|
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.GetQuizData;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam;
|
||||||
|
@ -79,7 +75,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ExamForm.class);
|
private static final Logger log = LoggerFactory.getLogger(ExamForm.class);
|
||||||
|
|
||||||
protected static final String ATTR_READ_GRANT = "ATTR_READ_GRANT";
|
protected static final String ATTR_READ_GRANT = "ATTR_READ_GRANT";
|
||||||
protected static final String ATTR_MODIFY_GRANT = "ATTR_MODIFY_GRANT";
|
protected static final String ATTR_EDITABLE = "ATTR_EDITABLE";
|
||||||
protected static final String ATTR_EXAM_STATUS = "ATTR_EXAM_STATUS";
|
protected static final String ATTR_EXAM_STATUS = "ATTR_EXAM_STATUS";
|
||||||
|
|
||||||
public static final LocTextKey EXAM_FORM_TITLE_KEY =
|
public static final LocTextKey EXAM_FORM_TITLE_KEY =
|
||||||
|
@ -118,6 +114,10 @@ public class ExamForm implements TemplateComposer {
|
||||||
new LocTextKey("sebserver.exam.consistency.missing-config");
|
new LocTextKey("sebserver.exam.consistency.missing-config");
|
||||||
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SEB_RESTRICTION =
|
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SEB_RESTRICTION =
|
||||||
new LocTextKey("sebserver.exam.consistency.missing-seb-restriction");
|
new LocTextKey("sebserver.exam.consistency.missing-seb-restriction");
|
||||||
|
private final static LocTextKey CONSISTENCY_MESSAGE_VALIDATION_LMS_CONNECTION =
|
||||||
|
new LocTextKey("sebserver.exam.consistency.no-lms-connection");
|
||||||
|
private final static LocTextKey CONSISTENCY_MESSAGEINVALID_ID_REFERENCE =
|
||||||
|
new LocTextKey("sebserver.exam.consistency.invalid-lms-id");
|
||||||
|
|
||||||
private final Map<String, LocTextKey> consistencyMessageMapping;
|
private final Map<String, LocTextKey> consistencyMessageMapping;
|
||||||
private final PageService pageService;
|
private final PageService pageService;
|
||||||
|
@ -166,6 +166,12 @@ public class ExamForm implements TemplateComposer {
|
||||||
this.consistencyMessageMapping.put(
|
this.consistencyMessageMapping.put(
|
||||||
APIMessage.ErrorMessage.EXAM_CONSISTENCY_VALIDATION_SEB_RESTRICTION.messageCode,
|
APIMessage.ErrorMessage.EXAM_CONSISTENCY_VALIDATION_SEB_RESTRICTION.messageCode,
|
||||||
CONSISTENCY_MESSAGE_MISSING_SEB_RESTRICTION);
|
CONSISTENCY_MESSAGE_MISSING_SEB_RESTRICTION);
|
||||||
|
this.consistencyMessageMapping.put(
|
||||||
|
APIMessage.ErrorMessage.EXAM_CONSISTENCY_VALIDATION_LMS_CONNECTION.messageCode,
|
||||||
|
CONSISTENCY_MESSAGE_VALIDATION_LMS_CONNECTION);
|
||||||
|
this.consistencyMessageMapping.put(
|
||||||
|
APIMessage.ErrorMessage.EXAM_CONSISTENCY_VALIDATION_INVALID_ID_REFERENCE.messageCode,
|
||||||
|
CONSISTENCY_MESSAGEINVALID_ID_REFERENCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -218,9 +224,8 @@ public class ExamForm implements TemplateComposer {
|
||||||
final boolean modifyGrant = userGrantCheck.m();
|
final boolean modifyGrant = userGrantCheck.m();
|
||||||
final boolean writeGrant = userGrantCheck.w();
|
final boolean writeGrant = userGrantCheck.w();
|
||||||
final ExamStatus examStatus = exam.getStatus();
|
final ExamStatus examStatus = exam.getStatus();
|
||||||
final boolean editable = examStatus == ExamStatus.UP_COMING
|
final boolean editable = modifyGrant && (examStatus == ExamStatus.UP_COMING ||
|
||||||
|| examStatus == ExamStatus.RUNNING
|
examStatus == ExamStatus.RUNNING);
|
||||||
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
|
||||||
final boolean sebRestrictionAvailable = testSEBRestrictionAPI(exam);
|
final boolean sebRestrictionAvailable = testSEBRestrictionAPI(exam);
|
||||||
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
||||||
.getBuilder(CheckSEBRestriction.class)
|
.getBuilder(CheckSEBRestriction.class)
|
||||||
|
@ -387,12 +392,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
.newAction(ActionDefinition.EXAM_MODIFY_SEB_RESTRICTION_DETAILS)
|
.newAction(ActionDefinition.EXAM_MODIFY_SEB_RESTRICTION_DETAILS)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(this.examSEBRestrictionSettings.settingsFunction(this.pageService))
|
.withExec(this.examSEBRestrictionSettings.settingsFunction(this.pageService))
|
||||||
.withAttribute(
|
.withAttribute(ExamSEBRestrictionSettings.PAGE_CONTEXT_ATTR_LMS_ID, String.valueOf(exam.lmsSetupId))
|
||||||
ExamSEBRestrictionSettings.PAGE_CONTEXT_ATTR_LMS_TYPE,
|
|
||||||
this.restService.getBuilder(GetLmsSetup.class)
|
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.lmsSetupId))
|
|
||||||
.call()
|
|
||||||
.getOrThrow().lmsType.name())
|
|
||||||
.withAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY, String.valueOf(!modifyGrant))
|
.withAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY, String.valueOf(!modifyGrant))
|
||||||
.noEventPropagation()
|
.noEventPropagation()
|
||||||
.publishIf(() -> sebRestrictionAvailable && readonly)
|
.publishIf(() -> sebRestrictionAvailable && readonly)
|
||||||
|
@ -433,7 +433,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
formContext
|
formContext
|
||||||
.copyOf(content)
|
.copyOf(content)
|
||||||
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
|
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
|
||||||
.withAttribute(ATTR_MODIFY_GRANT, String.valueOf(modifyGrant))
|
.withAttribute(ATTR_EDITABLE, String.valueOf(editable))
|
||||||
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
|
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
|
||||||
|
|
||||||
// Indicators
|
// Indicators
|
||||||
|
@ -441,7 +441,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
formContext
|
formContext
|
||||||
.copyOf(content)
|
.copyOf(content)
|
||||||
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
|
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
|
||||||
.withAttribute(ATTR_MODIFY_GRANT, String.valueOf(modifyGrant))
|
.withAttribute(ATTR_EDITABLE, String.valueOf(editable))
|
||||||
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
|
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -467,11 +467,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean testSEBRestrictionAPI(final Exam exam) {
|
private boolean testSEBRestrictionAPI(final Exam exam) {
|
||||||
final Result<LmsSetup> lmsSetupCall = this.restService.getBuilder(GetLmsSetup.class)
|
if (exam.status == ExamStatus.CORRUPT_NO_LMS_CONNECTION || exam.status == ExamStatus.CORRUPT_INVALID_ID) {
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.lmsSetupId))
|
|
||||||
.call();
|
|
||||||
|
|
||||||
if (!lmsSetupCall.hasError() && !lmsSetupCall.get().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||||
|
@ -41,7 +40,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
|
||||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||||
|
@ -89,20 +87,15 @@ public class ExamFormConfigs implements TemplateComposer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compose(final PageContext pageContext) {
|
public void compose(final PageContext pageContext) {
|
||||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
|
||||||
final Composite content = pageContext.getParent();
|
final Composite content = pageContext.getParent();
|
||||||
|
|
||||||
final EntityKey entityKey = pageContext.getEntityKey();
|
final EntityKey entityKey = pageContext.getEntityKey();
|
||||||
final boolean modifyGrant = BooleanUtils.toBoolean(
|
final boolean editable = BooleanUtils.toBoolean(
|
||||||
pageContext.getAttribute(ExamForm.ATTR_MODIFY_GRANT));
|
pageContext.getAttribute(ExamForm.ATTR_EDITABLE));
|
||||||
final boolean readGrant = BooleanUtils.toBoolean(
|
final boolean readGrant = BooleanUtils.toBoolean(
|
||||||
pageContext.getAttribute(ExamForm.ATTR_READ_GRANT));
|
pageContext.getAttribute(ExamForm.ATTR_READ_GRANT));
|
||||||
final ExamStatus examStatus = ExamStatus.valueOf(
|
final ExamStatus examStatus = ExamStatus.valueOf(
|
||||||
pageContext.getAttribute(ExamForm.ATTR_EXAM_STATUS));
|
pageContext.getAttribute(ExamForm.ATTR_EXAM_STATUS));
|
||||||
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
|
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
|
||||||
final boolean editable = examStatus == ExamStatus.UP_COMING
|
|
||||||
|| examStatus == ExamStatus.RUNNING
|
|
||||||
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
|
||||||
|
|
||||||
// List of SEB Configuration
|
// List of SEB Configuration
|
||||||
this.widgetFactory.addFormSubContextHeader(
|
this.widgetFactory.addFormSubContextHeader(
|
||||||
|
@ -134,7 +127,7 @@ public class ExamFormConfigs implements TemplateComposer {
|
||||||
this.resourceService::localizedExamConfigStatusName)
|
this.resourceService::localizedExamConfigStatusName)
|
||||||
.widthProportion(1))
|
.widthProportion(1))
|
||||||
.withDefaultActionIf(
|
.withDefaultActionIf(
|
||||||
() -> true,
|
() -> readGrant,
|
||||||
this::viewExamConfigPageAction)
|
this::viewExamConfigPageAction)
|
||||||
|
|
||||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||||
|
@ -162,12 +155,12 @@ public class ExamFormConfigs implements TemplateComposer {
|
||||||
.withParentEntityKey(entityKey)
|
.withParentEntityKey(entityKey)
|
||||||
.withExec(this.examToConfigBindingForm.bindFunction())
|
.withExec(this.examToConfigBindingForm.bindFunction())
|
||||||
.noEventPropagation()
|
.noEventPropagation()
|
||||||
.publishIf(() -> modifyGrant && editable && !configurationTable.hasAnyContent())
|
.publishIf(() -> editable && !configurationTable.hasAnyContent())
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
|
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
|
||||||
.withParentEntityKey(entityKey)
|
.withParentEntityKey(entityKey)
|
||||||
.withEntityKey(configMapKey)
|
.withEntityKey(configMapKey)
|
||||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent(), false)
|
.publishIf(() -> readGrant && configurationTable.hasAnyContent(), false)
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
|
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
|
@ -181,7 +174,7 @@ public class ExamFormConfigs implements TemplateComposer {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable, false)
|
.publishIf(() -> editable && configurationTable.hasAnyContent() && editable, false)
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
|
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
|
||||||
.withSelect(
|
.withSelect(
|
||||||
|
|
|
@ -72,8 +72,8 @@ public class ExamFormIndicators implements TemplateComposer {
|
||||||
public void compose(final PageContext pageContext) {
|
public void compose(final PageContext pageContext) {
|
||||||
final Composite content = pageContext.getParent();
|
final Composite content = pageContext.getParent();
|
||||||
final EntityKey entityKey = pageContext.getEntityKey();
|
final EntityKey entityKey = pageContext.getEntityKey();
|
||||||
final boolean modifyGrant = BooleanUtils.toBoolean(
|
final boolean editable = BooleanUtils.toBoolean(
|
||||||
pageContext.getAttribute(ExamForm.ATTR_MODIFY_GRANT));
|
pageContext.getAttribute(ExamForm.ATTR_EDITABLE));
|
||||||
|
|
||||||
// List of Indicators
|
// List of Indicators
|
||||||
this.widgetFactory.addFormSubContextHeader(
|
this.widgetFactory.addFormSubContextHeader(
|
||||||
|
@ -111,7 +111,7 @@ public class ExamFormIndicators implements TemplateComposer {
|
||||||
.asMarkup()
|
.asMarkup()
|
||||||
.widthProportion(4))
|
.widthProportion(4))
|
||||||
.withDefaultActionIf(
|
.withDefaultActionIf(
|
||||||
() -> modifyGrant,
|
() -> editable,
|
||||||
() -> actionBuilder
|
() -> actionBuilder
|
||||||
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
|
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
|
||||||
.withParentEntityKey(entityKey)
|
.withParentEntityKey(entityKey)
|
||||||
|
@ -132,7 +132,7 @@ public class ExamFormIndicators implements TemplateComposer {
|
||||||
indicatorTable::getSelection,
|
indicatorTable::getSelection,
|
||||||
PageAction::applySingleSelectionAsEntityKey,
|
PageAction::applySingleSelectionAsEntityKey,
|
||||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
|
.publishIf(() -> editable && indicatorTable.hasAnyContent(), false)
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
|
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
|
@ -140,11 +140,11 @@ public class ExamFormIndicators implements TemplateComposer {
|
||||||
indicatorTable::getSelection,
|
indicatorTable::getSelection,
|
||||||
this::deleteSelectedIndicator,
|
this::deleteSelectedIndicator,
|
||||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
|
.publishIf(() -> editable && indicatorTable.hasAnyContent(), false)
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_INDICATOR_NEW)
|
.newAction(ActionDefinition.EXAM_INDICATOR_NEW)
|
||||||
.withParentEntityKey(entityKey)
|
.withParentEntityKey(entityKey)
|
||||||
.publishIf(() -> modifyGrant);
|
.publishIf(() -> editable);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -254,7 +254,7 @@ public class ExamList implements TemplateComposer {
|
||||||
final Exam exam,
|
final Exam exam,
|
||||||
final PageService pageService) {
|
final PageService pageService) {
|
||||||
|
|
||||||
if (exam.getStatus() != ExamStatus.RUNNING) {
|
if (exam.getStatus() == ExamStatus.UP_COMING || exam.getStatus() == ExamStatus.FINISHED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeactivateSE
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetCourseChapters;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetCourseChapters;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSEBRestrictionSettings;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSEBRestrictionSettings;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSEBRestriction;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSEBRestriction;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
|
@ -81,7 +82,7 @@ public class ExamSEBRestrictionSettings {
|
||||||
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED =
|
private final static LocTextKey SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED =
|
||||||
new LocTextKey("sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED");
|
new LocTextKey("sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED");
|
||||||
|
|
||||||
static final String PAGE_CONTEXT_ATTR_LMS_TYPE = "ATTR_LMS_TYPE";
|
static final String PAGE_CONTEXT_ATTR_LMS_ID = "ATTR_LMS_ID";
|
||||||
|
|
||||||
Function<PageAction, PageAction> settingsFunction(final PageService pageService) {
|
Function<PageAction, PageAction> settingsFunction(final PageService pageService) {
|
||||||
|
|
||||||
|
@ -126,7 +127,7 @@ public class ExamSEBRestrictionSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
final EntityKey entityKey = pageContext.getEntityKey();
|
final EntityKey entityKey = pageContext.getEntityKey();
|
||||||
final LmsType lmsType = getLmsType(pageContext);
|
final LmsType lmsType = getLmsType(pageService, pageContext.getAttribute(PAGE_CONTEXT_ATTR_LMS_ID));
|
||||||
SEBRestriction bodyValue = null;
|
SEBRestriction bodyValue = null;
|
||||||
try {
|
try {
|
||||||
final Form form = formHandle.getForm();
|
final Form form = formHandle.getForm();
|
||||||
|
@ -194,7 +195,9 @@ public class ExamSEBRestrictionSettings {
|
||||||
final RestService restService = this.pageService.getRestService();
|
final RestService restService = this.pageService.getRestService();
|
||||||
final ResourceService resourceService = this.pageService.getResourceService();
|
final ResourceService resourceService = this.pageService.getResourceService();
|
||||||
final EntityKey entityKey = this.pageContext.getEntityKey();
|
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||||
final LmsType lmsType = getLmsType(this.pageContext);
|
final LmsType lmsType = getLmsType(
|
||||||
|
this.pageService,
|
||||||
|
this.pageContext.getAttribute(PAGE_CONTEXT_ATTR_LMS_ID));
|
||||||
final boolean isReadonly = BooleanUtils.toBoolean(
|
final boolean isReadonly = BooleanUtils.toBoolean(
|
||||||
this.pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY));
|
this.pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY));
|
||||||
|
|
||||||
|
@ -305,9 +308,16 @@ public class ExamSEBRestrictionSettings {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LmsType getLmsType(final PageContext pageContext) {
|
private LmsType getLmsType(final PageService pageService, final String lmsSetupId) {
|
||||||
try {
|
try {
|
||||||
return LmsType.valueOf(pageContext.getAttribute(PAGE_CONTEXT_ATTR_LMS_TYPE));
|
|
||||||
|
return pageService
|
||||||
|
.getRestService()
|
||||||
|
.getBuilder(GetLmsSetup.class)
|
||||||
|
.withURIVariable(API.PARAM_MODEL_ID, lmsSetupId)
|
||||||
|
.call()
|
||||||
|
.getOrThrow().lmsType;
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<GrantEntity> examGrantEntityByPK(final Long id) {
|
public Result<GrantEntity> examGrantEntityByPK(final Long id) {
|
||||||
return recordById(id)
|
return recordById(id)
|
||||||
.map(record -> toDomainModel(record, null).getOrThrow());
|
.map(record -> toDomainModel(record, null, null).getOrThrow());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -111,7 +111,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
||||||
.selectByPrimaryKey(connectionId))
|
.selectByPrimaryKey(connectionId))
|
||||||
.flatMap(ccRecord -> recordById(ccRecord.getExamId()))
|
.flatMap(ccRecord -> recordById(ccRecord.getExamId()))
|
||||||
.map(record -> toDomainModel(record, null).getOrThrow());
|
.map(record -> toDomainModel(record, null, null).getOrThrow());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -793,17 +793,48 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
final Map<String, QuizData> quizzes = this.lmsAPIService
|
final Map<String, QuizData> quizzes = this.lmsAPIService
|
||||||
.getLmsAPITemplate(lmsSetupId)
|
.getLmsAPITemplate(lmsSetupId)
|
||||||
.map(template -> getQuizzesFromLMS(template, recordMapping.keySet(), cached))
|
.map(template -> getQuizzesFromLMS(template, recordMapping.keySet(), cached))
|
||||||
.getOrThrow()
|
.onError(error -> log.error("Failed to get quizzes for exams: ", error))
|
||||||
|
.getOr(Collections.emptyList())
|
||||||
.stream()
|
.stream()
|
||||||
.flatMap(Result::skipOnError)
|
.flatMap(Result::skipOnError)
|
||||||
.collect(Collectors.toMap(q -> q.id, Function.identity()));
|
.collect(Collectors.toMap(q -> q.id, Function.identity()));
|
||||||
|
|
||||||
|
if (records.size() != quizzes.size()) {
|
||||||
|
|
||||||
|
// Check if we have LMS connection to verify the source of the exam quiz mismatch
|
||||||
|
final LmsAPITemplate lmsSetup = this.lmsAPIService
|
||||||
|
.getLmsAPITemplate(lmsSetupId)
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Quizzes size mismatch detected by getting exams quiz data from LMS: {}", lmsSetup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lmsSetup.testCourseAccessAPI().hasAnyError()) {
|
||||||
|
// No course access on the LMS. This means we can't get any quizzes from this LMSSetup at the moment
|
||||||
|
// All exams are marked as corrupt because of LMS Setup failure
|
||||||
|
|
||||||
|
log.warn("Failed to get quizzes form LMS Setup. No access to LMS {}", lmsSetup);
|
||||||
|
|
||||||
|
return recordMapping.entrySet()
|
||||||
|
.stream()
|
||||||
|
.map(entry -> toDomainModel(
|
||||||
|
entry.getValue(),
|
||||||
|
null,
|
||||||
|
ExamStatus.CORRUPT_NO_LMS_CONNECTION)
|
||||||
|
.getOr(null))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// collect Exam's
|
// collect Exam's
|
||||||
return recordMapping.entrySet()
|
return recordMapping.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
.map(entry -> toDomainModel(
|
.map(entry -> toDomainModel(
|
||||||
entry.getValue(),
|
entry.getValue(),
|
||||||
getQuizData(quizzes, entry.getKey(), entry.getValue()))
|
getQuizData(quizzes, entry.getKey(), entry.getValue()),
|
||||||
|
ExamStatus.CORRUPT_INVALID_ID)
|
||||||
.onError(error -> log.error(
|
.onError(error -> log.error(
|
||||||
"Failed to get quiz data from remote LMS for exam: ",
|
"Failed to get quiz data from remote LMS for exam: ",
|
||||||
error))
|
error))
|
||||||
|
@ -813,8 +844,11 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<Result<QuizData>> getQuizzesFromLMS(final LmsAPITemplate template, final Set<String> ids,
|
private Collection<Result<QuizData>> getQuizzesFromLMS(
|
||||||
|
final LmsAPITemplate template,
|
||||||
|
final Set<String> ids,
|
||||||
final boolean cached) {
|
final boolean cached) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return (cached)
|
return (cached)
|
||||||
? template.getQuizzesFromCache(ids)
|
? template.getQuizzesFromCache(ids)
|
||||||
|
@ -840,6 +874,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
// a case by using the short name of the quiz and search for the quiz within the course with this
|
// a case by using the short name of the quiz and search for the quiz within the course with this
|
||||||
// short name. If one quiz has been found that matches all criteria, we adapt the internal id
|
// short name. If one quiz has been found that matches all criteria, we adapt the internal id
|
||||||
// mapping to this quiz.
|
// mapping to this quiz.
|
||||||
|
// If recovering fails, this returns null and the calling side must handle the lack of quiz data
|
||||||
try {
|
try {
|
||||||
final LmsSetup lmsSetup = this.lmsAPIService
|
final LmsSetup lmsSetup = this.lmsAPIService
|
||||||
.getLmsSetup(record.getLmsSetupId())
|
.getLmsSetup(record.getLmsSetupId())
|
||||||
|
@ -914,7 +949,8 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
|
|
||||||
private Result<Exam> toDomainModel(
|
private Result<Exam> toDomainModel(
|
||||||
final ExamRecord record,
|
final ExamRecord record,
|
||||||
final QuizData quizData) {
|
final QuizData quizData,
|
||||||
|
final ExamStatus statusOverride) {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
@ -943,7 +979,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
ExamType.valueOf(record.getType()),
|
ExamType.valueOf(record.getType()),
|
||||||
record.getOwner(),
|
record.getOwner(),
|
||||||
supporter,
|
supporter,
|
||||||
status,
|
(quizData != null) ? status : (statusOverride != null) ? statusOverride : status,
|
||||||
record.getBrowserKeys(),
|
record.getBrowserKeys(),
|
||||||
BooleanUtils.toBooleanObject((quizData != null) ? record.getActive() : null),
|
BooleanUtils.toBooleanObject((quizData != null) ? record.getActive() : null),
|
||||||
record.getLastupdate());
|
record.getLastupdate());
|
||||||
|
|
|
@ -191,7 +191,7 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
if (restTemplateRequest.hasError()) {
|
if (restTemplateRequest.hasError()) {
|
||||||
final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " +
|
final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " +
|
||||||
this.moodleRestTemplateFactory.knownTokenAccessPaths;
|
this.moodleRestTemplateFactory.knownTokenAccessPaths;
|
||||||
log.error(message + " cause: ", restTemplateRequest.getError());
|
log.error(message + " cause: {}", restTemplateRequest.getError().getMessage());
|
||||||
return LmsSetupTestResult.ofTokenRequestError(message);
|
return LmsSetupTestResult.ofTokenRequestError(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,14 +213,14 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
protected Supplier<List<QuizData>> quizzesSupplier(final Set<String> ids) {
|
protected Supplier<List<QuizData>> quizzesSupplier(final Set<String> ids) {
|
||||||
return () -> getRestTemplate()
|
return () -> getRestTemplate()
|
||||||
.map(template -> getQuizzesForIds(template, ids))
|
.map(template -> getQuizzesForIds(template, ids))
|
||||||
.getOrThrow();
|
.getOr(Collections.emptyList());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap) {
|
protected Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap) {
|
||||||
return () -> getRestTemplate()
|
return () -> getRestTemplate()
|
||||||
.map(template -> collectAllQuizzes(template, filterMap))
|
.map(template -> collectAllQuizzes(template, filterMap))
|
||||||
.getOrThrow();
|
.getOr(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -380,17 +380,7 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (courseQuizData.warnings != null && !courseQuizData.warnings.isEmpty()) {
|
logMoodleWarnings(courseQuizData.warnings);
|
||||||
log.warn(
|
|
||||||
"There are warnings from Moodle response: Moodle: {} request: {} warnings: {} warning sample: {}",
|
|
||||||
this.lmsSetup,
|
|
||||||
MoodleCourseAccess.MOODLE_QUIZ_API_FUNCTION_NAME,
|
|
||||||
courseQuizData.warnings.size(),
|
|
||||||
courseQuizData.warnings.iterator().next().toString());
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("All warnings from Moodle: {}", courseQuizData.warnings.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (courseQuizData.quizzes == null || courseQuizData.quizzes.isEmpty()) {
|
if (courseQuizData.quizzes == null || courseQuizData.quizzes.isEmpty()) {
|
||||||
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, this.lmsSetup.name);
|
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, this.lmsSetup.name);
|
||||||
|
@ -461,17 +451,7 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
Collections.emptyList();
|
Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (courses.warnings != null && !courses.warnings.isEmpty()) {
|
logMoodleWarnings(courses.warnings);
|
||||||
log.warn(
|
|
||||||
"There are warnings from Moodle response: Moodle: {} request: {} warnings: {} warning sample: {}",
|
|
||||||
this.lmsSetup,
|
|
||||||
MoodleCourseAccess.MOODLE_COURSE_BY_FIELD_API_FUNCTION_NAME,
|
|
||||||
courses.warnings.size(),
|
|
||||||
courses.warnings.iterator().next().toString());
|
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("All warnings from Moodle: {}", courses.warnings.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (courses.courses == null || courses.courses.isEmpty()) {
|
if (courses.courses == null || courses.courses.isEmpty()) {
|
||||||
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
||||||
|
@ -643,6 +623,21 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
return idNumber.equals(Constants.EMPTY_NOTE) ? null : idNumber;
|
return idNumber.equals(Constants.EMPTY_NOTE) ? null : idNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void logMoodleWarnings(final Collection<Warning> warnings) {
|
||||||
|
if (warnings != null && !warnings.isEmpty()) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(
|
||||||
|
"There are warnings from Moodle response: Moodle: {} request: {} warnings: {} warning sample: {}",
|
||||||
|
this.lmsSetup,
|
||||||
|
MoodleCourseAccess.MOODLE_QUIZ_API_FUNCTION_NAME,
|
||||||
|
warnings.size(),
|
||||||
|
warnings.iterator().next().toString());
|
||||||
|
} else if (log.isTraceEnabled()) {
|
||||||
|
log.trace("All warnings from Moodle: {}", warnings.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final Pattern ACCESS_DENIED_PATTERN_1 =
|
private static final Pattern ACCESS_DENIED_PATTERN_1 =
|
||||||
Pattern.compile(Pattern.quote("No access rights"), Pattern.CASE_INSENSITIVE);
|
Pattern.compile(Pattern.quote("No access rights"), Pattern.CASE_INSENSITIVE);
|
||||||
private static final Pattern ACCESS_DENIED_PATTERN_2 =
|
private static final Pattern ACCESS_DENIED_PATTERN_2 =
|
||||||
|
|
|
@ -117,7 +117,7 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
|
||||||
@Override
|
@Override
|
||||||
public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) {
|
public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Get SEB Client restriction for Exam: {}", exam);
|
log.debug("Get SEB Client restriction for Exam: {}", exam.externalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.moodleCourseRestriction
|
return this.moodleCourseRestriction
|
||||||
|
|
|
@ -128,14 +128,18 @@ class MoodleRestTemplateFactory {
|
||||||
.map(this::createRestTemplate)
|
.map(this::createRestTemplate)
|
||||||
.map(result -> {
|
.map(result -> {
|
||||||
if (result.hasError()) {
|
if (result.hasError()) {
|
||||||
log.error("Failed to get access token: ", result.getError());
|
log.warn("Failed to get access token for LMS: {}({})",
|
||||||
|
this.lmsSetup.name,
|
||||||
|
this.lmsSetup.id);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
.filter(Result::hasValue)
|
.filter(Result::hasValue)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(Result.ofRuntimeError(
|
.orElse(Result.ofRuntimeError(
|
||||||
"Failed to gain any access on paths: " + this.knownTokenAccessPaths));
|
"Failed to gain any access for LMS " +
|
||||||
|
this.lmsSetup.name + "(" + this.lmsSetup.id +
|
||||||
|
") on paths: " + this.knownTokenAccessPaths));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<MoodleAPIRestTemplate> createRestTemplate(final String accessTokenPath) {
|
Result<MoodleAPIRestTemplate> createRestTemplate(final String accessTokenPath) {
|
||||||
|
@ -146,7 +150,9 @@ class MoodleRestTemplateFactory {
|
||||||
|
|
||||||
final CharSequence accessToken = template.getAccessToken();
|
final CharSequence accessToken = template.getAccessToken();
|
||||||
if (accessToken == null) {
|
if (accessToken == null) {
|
||||||
throw new RuntimeException("Failed to gain access token on path: " + accessTokenPath);
|
throw new RuntimeException("Failed to get access token for LMS " +
|
||||||
|
this.lmsSetup.name + "(" + this.lmsSetup.id +
|
||||||
|
") on path: " + accessTokenPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
|
|
|
@ -64,6 +64,7 @@ public interface ExamSessionService {
|
||||||
/** Use this to check the consistency of a running Exam.
|
/** Use this to check the consistency of a running Exam.
|
||||||
* Current consistency checks are:
|
* Current consistency checks are:
|
||||||
* - Check if there is at least one Exam supporter attached to the Exam
|
* - Check if there is at least one Exam supporter attached to the Exam
|
||||||
|
* - Check if we have access to LMS for the exam
|
||||||
* - Check if there is one default SEB Exam Configuration attached to the Exam
|
* - Check if there is one default SEB Exam Configuration attached to the Exam
|
||||||
* - Check if SEB restriction API is available and the exam is running but not yet restricted on LMS side
|
* - Check if SEB restriction API is available and the exam is running but not yet restricted on LMS side
|
||||||
* - Check if there is at least one Indicator defined for the monitoring of the Exam
|
* - Check if there is at least one Indicator defined for the monitoring of the Exam
|
||||||
|
@ -71,7 +72,7 @@ public interface ExamSessionService {
|
||||||
* @param examId the identifier of the Exam to check
|
* @param examId the identifier of the Exam to check
|
||||||
* @return Result of one APIMessage per consistency check if the check failed. An empty Collection of everything is
|
* @return Result of one APIMessage per consistency check if the check failed. An empty Collection of everything is
|
||||||
* okay. */
|
* okay. */
|
||||||
Result<Collection<APIMessage>> checkRunningExamConsistency(Long examId);
|
Result<Collection<APIMessage>> checkExamConsistency(Long examId);
|
||||||
|
|
||||||
/** 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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -133,7 +133,8 @@ public class ExamSessionCacheService {
|
||||||
case RUNNING: {
|
case RUNNING: {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case UP_COMING: {
|
case UP_COMING:
|
||||||
|
case FINISHED: {
|
||||||
return this.examUpdateHandler.updateRunning(exam.id)
|
return this.examUpdateHandler.updateRunning(exam.id)
|
||||||
.map(e -> e.status == ExamStatus.RUNNING)
|
.map(e -> e.status == ExamStatus.RUNNING)
|
||||||
.getOr(false);
|
.getOr(false);
|
||||||
|
|
|
@ -121,14 +121,22 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Collection<APIMessage>> checkRunningExamConsistency(final Long examId) {
|
public Result<Collection<APIMessage>> checkExamConsistency(final Long examId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
final Collection<APIMessage> result = new ArrayList<>();
|
final Collection<APIMessage> result = new ArrayList<>();
|
||||||
|
|
||||||
if (isExamRunning(examId)) {
|
final Exam exam = this.examDAO.byPK(examId)
|
||||||
final Exam exam = getRunningExam(examId)
|
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
// check lms connection
|
||||||
|
if (exam.status == ExamStatus.CORRUPT_NO_LMS_CONNECTION) {
|
||||||
|
result.add(ErrorMessage.EXAM_CONSISTENCY_VALIDATION_LMS_CONNECTION.of(exam.getModelId()));
|
||||||
|
}
|
||||||
|
if (exam.status == ExamStatus.CORRUPT_INVALID_ID) {
|
||||||
|
result.add(ErrorMessage.EXAM_CONSISTENCY_VALIDATION_INVALID_ID_REFERENCE.of(exam.getModelId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exam.status == ExamStatus.RUNNING) {
|
||||||
// check exam supporter
|
// check exam supporter
|
||||||
if (exam.getSupporter().isEmpty()) {
|
if (exam.getSupporter().isEmpty()) {
|
||||||
result.add(ErrorMessage.EXAM_CONSISTENCY_VALIDATION_SUPPORTER.of(exam.getModelId()));
|
result.add(ErrorMessage.EXAM_CONSISTENCY_VALIDATION_SUPPORTER.of(exam.getModelId()));
|
||||||
|
|
|
@ -246,7 +246,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
|
|
||||||
checkReadPrivilege(institutionId);
|
checkReadPrivilege(institutionId);
|
||||||
return this.examSessionService
|
return this.examSessionService
|
||||||
.checkRunningExamConsistency(modelId)
|
.checkExamConsistency(modelId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -430,6 +430,8 @@ sebserver.exam.consistency.missing-supporter= - There are no Exam Supporter defi
|
||||||
sebserver.exam.consistency.missing-indicator= - There is no indicator defined for this exam. Use 'Add Indicator" on the right to add an indicator.
|
sebserver.exam.consistency.missing-indicator= - There is no indicator defined for this exam. Use 'Add Indicator" on the right to add an indicator.
|
||||||
sebserver.exam.consistency.missing-config= - There is no configuration defined for this exam. Use 'Add Configuration' to attach one.
|
sebserver.exam.consistency.missing-config= - There is no configuration defined for this exam. Use 'Add Configuration' to attach one.
|
||||||
sebserver.exam.consistency.missing-seb-restriction= - There is currently no SEB restriction applied on the LMS side. Use 'Enable SEB Restriction' on the right to activate auto-restriction.<br/> Or if this is not possible consider doing it manually on the LMS.
|
sebserver.exam.consistency.missing-seb-restriction= - There is currently no SEB restriction applied on the LMS side. Use 'Enable SEB Restriction' on the right to activate auto-restriction.<br/> Or if this is not possible consider doing it manually on the LMS.
|
||||||
|
sebserver.exam.consistency.no-lms-connection= - Failed to connect to the LMS Setup of this exam yet.<br/>Please check the LMS connection within the LMS Setup.
|
||||||
|
sebserver.exam.consistency.invalid-lms-id= - The referencing course identifier seems to be invalid.<br/>Please check if the course for this exam still exists on the LMS and the course identifier has not changed.
|
||||||
sebserver.exam.confirm.remove-config=This exam is current running. The remove of the attached configuration will led to an invalid state<br/>where connecting SEB clients cannot download the configuration for the exam.<br/><br/>Are you sure to remove the configuration?
|
sebserver.exam.confirm.remove-config=This exam is current running. The remove of the attached configuration will led to an invalid state<br/>where connecting SEB clients cannot download the configuration for the exam.<br/><br/>Are you sure to remove the configuration?
|
||||||
|
|
||||||
sebserver.exam.action.list=Exam
|
sebserver.exam.action.list=Exam
|
||||||
|
@ -530,6 +532,8 @@ sebserver.exam.type.VDI.tooltip=Exam type specified for Virtual Desktop Infrastr
|
||||||
sebserver.exam.status.UP_COMING=Up Coming
|
sebserver.exam.status.UP_COMING=Up Coming
|
||||||
sebserver.exam.status.RUNNING=Running
|
sebserver.exam.status.RUNNING=Running
|
||||||
sebserver.exam.status.FINISHED=Finished
|
sebserver.exam.status.FINISHED=Finished
|
||||||
|
sebserver.exam.status.CORRUPT_NO_LMS_CONNECTION=Corrupt (No LMS Connection)
|
||||||
|
sebserver.exam.status.CORRUPT_INVALID_ID=Corrupt (Invalid Identifier)
|
||||||
|
|
||||||
sebserver.exam.configuration.list.actions=
|
sebserver.exam.configuration.list.actions=
|
||||||
sebserver.exam.configuration.list.title=Exam Configuration
|
sebserver.exam.configuration.list.title=Exam Configuration
|
||||||
|
|
Loading…
Reference in a new issue