SEBSERV-160 fixed "In Use" for selection

This commit is contained in:
anhefti 2022-05-02 13:35:35 +02:00
parent 8b9eebfe5b
commit 6396afa53b
7 changed files with 65 additions and 9 deletions

View file

@ -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.Domain.EXAM_CONFIGURATION_MAP;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity; 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.exam.Exam.ExamType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public final class ExamConfigurationMap implements GrantEntity { 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 ATTR_CONFIRM_ENCRYPT_SECRET = "confirm_encrypt_secret";
public static final String FILTER_ATTR_EXAM_ID = "examId"; public static final String FILTER_ATTR_EXAM_ID = "examId";
@ -60,6 +63,9 @@ public final class ExamConfigurationMap implements GrantEntity {
@JsonProperty(EXAM.ATTR_TYPE) @JsonProperty(EXAM.ATTR_TYPE)
public final ExamType examType; public final ExamType examType;
@JsonProperty(ARR_EXAM_STATUS)
public final ExamStatus examStatus;
@NotNull(message = "examConfigurationMap:configurationNodeId:notNull") @NotNull(message = "examConfigurationMap:configurationNodeId:notNull")
@JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID) @JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID)
public final Long configurationNodeId; 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_DESCRIPTION) final String examDescription,
@JsonProperty(QuizData.QUIZ_ATTR_START_TIME) final DateTime examStartTime, @JsonProperty(QuizData.QUIZ_ATTR_START_TIME) final DateTime examStartTime,
@JsonProperty(EXAM.ATTR_TYPE) final ExamType examType, @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_CONFIGURATION_NODE_ID) final Long configurationNodeId,
@JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_USER_NAMES) final String userNames, @JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_USER_NAMES) final String userNames,
@JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret, @JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret,
@ -106,6 +113,7 @@ public final class ExamConfigurationMap implements GrantEntity {
this.examDescription = examDescription; this.examDescription = examDescription;
this.examStartTime = examStartTime; this.examStartTime = examStartTime;
this.examType = examType; this.examType = examType;
this.examStatus = examStatus;
this.configurationNodeId = configurationNodeId; this.configurationNodeId = configurationNodeId;
this.userNames = userNames; this.userNames = userNames;
this.encryptSecret = encryptSecret; this.encryptSecret = encryptSecret;
@ -125,6 +133,7 @@ public final class ExamConfigurationMap implements GrantEntity {
this.examDescription = postParams.getString(QuizData.QUIZ_ATTR_DESCRIPTION); this.examDescription = postParams.getString(QuizData.QUIZ_ATTR_DESCRIPTION);
this.examStartTime = postParams.getDateTime(QuizData.QUIZ_ATTR_START_TIME); this.examStartTime = postParams.getDateTime(QuizData.QUIZ_ATTR_START_TIME);
this.examType = postParams.getEnum(EXAM.ATTR_TYPE, ExamType.class); 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.configurationNodeId = postParams.getLong(Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID);
this.userNames = postParams.getString(Domain.EXAM_CONFIGURATION_MAP.ATTR_USER_NAMES); 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.examDescription = null;
this.examStartTime = null; this.examStartTime = null;
this.examType = null; this.examType = null;
this.examStatus = null;
this.configurationNodeId = configurationNodeId; this.configurationNodeId = configurationNodeId;
this.userNames = userNames; this.userNames = userNames;
this.encryptSecret = null; this.encryptSecret = null;
@ -205,6 +215,10 @@ public final class ExamConfigurationMap implements GrantEntity {
return this.examType; return this.examType;
} }
public ExamStatus getExamStatus() {
return this.examStatus;
}
public Long getConfigurationNodeId() { public Long getConfigurationNodeId() {
return this.configurationNodeId; return this.configurationNodeId;
} }
@ -250,6 +264,7 @@ public final class ExamConfigurationMap implements GrantEntity {
this.examDescription, this.examDescription,
this.examStartTime, this.examStartTime,
this.examType, this.examType,
this.examStatus,
this.configurationNodeId, this.configurationNodeId,
this.userNames, this.userNames,
Constants.EMPTY_NOTE, Constants.EMPTY_NOTE,
@ -296,7 +311,7 @@ public final class ExamConfigurationMap implements GrantEntity {
public static ExamConfigurationMap createNew(final Exam exam) { public static ExamConfigurationMap createNew(final Exam exam) {
return new ExamConfigurationMap( 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); null, null, null, null, null, null, null);
} }

View file

@ -87,7 +87,7 @@ public class SEBExamConfigBatchStateChangePopup extends AbstractBatchActionWizar
FORM_STATUS_TEXT_KEY, FORM_STATUS_TEXT_KEY,
targetStateName, targetStateName,
() -> this.pageService.getResourceService() () -> this.pageService.getResourceService()
.examConfigStatusResources(false)) .examConfigStatusResourcesAll())
.readonly(readonly)); .readonly(readonly));
} }

View file

@ -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.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; 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.ExamConfigurationMap;
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.sebconfig.ConfigKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
@ -173,6 +174,16 @@ public class SEBExamConfigForm implements TemplateComposer {
.call() .call()
.map(names -> names != null && !names.isEmpty()) .map(names -> names != null && !names.isEmpty())
.getOr(Boolean.FALSE); .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 // new PageContext with actual EntityKey
final PageContext formContext = pageContext.withEntityKey(examConfig.getEntityKey()); final PageContext formContext = pageContext.withEntityKey(examConfig.getEntityKey());
@ -223,7 +234,7 @@ public class SEBExamConfigForm implements TemplateComposer {
Domain.CONFIGURATION_NODE.ATTR_STATUS, Domain.CONFIGURATION_NODE.ATTR_STATUS,
FORM_STATUS_TEXT_KEY, FORM_STATUS_TEXT_KEY,
examConfig.status.name(), examConfig.status.name(),
() -> resourceService.examConfigStatusResources(isAttachedToExam)) () -> resourceService.examConfigStatusResources(isAttachedToExam, hasRunningExam))
.withEmptyCellSeparation(!isReadonly)) .withEmptyCellSeparation(!isReadonly))
.buildFor((isNew) .buildFor((isNew)
? this.restService.getRestCall(NewExamConfig.class) ? this.restService.getRestCall(NewExamConfig.class)
@ -297,7 +308,7 @@ public class SEBExamConfigForm implements TemplateComposer {
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(formHandle::processFormSave) .withExec(formHandle::processFormSave)
.ignoreMoveAwayFromEdit() .ignoreMoveAwayFromEdit()
.withConfirm(() -> stateChangeConfirm(isAttachedToExam, formHandle)) .withConfirm(() -> stateChangeConfirm(hasRunningExam, formHandle))
.publishIf(() -> !isReadonly) .publishIf(() -> !isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY) .newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY)
@ -436,17 +447,17 @@ public class SEBExamConfigForm implements TemplateComposer {
} }
private LocTextKey stateChangeConfirm( private LocTextKey stateChangeConfirm(
final boolean isAttachedToExam, final boolean hasRunningExam,
final FormHandle<ConfigurationNode> formHandle) { final FormHandle<ConfigurationNode> formHandle) {
if (isAttachedToExam) { if (hasRunningExam) {
final String fieldValue = formHandle final String fieldValue = formHandle
.getForm() .getForm()
.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS); .getFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS);
if (fieldValue != null) { if (fieldValue != null) {
final ConfigurationStatus state = ConfigurationStatus.valueOf(fieldValue); 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; return SAVE_CONFIRM_STATE_CHANGE_WHILE_ATTACHED;
} }
} }

View file

@ -471,7 +471,20 @@ public class ResourceService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public List<Tuple<String>> examConfigStatusResources(final boolean isAttachedToExam) { public List<Tuple<String>> 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<Tuple<String>> examConfigStatusResources(final boolean isAttachedToExam, final boolean hasRunningExam) {
return Arrays.stream(ConfigurationStatus.values()) return Arrays.stream(ConfigurationStatus.values())
.filter(status -> { .filter(status -> {
if (isAttachedToExam) { if (isAttachedToExam) {
@ -480,6 +493,7 @@ public class ResourceService {
return status != ConfigurationStatus.IN_USE; return status != ConfigurationStatus.IN_USE;
} }
}) })
.filter(status -> !hasRunningExam || status != ConfigurationStatus.ARCHIVED)
.map(type -> new Tuple3<>( .map(type -> new Tuple3<>(
type.name(), type.name(),
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name()), this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name()),

View file

@ -442,6 +442,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
(exam != null) ? exam.description : null, (exam != null) ? exam.description : null,
(exam != null) ? exam.startTime : null, (exam != null) ? exam.startTime : null,
(exam != null) ? exam.type : ExamType.UNDEFINED, (exam != null) ? exam.type : ExamType.UNDEFINED,
(exam != null) ? exam.status : null,
record.getConfigurationNodeId(), record.getConfigurationNodeId(),
record.getUserNames(), record.getUserNames(),
record.getEncryptSecret(), record.getEncryptSecret(),

View file

@ -136,6 +136,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
} }
} }
@Override
public Result<Long> getFollowupConfigurationId(final Long examConfigNodeId) { public Result<Long> getFollowupConfigurationId(final Long examConfigNodeId) {
return this.configurationDAO.getFollowupConfigurationId(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; return configurationNode;
}); });

View file

@ -209,7 +209,7 @@ public class ModelObjectJSONGenerator {
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));
domainObject = new ExamConfigurationMap( 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", 1L, "userNames", "encryptSecret", "confirmEncryptSecret", "configName", "configDescription",
ConfigurationStatus.IN_USE); ConfigurationStatus.IN_USE);
System.out.println(domainObject.getClass().getSimpleName() + ":"); System.out.println(domainObject.getClass().getSimpleName() + ":");