diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ExamConfigurationMap.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ExamConfigurationMap.java index c91154b4..783747b8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ExamConfigurationMap.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ExamConfigurationMap.java @@ -26,12 +26,15 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM; import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM_CONFIGURATION_MAP; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.GrantEntity; +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.sebconfig.ConfigurationNode.ConfigurationStatus; @JsonIgnoreProperties(ignoreUnknown = true) public final class ExamConfigurationMap implements GrantEntity { + private static final String ARR_EXAM_STATUS = "examStatus"; + public static final String ATTR_CONFIRM_ENCRYPT_SECRET = "confirm_encrypt_secret"; public static final String FILTER_ATTR_EXAM_ID = "examId"; @@ -60,6 +63,9 @@ public final class ExamConfigurationMap implements GrantEntity { @JsonProperty(EXAM.ATTR_TYPE) public final ExamType examType; + @JsonProperty(ARR_EXAM_STATUS) + public final ExamStatus examStatus; + @NotNull(message = "examConfigurationMap:configurationNodeId:notNull") @JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID) public final Long configurationNodeId; @@ -91,6 +97,7 @@ public final class ExamConfigurationMap implements GrantEntity { @JsonProperty(QuizData.QUIZ_ATTR_DESCRIPTION) final String examDescription, @JsonProperty(QuizData.QUIZ_ATTR_START_TIME) final DateTime examStartTime, @JsonProperty(EXAM.ATTR_TYPE) final ExamType examType, + @JsonProperty(ARR_EXAM_STATUS) final ExamStatus examStatus, @JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID) final Long configurationNodeId, @JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_USER_NAMES) final String userNames, @JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret, @@ -106,6 +113,7 @@ public final class ExamConfigurationMap implements GrantEntity { this.examDescription = examDescription; this.examStartTime = examStartTime; this.examType = examType; + this.examStatus = examStatus; this.configurationNodeId = configurationNodeId; this.userNames = userNames; this.encryptSecret = encryptSecret; @@ -125,6 +133,7 @@ public final class ExamConfigurationMap implements GrantEntity { this.examDescription = postParams.getString(QuizData.QUIZ_ATTR_DESCRIPTION); this.examStartTime = postParams.getDateTime(QuizData.QUIZ_ATTR_START_TIME); this.examType = postParams.getEnum(EXAM.ATTR_TYPE, ExamType.class); + this.examStatus = postParams.getEnum(ARR_EXAM_STATUS, ExamStatus.class); this.configurationNodeId = postParams.getLong(Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID); this.userNames = postParams.getString(Domain.EXAM_CONFIGURATION_MAP.ATTR_USER_NAMES); @@ -149,6 +158,7 @@ public final class ExamConfigurationMap implements GrantEntity { this.examDescription = null; this.examStartTime = null; this.examType = null; + this.examStatus = null; this.configurationNodeId = configurationNodeId; this.userNames = userNames; this.encryptSecret = null; @@ -205,6 +215,10 @@ public final class ExamConfigurationMap implements GrantEntity { return this.examType; } + public ExamStatus getExamStatus() { + return this.examStatus; + } + public Long getConfigurationNodeId() { return this.configurationNodeId; } @@ -250,6 +264,7 @@ public final class ExamConfigurationMap implements GrantEntity { this.examDescription, this.examStartTime, this.examType, + this.examStatus, this.configurationNodeId, this.userNames, Constants.EMPTY_NOTE, @@ -296,7 +311,7 @@ public final class ExamConfigurationMap implements GrantEntity { public static ExamConfigurationMap createNew(final Exam exam) { return new ExamConfigurationMap( - null, exam.institutionId, exam.id, exam.name, exam.description, exam.startTime, exam.type, + null, exam.institutionId, exam.id, exam.name, exam.description, exam.startTime, exam.type, exam.status, null, null, null, null, null, null, null); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchStateChangePopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchStateChangePopup.java index 76e5d5fa..ecb21abd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchStateChangePopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigBatchStateChangePopup.java @@ -87,7 +87,7 @@ public class SEBExamConfigBatchStateChangePopup extends AbstractBatchActionWizar FORM_STATUS_TEXT_KEY, targetStateName, () -> this.pageService.getResourceService() - .examConfigStatusResources(false)) + .examConfigStatusResourcesAll()) .readonly(readonly)); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigForm.java index 1a11d122..002e9106 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigForm.java @@ -27,6 +27,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; +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.QuizData; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey; @@ -173,6 +174,16 @@ public class SEBExamConfigForm implements TemplateComposer { .call() .map(names -> names != null && !names.isEmpty()) .getOr(Boolean.FALSE); + final boolean hasRunningExam = isAttachedToExam && this.restService + .getBuilder(GetExamConfigMappingsPage.class) + .withQueryParam(ExamConfigurationMap.FILTER_ATTR_CONFIG_ID, examConfig.getModelId()) + .call() + .map(res -> res.content + .stream() + .filter(map -> map.examStatus == ExamStatus.RUNNING) + .findAny() + .isPresent()) + .getOr(false); // new PageContext with actual EntityKey final PageContext formContext = pageContext.withEntityKey(examConfig.getEntityKey()); @@ -223,7 +234,7 @@ public class SEBExamConfigForm implements TemplateComposer { Domain.CONFIGURATION_NODE.ATTR_STATUS, FORM_STATUS_TEXT_KEY, examConfig.status.name(), - () -> resourceService.examConfigStatusResources(isAttachedToExam)) + () -> resourceService.examConfigStatusResources(isAttachedToExam, hasRunningExam)) .withEmptyCellSeparation(!isReadonly)) .buildFor((isNew) ? this.restService.getRestCall(NewExamConfig.class) @@ -297,7 +308,7 @@ public class SEBExamConfigForm implements TemplateComposer { .withEntityKey(entityKey) .withExec(formHandle::processFormSave) .ignoreMoveAwayFromEdit() - .withConfirm(() -> stateChangeConfirm(isAttachedToExam, formHandle)) + .withConfirm(() -> stateChangeConfirm(hasRunningExam, formHandle)) .publishIf(() -> !isReadonly) .newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY) @@ -436,17 +447,17 @@ public class SEBExamConfigForm implements TemplateComposer { } private LocTextKey stateChangeConfirm( - final boolean isAttachedToExam, + final boolean hasRunningExam, final FormHandle formHandle) { - if (isAttachedToExam) { + if (hasRunningExam) { final String fieldValue = formHandle .getForm() .getFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS); if (fieldValue != null) { final ConfigurationStatus state = ConfigurationStatus.valueOf(fieldValue); - if (state != ConfigurationStatus.IN_USE) { + if (state != ConfigurationStatus.IN_USE && state != ConfigurationStatus.ARCHIVED) { return SAVE_CONFIRM_STATE_CHANGE_WHILE_ATTACHED; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index ee703f4c..0799b45b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -471,7 +471,20 @@ public class ResourceService { .collect(Collectors.toList()); } - public List> examConfigStatusResources(final boolean isAttachedToExam) { + public List> examConfigStatusResourcesAll() { + return Arrays.stream(ConfigurationStatus.values()) + .map(type -> new Tuple3<>( + type.name(), + this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name()), + Utils.formatLineBreaks(this.i18nSupport.getText( + this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name()) + + Constants.TOOLTIP_TEXT_KEY_SUFFIX, + StringUtils.EMPTY)))) + .sorted(RESOURCE_COMPARATOR) + .collect(Collectors.toList()); + } + + public List> examConfigStatusResources(final boolean isAttachedToExam, final boolean hasRunningExam) { return Arrays.stream(ConfigurationStatus.values()) .filter(status -> { if (isAttachedToExam) { @@ -480,6 +493,7 @@ public class ResourceService { return status != ConfigurationStatus.IN_USE; } }) + .filter(status -> !hasRunningExam || status != ConfigurationStatus.ARCHIVED) .map(type -> new Tuple3<>( type.name(), this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name()), diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java index ff364d13..d610e89c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java @@ -442,6 +442,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO { (exam != null) ? exam.description : null, (exam != null) ? exam.startTime : null, (exam != null) ? exam.type : ExamType.UNDEFINED, + (exam != null) ? exam.status : null, record.getConfigurationNodeId(), record.getUserNames(), record.getEncryptSecret(), diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java index f885977c..2894bc47 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java @@ -136,6 +136,7 @@ public class ExamConfigServiceImpl implements ExamConfigService { } } + @Override public Result getFollowupConfigurationId(final Long examConfigNodeId) { return this.configurationDAO.getFollowupConfigurationId(examConfigNodeId); } @@ -443,6 +444,20 @@ public class ExamConfigServiceImpl implements ExamConfigService { } } + // if changing to "In Use" check config is mapped for at least one exam + if (configurationNode.status == ConfigurationStatus.IN_USE && + existingNode.status != ConfigurationStatus.IN_USE) { + + if (this.examConfigurationMapDAO + .getExamIdsForConfigNodeId(configurationNode.id) + .getOr(Collections.emptyList()) + .isEmpty()) { + throw new APIMessageException( + APIMessage.ErrorMessage.INTEGRITY_VALIDATION + .of("Exam configuration has no reference to any exam.")); + } + } + return configurationNode; }); diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java index 72e089d8..99c4b071 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java @@ -209,7 +209,7 @@ public class ModelObjectJSONGenerator { System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); domainObject = new ExamConfigurationMap( - 1L, 1L, 1L, "examName", "examDescription", DateTime.now(), ExamType.BYOD, + 1L, 1L, 1L, "examName", "examDescription", DateTime.now(), ExamType.BYOD, ExamStatus.RUNNING, 1L, "userNames", "encryptSecret", "confirmEncryptSecret", "configName", "configDescription", ConfigurationStatus.IN_USE); System.out.println(domainObject.getClass().getSimpleName() + ":");