SEBSERV-73 Exam Config changes and test fixes

This commit is contained in:
anhefti 2019-10-24 12:00:56 +02:00
parent 83985cdbf7
commit fb13c62eeb
19 changed files with 256 additions and 121 deletions

View file

@ -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;

View file

@ -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)

View file

@ -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)

View file

@ -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(

View file

@ -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);
}
}

View file

@ -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:

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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"),

View file

@ -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) {

View file

@ -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(),

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

View file

@ -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(

View file

@ -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()

View file

@ -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>() {