SEBSERV-73 Exam Config changes and test fixes
This commit is contained in:
parent
83985cdbf7
commit
fb13c62eeb
19 changed files with 256 additions and 121 deletions
|
@ -9,6 +9,7 @@
|
|||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
|
@ -19,6 +20,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
|||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
|
@ -30,7 +32,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.Co
|
|||
|
||||
public final class SebExamConfigCopy {
|
||||
|
||||
static Function<PageAction, PageAction> importConfigFunction(
|
||||
static Function<PageAction, PageAction> copyConfigFunction(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
|
@ -46,12 +48,14 @@ public final class SebExamConfigCopy {
|
|||
pageService,
|
||||
action.pageContext());
|
||||
|
||||
final Predicate<FormHandle<ConfigCopyInfo>> doCopy = formHandle -> doCopy(
|
||||
pageService,
|
||||
pageContext,
|
||||
formHandle);
|
||||
|
||||
dialog.open(
|
||||
SebExamConfigPropForm.FORM_COPY_TEXT_KEY,
|
||||
formHandle -> doCopy(
|
||||
pageService,
|
||||
pageContext,
|
||||
formHandle),
|
||||
doCopy,
|
||||
Utils.EMPTY_EXECUTION,
|
||||
formContext);
|
||||
|
||||
|
@ -59,7 +63,7 @@ public final class SebExamConfigCopy {
|
|||
};
|
||||
}
|
||||
|
||||
private static final void doCopy(
|
||||
private static final boolean doCopy(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final FormHandle<ConfigCopyInfo> formHandle) {
|
||||
|
@ -67,13 +71,21 @@ public final class SebExamConfigCopy {
|
|||
final ConfigurationNode newConfig = pageService.getRestService().getBuilder(CopyConfiguration.class)
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
.call()
|
||||
.getOrThrow();
|
||||
.onError(formHandle::handleError)
|
||||
.getOr(null);
|
||||
|
||||
final PageAction viewNewConfig = pageService.pageActionBuilder(pageContext.copy().clearAttributes())
|
||||
if (newConfig == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PageAction viewNewConfig = pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
||||
.withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
|
||||
.create();
|
||||
|
||||
pageService.executePageAction(viewNewConfig);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final class CopyFormContext implements ModalInputDialogComposer<FormHandle<ConfigCopyInfo>> {
|
||||
|
@ -103,6 +115,9 @@ public final class SebExamConfigCopy {
|
|||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
|
||||
.asArea())
|
||||
.addField(FormBuilder.checkbox(
|
||||
ConfigCopyInfo.ATTR_COPY_WITH_HISTORY,
|
||||
SebExamConfigPropForm.FORM_HISTORY_TEXT_KEY))
|
||||
.build();
|
||||
|
||||
return () -> formHandle;
|
||||
|
|
|
@ -69,6 +69,8 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.examconfig.form.name");
|
||||
static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.form.description");
|
||||
static final LocTextKey FORM_HISTORY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.form.with-history");
|
||||
static final LocTextKey FORM_TEMPLATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.form.template");
|
||||
static final LocTextKey FORM_STATUS_TEXT_KEY =
|
||||
|
@ -196,7 +198,8 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
|
||||
final boolean settingsReadonly = examConfig.status == ConfigurationStatus.IN_USE;
|
||||
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
|
||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||
final PageContext actionContext = formContext.clearEntityKeys();
|
||||
this.pageService.pageActionBuilder(actionContext)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_NEW)
|
||||
.publishIf(() -> writeGrant && isReadonly)
|
||||
|
@ -237,6 +240,12 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
.noEventPropagation()
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(SebExamConfigCopy.copyConfigFunction(this.pageService, actionContext))
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_SAVE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(formHandle::processFormSave)
|
||||
|
|
|
@ -155,6 +155,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
return action;
|
||||
})
|
||||
.withSuccess(KEY_SAVE_TO_HISTORY_SUCCESS)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> examConfigGrant.iw() && !readonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_UNDO)
|
||||
|
@ -168,6 +169,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
return action;
|
||||
})
|
||||
.withSuccess(KEY_UNDO_SUCCESS)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> examConfigGrant.iw() && !readonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
||||
|
|
|
@ -392,7 +392,6 @@ public enum ActionDefinition {
|
|||
SEB_EXAM_CONFIG_EXPORT_PLAIN_XML(
|
||||
new LocTextKey("sebserver.examconfig.action.export.plainxml"),
|
||||
ImageIcon.EXPORT,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
||||
ActionCategory.FORM),
|
||||
SEB_EXAM_CONFIG_GET_CONFIG_KEY(
|
||||
new LocTextKey("sebserver.examconfig.action.get-config-key"),
|
||||
|
@ -402,12 +401,9 @@ public enum ActionDefinition {
|
|||
new LocTextKey("sebserver.examconfig.action.import-config"),
|
||||
ImageIcon.IMPORT,
|
||||
ActionCategory.FORM),
|
||||
|
||||
// TODO copy config action
|
||||
// TODO
|
||||
SEB_EXAM_CONFIG_COPY_CONFIG(
|
||||
new LocTextKey("sebserver.examconfig.action.copy-config"),
|
||||
ImageIcon.IMPORT,
|
||||
new LocTextKey("sebserver.examconfig.action.copy"),
|
||||
ImageIcon.COPY,
|
||||
ActionCategory.FORM),
|
||||
|
||||
SEB_EXAM_CONFIG_MODIFY_FROM_LIST(
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* 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.form;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
|
||||
public class CheckboxFieldBuilder extends FieldBuilder<String> {
|
||||
|
||||
protected CheckboxFieldBuilder(final String name, final LocTextKey label, final String value) {
|
||||
super(name, label, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void build(final FormBuilder builder) {
|
||||
final boolean readonly = builder.readonly || this.readonly;
|
||||
final Label lab = builder.labelLocalized(
|
||||
builder.formParent,
|
||||
this.label,
|
||||
this.defaultLabel,
|
||||
this.spanLabel);
|
||||
|
||||
final Composite fieldGrid = Form.createFieldGrid(builder.formParent, this.spanInput);
|
||||
final Button checkbox = builder.widgetFactory.buttonLocalized(
|
||||
fieldGrid,
|
||||
SWT.CHECK,
|
||||
null, null);
|
||||
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
|
||||
checkbox.setLayoutData(gridData);
|
||||
checkbox.setSelection(BooleanUtils.toBoolean(this.value));
|
||||
|
||||
if (readonly) {
|
||||
checkbox.setEnabled(false);
|
||||
}
|
||||
|
||||
builder.form.putField(this.name, lab, checkbox);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,12 +18,14 @@ import java.util.function.BiConsumer;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Color;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
|
@ -130,6 +132,11 @@ public final class Form implements FormBinding {
|
|||
return this;
|
||||
}
|
||||
|
||||
Form putField(final String name, final Label label, final Button checkbox) {
|
||||
this.formFields.add(name, createAccessor(label, checkbox, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
void putField(final String name, final Label label, final Selection field, final Label errorLabel) {
|
||||
this.formFields.add(name, createAccessor(label, field, errorLabel));
|
||||
}
|
||||
|
@ -266,6 +273,12 @@ public final class Form implements FormBinding {
|
|||
@Override public void setStringValue(final String value) {text.setText(value);}
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final Button checkbox, final Label errorLabel) {
|
||||
return new FormFieldAccessor(label, checkbox, errorLabel) {
|
||||
@Override public String getStringValue() {return BooleanUtils.toStringTrueFalse(checkbox.getSelection());}
|
||||
@Override public void setStringValue(final String value) {checkbox.setSelection(BooleanUtils.toBoolean(value));}
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final Selection selection, final Label errorLabel) {
|
||||
switch (selection.type()) {
|
||||
case MULTI:
|
||||
|
|
|
@ -194,6 +194,14 @@ public class FormBuilder {
|
|||
empty.setText("");
|
||||
}
|
||||
|
||||
public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label) {
|
||||
return new CheckboxFieldBuilder(name, label, null);
|
||||
}
|
||||
|
||||
public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label, final String value) {
|
||||
return new CheckboxFieldBuilder(name, label, value);
|
||||
}
|
||||
|
||||
public static TextFieldBuilder text(final String name, final LocTextKey label) {
|
||||
return new TextFieldBuilder(name, label, null);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
package ch.ethz.seb.sebserver.gui.service.page.impl;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
|
@ -72,6 +73,20 @@ public class ModalInputDialog<T> extends Dialog {
|
|||
final Runnable cancelCallback,
|
||||
final ModalInputDialogComposer<T> contentComposer) {
|
||||
|
||||
final Predicate<T> predicate = result -> {
|
||||
callback.accept(result);
|
||||
return true;
|
||||
};
|
||||
|
||||
open(title, predicate, cancelCallback, contentComposer);
|
||||
}
|
||||
|
||||
public void open(
|
||||
final LocTextKey title,
|
||||
final Predicate<T> callback,
|
||||
final Runnable cancelCallback,
|
||||
final ModalInputDialogComposer<T> contentComposer) {
|
||||
|
||||
// Create the selection dialog window
|
||||
final Shell shell = new Shell(getParent(), getStyle());
|
||||
shell.setText(getText());
|
||||
|
@ -98,8 +113,9 @@ public class ModalInputDialog<T> extends Dialog {
|
|||
ok.addListener(SWT.Selection, event -> {
|
||||
if (valueSuppier != null) {
|
||||
final T result = valueSuppier.get();
|
||||
callback.accept(result);
|
||||
shell.close();
|
||||
if (callback.test(result)) {
|
||||
shell.close();
|
||||
}
|
||||
} else {
|
||||
shell.close();
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ public class WidgetFactory {
|
|||
EDIT("edit.png"),
|
||||
EDIT_SETTINGS("settings.png"),
|
||||
TEST("test.png"),
|
||||
COPY("copy.png"),
|
||||
IMPORT("import.png"),
|
||||
CANCEL("cancel.png"),
|
||||
CANCEL_EDIT("cancelEdit.png"),
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.function.Function;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.mybatis.dynamic.sql.SqlBuilder;
|
||||
|
@ -32,6 +33,7 @@ import org.springframework.stereotype.Component;
|
|||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
|
@ -55,6 +57,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationNodeR
|
|||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationValueRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
||||
|
||||
/** This service is internally used to implement MyBatis batch functionality for the most
|
||||
* intensive write operation on Configuration domain. */
|
||||
|
@ -317,7 +320,99 @@ class ConfigurationDAOBatchService {
|
|||
.flatMap(ConfigurationDAOImpl::toDomainModel);
|
||||
}
|
||||
|
||||
Result<Configuration> copyConfiguration(
|
||||
Result<ConfigurationNode> createCopy(
|
||||
final Long institutionId,
|
||||
final String newOwner,
|
||||
final ConfigCopyInfo copyInfo) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
final ConfigurationNodeRecord sourceNode = this.batchConfigurationNodeRecordMapper
|
||||
.selectByPrimaryKey(copyInfo.configurationNodeId);
|
||||
|
||||
if (!sourceNode.getInstitutionId().equals(institutionId)) {
|
||||
new IllegalArgumentException("Institution integrity violation");
|
||||
}
|
||||
|
||||
return this.copyNodeRecord(sourceNode, newOwner, copyInfo);
|
||||
})
|
||||
.flatMap(ConfigurationNodeDAOImpl::toDomainModel)
|
||||
.onError(TransactionHandler::rollback);
|
||||
}
|
||||
|
||||
private ConfigurationNodeRecord copyNodeRecord(
|
||||
final ConfigurationNodeRecord nodeRec,
|
||||
final String newOwner,
|
||||
final ConfigCopyInfo copyInfo) {
|
||||
|
||||
final ConfigurationNodeRecord newNodeRec = new ConfigurationNodeRecord(
|
||||
null,
|
||||
nodeRec.getInstitutionId(),
|
||||
nodeRec.getTemplateId(),
|
||||
StringUtils.isNotBlank(newOwner) ? newOwner : nodeRec.getOwner(),
|
||||
copyInfo.getName(),
|
||||
copyInfo.getDescription(),
|
||||
nodeRec.getType(),
|
||||
ConfigurationStatus.CONSTRUCTION.name());
|
||||
this.batchConfigurationNodeRecordMapper.insert(newNodeRec);
|
||||
this.batchSqlSessionTemplate.flushStatements();
|
||||
|
||||
final List<ConfigurationRecord> configs = this.batchConfigurationRecordMapper
|
||||
.selectByExample()
|
||||
.where(
|
||||
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
|
||||
isEqualTo(nodeRec.getId()))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
if (BooleanUtils.toBoolean(copyInfo.withHistory)) {
|
||||
configs
|
||||
.stream()
|
||||
.forEach(configRec -> this.copyConfiguration(
|
||||
configRec.getInstitutionId(),
|
||||
configRec.getId(),
|
||||
newNodeRec.getId()));
|
||||
} else {
|
||||
configs
|
||||
.stream()
|
||||
.filter(configRec -> configRec.getVersionDate() == null)
|
||||
.findFirst()
|
||||
.ifPresent(configRec -> {
|
||||
// No history means to create a first version and a follow-up with the copied values
|
||||
final ConfigurationRecord newFirstVersion = new ConfigurationRecord(
|
||||
null,
|
||||
configRec.getInstitutionId(),
|
||||
newNodeRec.getId(),
|
||||
ConfigurationDAOBatchService.INITIAL_VERSION_NAME,
|
||||
DateTime.now(DateTimeZone.UTC),
|
||||
BooleanUtils.toInteger(false));
|
||||
this.batchConfigurationRecordMapper.insert(newFirstVersion);
|
||||
this.batchSqlSessionTemplate.flushStatements();
|
||||
this.copyValues(
|
||||
configRec.getInstitutionId(),
|
||||
configRec.getId(),
|
||||
newFirstVersion.getId());
|
||||
// and copy the follow-up
|
||||
final ConfigurationRecord followup = new ConfigurationRecord(
|
||||
null,
|
||||
configRec.getInstitutionId(),
|
||||
newNodeRec.getId(),
|
||||
null,
|
||||
null,
|
||||
BooleanUtils.toInteger(true));
|
||||
this.batchConfigurationRecordMapper.insert(followup);
|
||||
this.batchSqlSessionTemplate.flushStatements();
|
||||
this.copyValues(
|
||||
configRec.getInstitutionId(),
|
||||
configRec.getId(),
|
||||
followup.getId());
|
||||
});
|
||||
}
|
||||
|
||||
this.batchSqlSessionTemplate.flushStatements();
|
||||
return newNodeRec;
|
||||
}
|
||||
|
||||
private Result<Configuration> copyConfiguration(
|
||||
final Long institutionId,
|
||||
final Long fromConfigurationId,
|
||||
final Long toConfigurationNodeId) {
|
||||
|
@ -338,6 +433,7 @@ class ConfigurationDAOBatchService {
|
|||
fromRecord.getVersionDate(),
|
||||
fromRecord.getFollowup());
|
||||
this.batchConfigurationRecordMapper.insert(configurationRecord);
|
||||
this.batchSqlSessionTemplate.flushStatements();
|
||||
return configurationRecord;
|
||||
})
|
||||
.flatMap(ConfigurationDAOImpl::toDomainModel)
|
||||
|
@ -347,14 +443,10 @@ class ConfigurationDAOBatchService {
|
|||
fromConfigurationId,
|
||||
newConfig.getId());
|
||||
return newConfig;
|
||||
})
|
||||
.map(config -> {
|
||||
this.batchSqlSessionTemplate.flushStatements();
|
||||
return config;
|
||||
});
|
||||
}
|
||||
|
||||
void copyValues(
|
||||
private void copyValues(
|
||||
final Long institutionId,
|
||||
final Long fromConfigId,
|
||||
final Long toConfigId) {
|
||||
|
|
|
@ -19,12 +19,7 @@ import java.util.function.Function;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.mybatis.dynamic.sql.SqlBuilder;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -46,7 +41,6 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationReco
|
|||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordMapper;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationNodeRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkAction;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
|
||||
|
@ -63,24 +57,18 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
|||
private final ConfigurationNodeRecordMapper configurationNodeRecordMapper;
|
||||
private final ConfigurationValueRecordMapper configurationValueRecordMapper;
|
||||
private final ConfigurationDAOBatchService configurationDAOBatchService;
|
||||
private final String copyNamePrefix;
|
||||
private final String copyNameSuffix;
|
||||
|
||||
protected ConfigurationNodeDAOImpl(
|
||||
final ConfigurationRecordMapper configurationRecordMapper,
|
||||
final ConfigurationNodeRecordMapper configurationNodeRecordMapper,
|
||||
final ConfigurationValueRecordMapper configurationValueRecordMapper,
|
||||
final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper,
|
||||
final ConfigurationDAOBatchService ConfigurationDAOBatchService,
|
||||
@Value("${sebserver.webservice.api.copy-name-prefix:Copy of }") final String copyNamePrefix,
|
||||
@Value("${sebserver.webservice.api.copy-name-suffix:}") final String copyNameSuffix) {
|
||||
final ConfigurationDAOBatchService ConfigurationDAOBatchService) {
|
||||
|
||||
this.configurationRecordMapper = configurationRecordMapper;
|
||||
this.configurationNodeRecordMapper = configurationNodeRecordMapper;
|
||||
this.configurationValueRecordMapper = configurationValueRecordMapper;
|
||||
this.configurationDAOBatchService = ConfigurationDAOBatchService;
|
||||
this.copyNamePrefix = copyNamePrefix;
|
||||
this.copyNameSuffix = copyNameSuffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -211,12 +199,7 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
|||
final String newOwner,
|
||||
final ConfigCopyInfo copyInfo) {
|
||||
|
||||
return this.recordById(copyInfo.configurationNodeId)
|
||||
.flatMap(nodeRec -> (nodeRec.getInstitutionId().equals(institutionId)
|
||||
? Result.of(nodeRec)
|
||||
: Result.ofError(new IllegalArgumentException("Institution integrity violation"))))
|
||||
.map(nodeRec -> this.copyNodeRecord(nodeRec, newOwner, copyInfo))
|
||||
.flatMap(ConfigurationNodeDAOImpl::toDomainModel);
|
||||
return this.configurationDAOBatchService.createCopy(institutionId, newOwner, copyInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -283,68 +266,6 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
|||
});
|
||||
}
|
||||
|
||||
private ConfigurationNodeRecord copyNodeRecord(
|
||||
final ConfigurationNodeRecord nodeRec,
|
||||
final String newOwner,
|
||||
final ConfigCopyInfo copyInfo) {
|
||||
|
||||
final ConfigurationNodeRecord newNodeRec = new ConfigurationNodeRecord(
|
||||
null,
|
||||
nodeRec.getInstitutionId(),
|
||||
nodeRec.getTemplateId(),
|
||||
StringUtils.isNotBlank(newOwner) ? newOwner : nodeRec.getOwner(),
|
||||
this.copyNamePrefix + nodeRec.getName() + this.copyNameSuffix,
|
||||
nodeRec.getDescription(),
|
||||
nodeRec.getType(),
|
||||
ConfigurationStatus.CONSTRUCTION.name());
|
||||
this.configurationNodeRecordMapper.insert(newNodeRec);
|
||||
|
||||
final List<ConfigurationRecord> configs = this.configurationRecordMapper
|
||||
.selectByExample()
|
||||
.where(
|
||||
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
|
||||
isEqualTo(nodeRec.getId()))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
if (BooleanUtils.toBoolean(copyInfo.withHistory)) {
|
||||
configs
|
||||
.stream()
|
||||
.forEach(configRec -> this.configurationDAOBatchService.copyConfiguration(
|
||||
configRec.getInstitutionId(),
|
||||
configRec.getId(),
|
||||
newNodeRec.getId()));
|
||||
} else {
|
||||
configs
|
||||
.stream()
|
||||
.filter(configRec -> configRec.getVersionDate() == null)
|
||||
.findFirst()
|
||||
.map(configRec -> {
|
||||
// No history means to create a first version and a follow-up with the copied values
|
||||
final ConfigurationRecord newFirstVersion = new ConfigurationRecord(
|
||||
null,
|
||||
configRec.getInstitutionId(),
|
||||
configRec.getConfigurationNodeId(),
|
||||
ConfigurationDAOBatchService.INITIAL_VERSION_NAME,
|
||||
DateTime.now(DateTimeZone.UTC),
|
||||
BooleanUtils.toInteger(false));
|
||||
this.configurationRecordMapper.insert(newFirstVersion);
|
||||
this.configurationDAOBatchService.copyValues(
|
||||
configRec.getInstitutionId(),
|
||||
configRec.getId(),
|
||||
newFirstVersion.getId());
|
||||
// and copy the follow-up
|
||||
this.configurationDAOBatchService.copyConfiguration(
|
||||
configRec.getInstitutionId(),
|
||||
configRec.getId(),
|
||||
newNodeRec.getId());
|
||||
return configRec;
|
||||
});
|
||||
}
|
||||
|
||||
return newNodeRec;
|
||||
}
|
||||
|
||||
static Result<ConfigurationNode> toDomainModel(final ConfigurationNodeRecord record) {
|
||||
return Result.tryCatch(() -> new ConfigurationNode(
|
||||
record.getId(),
|
||||
|
|
|
@ -257,6 +257,12 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
|||
.getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<Exam> validForCreate(final Exam entity) {
|
||||
return super.validForCreate(entity)
|
||||
.map(this::checkExamSupporterRole);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<Exam> validForSave(final Exam entity) {
|
||||
return super.validForSave(entity)
|
||||
|
|
|
@ -34,8 +34,6 @@ sebserver.webservice.api.exam.accessTokenValiditySeconds=3600
|
|||
sebserver.webservice.api.exam.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY
|
||||
sebserver.webservice.api.exam.enable-indicator-cache=true
|
||||
sebserver.webservice.api.pagination.maxPageSize=500
|
||||
sebserver.webservice.api.copy-name-prefix=Copy of
|
||||
sebserver.webservice.api.copy-name-suffix=
|
||||
# comma separated list of known possible OpenEdX API access token request endpoints
|
||||
sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
|
||||
sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias
|
||||
|
|
|
@ -495,14 +495,9 @@ INSERT IGNORE INTO orientation VALUES
|
|||
(520, 520, 0, 11, 'functionKeys', 3, 12, 3, 1, 'NONE')
|
||||
|
||||
;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
INSERT IGNORE INTO configuration_node VALUES
|
||||
(1, 1, 0, 'super-admin', 'test', null, 'EXAM_CONFIG', 'READY_TO_USE')
|
||||
(1, 1, 0, 'super-admin', 'test', null, 'EXAM_CONFIG', 'IN_USE')
|
||||
;
|
||||
|
||||
INSERT IGNORE INTO configuration VALUES
|
||||
|
|
|
@ -450,6 +450,7 @@ sebserver.examconfig.action.saveToHistory=Save / Publish
|
|||
sebserver.examconfig.action.saveToHistory.success=Successfully saved in history
|
||||
sebserver.examconfig.action.undo=Undo
|
||||
sebserver.examconfig.action.undo.success=Successfully reverted to last saved state
|
||||
sebserver.examconfig.action.copy=Copy Configuration
|
||||
sebserver.examconfig.action.export.plainxml=Export Configuration
|
||||
sebserver.examconfig.action.get-config-key=Export Config-Key
|
||||
sebserver.examconfig.action.import-config=Import Configuration
|
||||
|
@ -462,6 +463,7 @@ sebserver.examconfig.form.title.new=Add Exam Configuration
|
|||
sebserver.examconfig.form.title=Exam Configuration
|
||||
sebserver.examconfig.form.name=Name
|
||||
sebserver.examconfig.form.description=Description
|
||||
sebserver.examconfig.form.with-history=With History
|
||||
sebserver.examconfig.form.template=From Template
|
||||
sebserver.examconfig.form.status=Status
|
||||
sebserver.examconfig.form.config-key.title=Config Key
|
||||
|
|
BIN
src/main/resources/static/images/copy.png
Normal file
BIN
src/main/resources/static/images/copy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 165 B |
|
@ -679,6 +679,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
.getBuilder(ImportAsExam.class)
|
||||
.withFormParam(QuizData.QUIZ_ATTR_LMS_SETUP_ID, String.valueOf(quizData.lmsSetupId))
|
||||
.withFormParam(QuizData.QUIZ_ATTR_ID, quizData.id)
|
||||
.withFormParam(Domain.EXAM.ATTR_SUPPORTER, userId)
|
||||
.call();
|
||||
|
||||
assertNotNull(newExamResult);
|
||||
|
@ -687,7 +688,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
|
||||
assertEquals("Demo Quiz 1", newExam.name);
|
||||
assertEquals(ExamType.UNDEFINED, newExam.type);
|
||||
assertTrue(newExam.supporter.isEmpty());
|
||||
assertFalse(newExam.supporter.isEmpty());
|
||||
|
||||
// create Exam with type and supporter examSupport2
|
||||
final Exam examForSave = new Exam(
|
||||
|
|
|
@ -37,12 +37,13 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
|
|||
sebAdminAccess,
|
||||
"LmsSetupMock",
|
||||
"quiz2",
|
||||
ExamType.MANAGED);
|
||||
ExamType.MANAGED,
|
||||
"user5");
|
||||
|
||||
assertNotNull(exam);
|
||||
assertEquals("quiz2", exam.getExternalId());
|
||||
assertEquals(ExamType.MANAGED, exam.getType());
|
||||
assertTrue(exam.getSupporter().isEmpty());
|
||||
assertFalse(exam.getSupporter().isEmpty());
|
||||
|
||||
// add ExamSupporter
|
||||
final Exam newExam = new RestAPITestHelper()
|
||||
|
|
|
@ -43,6 +43,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
.withMethod(HttpMethod.POST)
|
||||
.withAttribute(QuizData.QUIZ_ATTR_LMS_SETUP_ID, lmsSetup1.getModelId())
|
||||
.withAttribute(QuizData.QUIZ_ATTR_ID, "quiz1")
|
||||
.withAttribute(Domain.EXAM.ATTR_SUPPORTER, "user1")
|
||||
.withExpectedStatus(HttpStatus.OK)
|
||||
.getAsObject(new TypeReference<Exam>() {
|
||||
});
|
||||
|
@ -58,7 +59,8 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
getSebAdminAccess(),
|
||||
"LmsSetupMock",
|
||||
"quiz2",
|
||||
ExamType.MANAGED);
|
||||
ExamType.MANAGED,
|
||||
"user5");
|
||||
|
||||
assertNotNull(exam2);
|
||||
assertEquals("quiz2", exam2.getExternalId());
|
||||
|
@ -76,7 +78,8 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
getAdminInstitution2Access(),
|
||||
"LmsSetupMock",
|
||||
"quiz2",
|
||||
ExamType.MANAGED);
|
||||
ExamType.MANAGED,
|
||||
"user7");
|
||||
fail("AssertionError expected here");
|
||||
} catch (final AssertionError ae) {
|
||||
assertEquals("Response status expected:<200> but was:<403>", ae.getMessage());
|
||||
|
@ -89,7 +92,8 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
getExamAdmin1(), // this exam administrator is on Institution 2
|
||||
"LmsSetupMock2",
|
||||
"quiz2",
|
||||
ExamType.MANAGED);
|
||||
ExamType.MANAGED,
|
||||
"user7");
|
||||
|
||||
assertNotNull(exam2);
|
||||
assertEquals("quiz2", exam2.getExternalId());
|
||||
|
@ -106,7 +110,8 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
getExamAdmin1(), // this exam administrator is on Institution 2
|
||||
"LmsSetupMock",
|
||||
"quiz2",
|
||||
ExamType.MANAGED);
|
||||
ExamType.MANAGED,
|
||||
"user7");
|
||||
fail("AssertionError expected here");
|
||||
} catch (final AssertionError ae) {
|
||||
assertEquals("Response status expected:<200> but was:<403>", ae.getMessage());
|
||||
|
@ -119,7 +124,8 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
final String tokenForExamImport,
|
||||
final String lmsSetupName,
|
||||
final String importQuizName,
|
||||
final ExamType examType) throws Exception {
|
||||
final ExamType examType,
|
||||
final String supporter) throws Exception {
|
||||
|
||||
// create new active LmsSetup Mock with seb-admin
|
||||
final LmsSetup lmsSetup1 = QuizDataTest.createLmsSetupMock(
|
||||
|
@ -135,6 +141,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
|||
.withMethod(HttpMethod.POST)
|
||||
.withAttribute(QuizData.QUIZ_ATTR_LMS_SETUP_ID, lmsSetup1.getModelId())
|
||||
.withAttribute(QuizData.QUIZ_ATTR_ID, importQuizName)
|
||||
.withAttribute(Domain.EXAM.ATTR_SUPPORTER, supporter)
|
||||
.withAttribute(Domain.EXAM.ATTR_TYPE, examType.name())
|
||||
.withExpectedStatus(HttpStatus.OK)
|
||||
.getAsObject(new TypeReference<Exam>() {
|
||||
|
|
Loading…
Add table
Reference in a new issue