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