SEBSERV-457 finished implementation
This commit is contained in:
parent
fc310597e6
commit
cb9900a16d
13 changed files with 109 additions and 36 deletions
|
@ -228,8 +228,7 @@ public class ExamForm implements TemplateComposer {
|
|||
final EntityGrantCheck entityGrantCheck = currentUser.entityGrantCheck(exam);
|
||||
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.getStatus() == ExamStatus.UP_COMING || exam.getStatus() == ExamStatus.RUNNING);
|
||||
final boolean signatureKeyCheckEnabled = BooleanUtils.toBoolean(
|
||||
exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
|
||||
final boolean sebRestrictionAvailable = readonly && testSEBRestrictionAPI(exam);
|
||||
|
@ -294,7 +293,8 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(formContext
|
||||
.clearEntityKeys()
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA)
|
||||
.removeAttribute(AttributeKeys.NEW_EXAM_NO_LMS));
|
||||
|
||||
|
||||
// propagate content actions to action-pane
|
||||
|
@ -302,7 +302,8 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.EXAM_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant && readonly && editable)
|
||||
.publishIf(() -> modifyGrant && readonly &&
|
||||
(editable || (exam.getStatus() == ExamStatus.FINISHED && exam.lmsSetupId == null)))
|
||||
|
||||
.newAction(ActionDefinition.EXAM_DELETE)
|
||||
.withEntityKey(entityKey)
|
||||
|
@ -316,7 +317,7 @@ public class ExamForm implements TemplateComposer {
|
|||
.publishIf(() -> writeGrant && readonly && exam.status == ExamStatus.FINISHED)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_SAVE)
|
||||
.withExec(action -> (importFromQuizData)
|
||||
.withExec(action -> importFromQuizData
|
||||
? importExam(action, formHandle, sebRestrictionAvailable && exam.status == ExamStatus.RUNNING)
|
||||
: formHandle.processFormSave(action))
|
||||
.ignoreMoveAwayFromEdit()
|
||||
|
@ -493,7 +494,7 @@ public class ExamForm implements TemplateComposer {
|
|||
QuizData.QUIZ_ATTR_DESCRIPTION,
|
||||
FORM_DESCRIPTION_TEXT_KEY,
|
||||
exam.getDescription())
|
||||
.asHTML(50)
|
||||
.asHTMLOrArea(50, exam.lmsSetupId != null)
|
||||
.readonly(true)
|
||||
.withInputSpan(7)
|
||||
.withEmptyCellSeparation(false))
|
||||
|
|
|
@ -94,6 +94,14 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public TextFieldBuilder asHTMLOrArea(final int minHeight, final boolean html) {
|
||||
if (html) {
|
||||
return this.asHTML(minHeight);
|
||||
} else {
|
||||
return this.asArea(minHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public TextFieldBuilder asMarkupLabel() {
|
||||
this.isMarkupLabel = true;
|
||||
return this;
|
||||
|
@ -187,4 +195,5 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
|
|||
+ HTML_TEXT_BLOCK_END;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -222,7 +222,7 @@ public final class PageAction {
|
|||
} catch (final FormPostException e) {
|
||||
if (e.getCause() instanceof RestCallError) {
|
||||
final RestCallError cause = (RestCallError) e.getCause();
|
||||
if (cause.isUnexpectedError()) {
|
||||
if (cause.isUnexpectedError() || log.isDebugEnabled()) {
|
||||
log.error("Failed to execute action: {} | error: {} | cause: {}",
|
||||
PageAction.this.getName(),
|
||||
cause.getMessage(),
|
||||
|
|
|
@ -535,8 +535,9 @@ public class WidgetFactory {
|
|||
final LocTextKey ariaLabel) {
|
||||
|
||||
final Text input = readonly
|
||||
? new Text(content, SWT.LEFT | SWT.MULTI)
|
||||
: new Text(content, SWT.LEFT | SWT.BORDER | SWT.MULTI);
|
||||
? new Text(content, SWT.LEFT | SWT.MULTI | SWT.WRAP)
|
||||
: new Text(content, SWT.LEFT | SWT.BORDER | SWT.MULTI | SWT.WRAP);
|
||||
|
||||
if (ariaLabel != null) {
|
||||
WidgetFactory.setARIALabel(input, this.i18nSupport.getText(ariaLabel));
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import java.util.function.Predicate;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mybatis.dynamic.sql.SqlBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -147,7 +148,11 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
|
|||
attrId);
|
||||
}
|
||||
|
||||
return records.get(0).getValue();
|
||||
final String value = records.get(0).getValue();
|
||||
if (value == null) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -270,7 +270,7 @@ public class ExamRecordDAO {
|
|||
}
|
||||
|
||||
if (exam.status != null && !exam.status.name().equals(oldRecord.getStatus())) {
|
||||
log.warn("Exam state change on save. Exam. {}, Old state: {}, new state: {}",
|
||||
log.info("Exam state change on save. Exam. {}, Old state: {}, new state: {}",
|
||||
exam.externalId,
|
||||
oldRecord.getStatus(),
|
||||
exam.status);
|
||||
|
|
|
@ -67,7 +67,7 @@ public interface ExamAdminService {
|
|||
* @return Result refer to the created exam or to an error when happened */
|
||||
Result<Exam> applyAdditionalSEBRestrictions(Exam exam);
|
||||
|
||||
/** Indicates whether a specific exam is been restricted with SEB restriction feature on the LMS or not.
|
||||
/** Indicates whether a specific exam is being restricted with SEB restriction feature on the LMS or not.
|
||||
*
|
||||
* @param exam The exam instance
|
||||
* @return Result refer to the restriction flag or to an error when happened */
|
||||
|
@ -164,14 +164,23 @@ public interface ExamAdminService {
|
|||
void notifyExamSaved(Exam exam);
|
||||
|
||||
static void newExamFieldValidation(final POSTMapper postParams) {
|
||||
final Collection<APIMessage> validationErrors = new ArrayList<>();
|
||||
noLMSFieldValidation(new Exam(postParams));
|
||||
}
|
||||
|
||||
if (!postParams.contains(Domain.EXAM.ATTR_QUIZ_NAME)) {
|
||||
static Exam noLMSFieldValidation(final Exam exam) {
|
||||
|
||||
// This only applies to exams that has no LMS
|
||||
if (exam.lmsSetupId != null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
final Collection<APIMessage> validationErrors = new ArrayList<>();
|
||||
if (StringUtils.isBlank(exam.name)) {
|
||||
validationErrors.add(APIMessage.fieldValidationError(
|
||||
Domain.EXAM.ATTR_QUIZ_NAME,
|
||||
"exam:quizName:notNull"));
|
||||
} else {
|
||||
final int length = postParams.getString(Domain.EXAM.ATTR_QUIZ_NAME).length();
|
||||
final int length = exam.name.length();
|
||||
if (length < 3 || length > 255) {
|
||||
validationErrors.add(APIMessage.fieldValidationError(
|
||||
Domain.EXAM.ATTR_QUIZ_NAME,
|
||||
|
@ -179,13 +188,13 @@ public interface ExamAdminService {
|
|||
}
|
||||
}
|
||||
|
||||
if (!postParams.contains(QuizData.QUIZ_ATTR_START_URL)) {
|
||||
if (StringUtils.isBlank(exam.getStartURL())) {
|
||||
validationErrors.add(APIMessage.fieldValidationError(
|
||||
QuizData.QUIZ_ATTR_START_URL,
|
||||
"exam:quiz_start_url:notNull"));
|
||||
} else {
|
||||
try {
|
||||
new URL(postParams.getString(QuizData.QUIZ_ATTR_START_URL)).toURI();
|
||||
new URL(exam.getStartURL()).toURI();
|
||||
} catch (final Exception e) {
|
||||
validationErrors.add(APIMessage.fieldValidationError(
|
||||
QuizData.QUIZ_ATTR_START_URL,
|
||||
|
@ -193,13 +202,13 @@ public interface ExamAdminService {
|
|||
}
|
||||
}
|
||||
|
||||
if (!postParams.contains(Domain.EXAM.ATTR_QUIZ_START_TIME)) {
|
||||
if (exam.startTime == null) {
|
||||
validationErrors.add(APIMessage.fieldValidationError(
|
||||
Domain.EXAM.ATTR_QUIZ_START_TIME,
|
||||
"exam:quizStartTime:notNull"));
|
||||
} else if (postParams.contains(Domain.EXAM.ATTR_QUIZ_END_TIME)) {
|
||||
if (postParams.getDateTime(Domain.EXAM.ATTR_QUIZ_START_TIME)
|
||||
.isAfter(postParams.getDateTime(Domain.EXAM.ATTR_QUIZ_END_TIME))) {
|
||||
} else if (exam.endTime != null) {
|
||||
if (exam.startTime
|
||||
.isAfter(exam.endTime)) {
|
||||
validationErrors.add(APIMessage.fieldValidationError(
|
||||
Domain.EXAM.ATTR_QUIZ_END_TIME,
|
||||
"exam:quizEndTime:endBeforeStart"));
|
||||
|
@ -209,6 +218,8 @@ public interface ExamAdminService {
|
|||
if (!validationErrors.isEmpty()) {
|
||||
throw new APIMessageException(validationErrors);
|
||||
}
|
||||
|
||||
return exam;
|
||||
}
|
||||
|
||||
/** Used to check threshold consistency for a given list of thresholds.
|
||||
|
@ -326,4 +337,5 @@ public interface ExamAdminService {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -166,6 +166,11 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
public Result<Exam> applyAdditionalSEBRestrictions(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
// this only applies to exams that are attached to an LMS
|
||||
if (exam.lmsSetupId == null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Apply additional SEB restrictions for exam: {}",
|
||||
exam.externalId);
|
||||
|
@ -325,6 +330,11 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
|
||||
private Result<Exam> initAdditionalAttributesForMoodleExams(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
if (exam.lmsSetupId == null) {
|
||||
return exam;
|
||||
}
|
||||
|
||||
final LmsAPITemplate lmsTemplate = this.lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.getOrThrow();
|
||||
|
|
|
@ -84,9 +84,7 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
|
||||
// check only if SEB_RESTRICTION feature is on
|
||||
if (lmsSetup != null && lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
if (!exam.sebRestriction) {
|
||||
return false;
|
||||
}
|
||||
return exam.sebRestriction;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -97,12 +95,11 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
public Result<SEBRestriction> getSEBRestrictionFromExam(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
// load the config keys from restriction and merge with new generated config keys
|
||||
final Set<String> configKeys = new HashSet<>();
|
||||
final Collection<String> generatedKeys = this.examConfigService
|
||||
.generateConfigKeys(exam.institutionId, exam.id)
|
||||
.getOrThrow();
|
||||
|
||||
configKeys.addAll(generatedKeys);
|
||||
final Set<String> configKeys = new HashSet<>(generatedKeys);
|
||||
if (!generatedKeys.isEmpty()) {
|
||||
configKeys.addAll(this.lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
|
@ -210,6 +207,11 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
@EventListener(ExamStartedEvent.class)
|
||||
public void notifyExamStarted(final ExamStartedEvent event) {
|
||||
|
||||
// This affects only exams with LMS binding...
|
||||
if (event.exam.lmsSetupId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("ExamStartedEvent received, process applySEBClientRestriction...");
|
||||
}
|
||||
|
@ -225,6 +227,11 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
@EventListener(ExamFinishedEvent.class)
|
||||
public void notifyExamFinished(final ExamFinishedEvent event) {
|
||||
|
||||
// This affects only exams with LMS binding...
|
||||
if (event.exam.lmsSetupId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("ExamFinishedEvent received, process releaseSEBClientRestriction...");
|
||||
}
|
||||
|
@ -260,6 +267,11 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
@Override
|
||||
public Result<Exam> applySEBClientRestriction(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
if (exam.lmsSetupId == null) {
|
||||
log.info("No LMS for Exam: {}", exam.name);
|
||||
return exam;
|
||||
}
|
||||
|
||||
if (!this.lmsAPIService
|
||||
.getLmsSetup(exam.lmsSetupId)
|
||||
.getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||
|
|
|
@ -27,7 +27,7 @@ public class MockSEBRestrictionAPI implements SEBRestrictionAPI {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(MockSEBRestrictionAPI.class);
|
||||
|
||||
//private Map<Long, Boolean> restrictionDB = new ConcurrentHashMap<>();
|
||||
private Map<Long, SEBRestriction> restrictionDB = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public LmsSetupTestResult testCourseRestrictionAPI() {
|
||||
|
@ -37,7 +37,11 @@ public class MockSEBRestrictionAPI implements SEBRestrictionAPI {
|
|||
@Override
|
||||
public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) {
|
||||
log.info("Get SEB Client restriction for Exam: {}", exam);
|
||||
return Result.ofError(new NoSEBRestrictionException());
|
||||
if (!restrictionDB.containsKey(exam.id)) {
|
||||
return Result.ofError(new NoSEBRestrictionException());
|
||||
} else {
|
||||
return Result.of(restrictionDB.get(exam.id));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,13 +50,24 @@ public class MockSEBRestrictionAPI implements SEBRestrictionAPI {
|
|||
final SEBRestriction sebRestrictionData) {
|
||||
|
||||
log.info("Apply SEB Client restriction: {}", sebRestrictionData);
|
||||
//return Result.ofError(new NoSEBRestrictionException());
|
||||
restrictionDB.put(exam.id, sebRestrictionData);
|
||||
return Result.of(sebRestrictionData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> releaseSEBClientRestriction(final Exam exam) {
|
||||
log.info("Release SEB Client restriction for Exam: {}", exam);
|
||||
if (restrictionDB.containsKey(exam.id)) {
|
||||
SEBRestriction sebRestriction = restrictionDB.get(exam.id);
|
||||
restrictionDB.put(
|
||||
exam.id,
|
||||
new SEBRestriction(
|
||||
exam.id,
|
||||
null,
|
||||
null,
|
||||
sebRestriction.additionalProperties,
|
||||
sebRestriction.warningMessage));
|
||||
}
|
||||
return Result.of(exam);
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
|
|||
log.debug("Update-Lock successfully placed for all involved exams: {}", examsIds);
|
||||
}
|
||||
|
||||
// check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
|
||||
// check running exam integrity again after lock to ensure there were no SEB Client connection attempts in the meantime
|
||||
final Collection<Long> examIdsSecondCheck = checkRunningExamIntegrity(configurationNodeId)
|
||||
.getOrThrow();
|
||||
|
||||
|
@ -129,7 +129,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
|
|||
|
||||
// generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
|
||||
for (final Exam exam : exams) {
|
||||
if (exam.getStatus() == ExamStatus.RUNNING) {
|
||||
if (exam.getStatus() == ExamStatus.RUNNING && exam.lmsSetupId != null) {
|
||||
|
||||
this.examUpdateHandler
|
||||
.getSEBRestrictionService()
|
||||
|
|
|
@ -172,7 +172,9 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ
|
|||
@EventListener
|
||||
public void notifyExamFinished(final ExamFinishedEvent event) {
|
||||
|
||||
log.info("ExamFinishedEvent received, process disposeRoomsForExam...");
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("ExamFinishedEvent received, process disposeRoomsForExam...");
|
||||
}
|
||||
|
||||
disposeRoomsForExam(event.exam)
|
||||
.onError(error -> log.error("Failed to dispose rooms for finished exam: {}", event.exam, error));
|
||||
|
@ -183,12 +185,14 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ
|
|||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
log.info("Dispose and deleting proctoring rooms for exam: {}", exam.externalId);
|
||||
|
||||
final ProctoringServiceSettings proctoringSettings = this.examAdminService
|
||||
.getProctoringServiceSettings(exam.id)
|
||||
.getOrThrow();
|
||||
|
||||
if (proctoringSettings.enableProctoring) {
|
||||
log.info("Dispose and deleting proctoring rooms for exam: {}", exam.externalId);
|
||||
}
|
||||
|
||||
this.proctoringAdminService
|
||||
.getExamProctoringService(proctoringSettings.serverType)
|
||||
.flatMap(service -> service.disposeServiceRoomsForExam(exam.id, proctoringSettings))
|
||||
|
|
|
@ -539,7 +539,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
+ API.EXAM_ADMINISTRATION_SCREEN_PROCTORING_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ScreenProctoringSettings getScreenProctoringeSettings(
|
||||
public ScreenProctoringSettings getScreenProctoringSettings(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
|
@ -655,6 +655,9 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
errors.add(0, ErrorMessage.EXAM_IMPORT_ERROR_AUTO_SETUP.of(
|
||||
entity.getModelId(),
|
||||
API.PARAM_MODEL_ID + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + entity.getModelId()));
|
||||
|
||||
log.warn("Exam successfully created but some initialization did go wrong: {}", errors);
|
||||
|
||||
throw new APIMessageException(errors);
|
||||
} else {
|
||||
return this.examDAO.byPK(entity.id);
|
||||
|
@ -679,7 +682,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
@Override
|
||||
protected Result<Exam> validForSave(final Exam entity) {
|
||||
return super.validForSave(entity)
|
||||
.map(this::checkExamSupporterRole);
|
||||
.map(this::checkExamSupporterRole)
|
||||
.map(ExamAdminService::noLMSFieldValidation);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in a new issue