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; | package ch.ethz.seb.sebserver.gui.content; | ||||||
| 
 | 
 | ||||||
| import java.util.function.Function; | import java.util.function.Function; | ||||||
|  | import java.util.function.Predicate; | ||||||
| import java.util.function.Supplier; | import java.util.function.Supplier; | ||||||
| 
 | 
 | ||||||
| import org.eclipse.swt.widgets.Composite; | 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.ConfigCopyInfo; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; | import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | 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.FormBuilder; | ||||||
| import ch.ethz.seb.sebserver.gui.form.FormHandle; | import ch.ethz.seb.sebserver.gui.form.FormHandle; | ||||||
| import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer; | 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 { | public final class SebExamConfigCopy { | ||||||
| 
 | 
 | ||||||
|     static Function<PageAction, PageAction> importConfigFunction( |     static Function<PageAction, PageAction> copyConfigFunction( | ||||||
|             final PageService pageService, |             final PageService pageService, | ||||||
|             final PageContext pageContext) { |             final PageContext pageContext) { | ||||||
| 
 | 
 | ||||||
|  | @ -46,12 +48,14 @@ public final class SebExamConfigCopy { | ||||||
|                     pageService, |                     pageService, | ||||||
|                     action.pageContext()); |                     action.pageContext()); | ||||||
| 
 | 
 | ||||||
|             dialog.open( |             final Predicate<FormHandle<ConfigCopyInfo>> doCopy = formHandle -> doCopy( | ||||||
|                     SebExamConfigPropForm.FORM_COPY_TEXT_KEY, |  | ||||||
|                     formHandle -> doCopy( |  | ||||||
|                     pageService, |                     pageService, | ||||||
|                     pageContext, |                     pageContext, | ||||||
|                             formHandle), |                     formHandle); | ||||||
|  | 
 | ||||||
|  |             dialog.open( | ||||||
|  |                     SebExamConfigPropForm.FORM_COPY_TEXT_KEY, | ||||||
|  |                     doCopy, | ||||||
|                     Utils.EMPTY_EXECUTION, |                     Utils.EMPTY_EXECUTION, | ||||||
|                     formContext); |                     formContext); | ||||||
| 
 | 
 | ||||||
|  | @ -59,7 +63,7 @@ public final class SebExamConfigCopy { | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static final void doCopy( |     private static final boolean doCopy( | ||||||
|             final PageService pageService, |             final PageService pageService, | ||||||
|             final PageContext pageContext, |             final PageContext pageContext, | ||||||
|             final FormHandle<ConfigCopyInfo> formHandle) { |             final FormHandle<ConfigCopyInfo> formHandle) { | ||||||
|  | @ -67,13 +71,21 @@ public final class SebExamConfigCopy { | ||||||
|         final ConfigurationNode newConfig = pageService.getRestService().getBuilder(CopyConfiguration.class) |         final ConfigurationNode newConfig = pageService.getRestService().getBuilder(CopyConfiguration.class) | ||||||
|                 .withFormBinding(formHandle.getFormBinding()) |                 .withFormBinding(formHandle.getFormBinding()) | ||||||
|                 .call() |                 .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)) |                 .withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE)) | ||||||
|                 .create(); |                 .create(); | ||||||
| 
 | 
 | ||||||
|         pageService.executePageAction(viewNewConfig); |         pageService.executePageAction(viewNewConfig); | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static final class CopyFormContext implements ModalInputDialogComposer<FormHandle<ConfigCopyInfo>> { |     private static final class CopyFormContext implements ModalInputDialogComposer<FormHandle<ConfigCopyInfo>> { | ||||||
|  | @ -103,6 +115,9 @@ public final class SebExamConfigCopy { | ||||||
|                             Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, |                             Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, | ||||||
|                             SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY) |                             SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY) | ||||||
|                             .asArea()) |                             .asArea()) | ||||||
|  |                     .addField(FormBuilder.checkbox( | ||||||
|  |                             ConfigCopyInfo.ATTR_COPY_WITH_HISTORY, | ||||||
|  |                             SebExamConfigPropForm.FORM_HISTORY_TEXT_KEY)) | ||||||
|                     .build(); |                     .build(); | ||||||
| 
 | 
 | ||||||
|             return () -> formHandle; |             return () -> formHandle; | ||||||
|  |  | ||||||
|  | @ -69,6 +69,8 @@ public class SebExamConfigPropForm implements TemplateComposer { | ||||||
|             new LocTextKey("sebserver.examconfig.form.name"); |             new LocTextKey("sebserver.examconfig.form.name"); | ||||||
|     static final LocTextKey FORM_DESCRIPTION_TEXT_KEY = |     static final LocTextKey FORM_DESCRIPTION_TEXT_KEY = | ||||||
|             new LocTextKey("sebserver.examconfig.form.description"); |             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 = |     static final LocTextKey FORM_TEMPLATE_TEXT_KEY = | ||||||
|             new LocTextKey("sebserver.examconfig.form.template"); |             new LocTextKey("sebserver.examconfig.form.template"); | ||||||
|     static final LocTextKey FORM_STATUS_TEXT_KEY = |     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 boolean settingsReadonly = examConfig.status == ConfigurationStatus.IN_USE; | ||||||
|         final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class); |         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) |                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_NEW) | ||||||
|                 .publishIf(() -> writeGrant && isReadonly) |                 .publishIf(() -> writeGrant && isReadonly) | ||||||
|  | @ -237,6 +240,12 @@ public class SebExamConfigPropForm implements TemplateComposer { | ||||||
|                 .noEventPropagation() |                 .noEventPropagation() | ||||||
|                 .publishIf(() -> modifyGrant && isReadonly) |                 .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) |                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_SAVE) | ||||||
|                 .withEntityKey(entityKey) |                 .withEntityKey(entityKey) | ||||||
|                 .withExec(formHandle::processFormSave) |                 .withExec(formHandle::processFormSave) | ||||||
|  |  | ||||||
|  | @ -155,6 +155,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer { | ||||||
|                         return action; |                         return action; | ||||||
|                     }) |                     }) | ||||||
|                     .withSuccess(KEY_SAVE_TO_HISTORY_SUCCESS) |                     .withSuccess(KEY_SAVE_TO_HISTORY_SUCCESS) | ||||||
|  |                     .ignoreMoveAwayFromEdit() | ||||||
|                     .publishIf(() -> examConfigGrant.iw() && !readonly) |                     .publishIf(() -> examConfigGrant.iw() && !readonly) | ||||||
| 
 | 
 | ||||||
|                     .newAction(ActionDefinition.SEB_EXAM_CONFIG_UNDO) |                     .newAction(ActionDefinition.SEB_EXAM_CONFIG_UNDO) | ||||||
|  | @ -168,6 +169,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer { | ||||||
|                         return action; |                         return action; | ||||||
|                     }) |                     }) | ||||||
|                     .withSuccess(KEY_UNDO_SUCCESS) |                     .withSuccess(KEY_UNDO_SUCCESS) | ||||||
|  |                     .ignoreMoveAwayFromEdit() | ||||||
|                     .publishIf(() -> examConfigGrant.iw() && !readonly) |                     .publishIf(() -> examConfigGrant.iw() && !readonly) | ||||||
| 
 | 
 | ||||||
|                     .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP) |                     .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP) | ||||||
|  |  | ||||||
|  | @ -392,7 +392,6 @@ public enum ActionDefinition { | ||||||
|     SEB_EXAM_CONFIG_EXPORT_PLAIN_XML( |     SEB_EXAM_CONFIG_EXPORT_PLAIN_XML( | ||||||
|             new LocTextKey("sebserver.examconfig.action.export.plainxml"), |             new LocTextKey("sebserver.examconfig.action.export.plainxml"), | ||||||
|             ImageIcon.EXPORT, |             ImageIcon.EXPORT, | ||||||
|             PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW, |  | ||||||
|             ActionCategory.FORM), |             ActionCategory.FORM), | ||||||
|     SEB_EXAM_CONFIG_GET_CONFIG_KEY( |     SEB_EXAM_CONFIG_GET_CONFIG_KEY( | ||||||
|             new LocTextKey("sebserver.examconfig.action.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"), |             new LocTextKey("sebserver.examconfig.action.import-config"), | ||||||
|             ImageIcon.IMPORT, |             ImageIcon.IMPORT, | ||||||
|             ActionCategory.FORM), |             ActionCategory.FORM), | ||||||
| 
 |  | ||||||
|     // TODO copy config action |  | ||||||
|     // TODO |  | ||||||
|     SEB_EXAM_CONFIG_COPY_CONFIG( |     SEB_EXAM_CONFIG_COPY_CONFIG( | ||||||
|             new LocTextKey("sebserver.examconfig.action.copy-config"), |             new LocTextKey("sebserver.examconfig.action.copy"), | ||||||
|             ImageIcon.IMPORT, |             ImageIcon.COPY, | ||||||
|             ActionCategory.FORM), |             ActionCategory.FORM), | ||||||
| 
 | 
 | ||||||
|     SEB_EXAM_CONFIG_MODIFY_FROM_LIST( |     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.Consumer; | ||||||
| import java.util.function.Predicate; | import java.util.function.Predicate; | ||||||
| 
 | 
 | ||||||
|  | import org.apache.commons.lang3.BooleanUtils; | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
| import org.eclipse.rap.rwt.RWT; | import org.eclipse.rap.rwt.RWT; | ||||||
| import org.eclipse.swt.SWT; | import org.eclipse.swt.SWT; | ||||||
| import org.eclipse.swt.graphics.Color; | import org.eclipse.swt.graphics.Color; | ||||||
| import org.eclipse.swt.layout.GridData; | import org.eclipse.swt.layout.GridData; | ||||||
| import org.eclipse.swt.layout.GridLayout; | import org.eclipse.swt.layout.GridLayout; | ||||||
|  | import org.eclipse.swt.widgets.Button; | ||||||
| import org.eclipse.swt.widgets.Composite; | import org.eclipse.swt.widgets.Composite; | ||||||
| import org.eclipse.swt.widgets.Control; | import org.eclipse.swt.widgets.Control; | ||||||
| import org.eclipse.swt.widgets.Label; | import org.eclipse.swt.widgets.Label; | ||||||
|  | @ -130,6 +132,11 @@ public final class Form implements FormBinding { | ||||||
|         return this; |         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) { |     void putField(final String name, final Label label, final Selection field, final Label errorLabel) { | ||||||
|         this.formFields.add(name, createAccessor(label, field, 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);} |             @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) { |     private FormFieldAccessor createAccessor(final Label label, final Selection selection, final Label errorLabel) { | ||||||
|         switch (selection.type()) { |         switch (selection.type()) { | ||||||
|             case MULTI: |             case MULTI: | ||||||
|  |  | ||||||
|  | @ -194,6 +194,14 @@ public class FormBuilder { | ||||||
|         empty.setText(""); |         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) { |     public static TextFieldBuilder text(final String name, final LocTextKey label) { | ||||||
|         return new TextFieldBuilder(name, label, null); |         return new TextFieldBuilder(name, label, null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
| package ch.ethz.seb.sebserver.gui.service.page.impl; | package ch.ethz.seb.sebserver.gui.service.page.impl; | ||||||
| 
 | 
 | ||||||
| import java.util.function.Consumer; | import java.util.function.Consumer; | ||||||
|  | import java.util.function.Predicate; | ||||||
| import java.util.function.Supplier; | import java.util.function.Supplier; | ||||||
| 
 | 
 | ||||||
| import org.eclipse.rap.rwt.RWT; | import org.eclipse.rap.rwt.RWT; | ||||||
|  | @ -72,6 +73,20 @@ public class ModalInputDialog<T> extends Dialog { | ||||||
|             final Runnable cancelCallback, |             final Runnable cancelCallback, | ||||||
|             final ModalInputDialogComposer<T> contentComposer) { |             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 |         // Create the selection dialog window | ||||||
|         final Shell shell = new Shell(getParent(), getStyle()); |         final Shell shell = new Shell(getParent(), getStyle()); | ||||||
|         shell.setText(getText()); |         shell.setText(getText()); | ||||||
|  | @ -98,8 +113,9 @@ public class ModalInputDialog<T> extends Dialog { | ||||||
|         ok.addListener(SWT.Selection, event -> { |         ok.addListener(SWT.Selection, event -> { | ||||||
|             if (valueSuppier != null) { |             if (valueSuppier != null) { | ||||||
|                 final T result = valueSuppier.get(); |                 final T result = valueSuppier.get(); | ||||||
|                 callback.accept(result); |                 if (callback.test(result)) { | ||||||
|                     shell.close(); |                     shell.close(); | ||||||
|  |                 } | ||||||
|             } else { |             } else { | ||||||
|                 shell.close(); |                 shell.close(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -71,6 +71,7 @@ public class WidgetFactory { | ||||||
|         EDIT("edit.png"), |         EDIT("edit.png"), | ||||||
|         EDIT_SETTINGS("settings.png"), |         EDIT_SETTINGS("settings.png"), | ||||||
|         TEST("test.png"), |         TEST("test.png"), | ||||||
|  |         COPY("copy.png"), | ||||||
|         IMPORT("import.png"), |         IMPORT("import.png"), | ||||||
|         CANCEL("cancel.png"), |         CANCEL("cancel.png"), | ||||||
|         CANCEL_EDIT("cancelEdit.png"), |         CANCEL_EDIT("cancelEdit.png"), | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ import java.util.function.Function; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
| import org.apache.commons.lang3.BooleanUtils; | import org.apache.commons.lang3.BooleanUtils; | ||||||
|  | import org.apache.commons.lang3.StringUtils; | ||||||
| import org.joda.time.DateTime; | import org.joda.time.DateTime; | ||||||
| import org.joda.time.DateTimeZone; | import org.joda.time.DateTimeZone; | ||||||
| import org.mybatis.dynamic.sql.SqlBuilder; | 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.APIMessage.FieldValidationException; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.EntityType; | 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.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.Configuration; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; | import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; | 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.ConfigurationRecord; | ||||||
| import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationValueRecord; | 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.ResourceNotFoundException; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; | ||||||
| 
 | 
 | ||||||
| /** This service is internally used to implement MyBatis batch functionality for the most | /** This service is internally used to implement MyBatis batch functionality for the most | ||||||
|  * intensive write operation on Configuration domain. */ |  * intensive write operation on Configuration domain. */ | ||||||
|  | @ -317,7 +320,99 @@ class ConfigurationDAOBatchService { | ||||||
|                 .flatMap(ConfigurationDAOImpl::toDomainModel); |                 .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 institutionId, | ||||||
|             final Long fromConfigurationId, |             final Long fromConfigurationId, | ||||||
|             final Long toConfigurationNodeId) { |             final Long toConfigurationNodeId) { | ||||||
|  | @ -338,6 +433,7 @@ class ConfigurationDAOBatchService { | ||||||
|                     fromRecord.getVersionDate(), |                     fromRecord.getVersionDate(), | ||||||
|                     fromRecord.getFollowup()); |                     fromRecord.getFollowup()); | ||||||
|             this.batchConfigurationRecordMapper.insert(configurationRecord); |             this.batchConfigurationRecordMapper.insert(configurationRecord); | ||||||
|  |             this.batchSqlSessionTemplate.flushStatements(); | ||||||
|             return configurationRecord; |             return configurationRecord; | ||||||
|         }) |         }) | ||||||
|                 .flatMap(ConfigurationDAOImpl::toDomainModel) |                 .flatMap(ConfigurationDAOImpl::toDomainModel) | ||||||
|  | @ -347,14 +443,10 @@ class ConfigurationDAOBatchService { | ||||||
|                             fromConfigurationId, |                             fromConfigurationId, | ||||||
|                             newConfig.getId()); |                             newConfig.getId()); | ||||||
|                     return newConfig; |                     return newConfig; | ||||||
|                 }) |  | ||||||
|                 .map(config -> { |  | ||||||
|                     this.batchSqlSessionTemplate.flushStatements(); |  | ||||||
|                     return config; |  | ||||||
|                 }); |                 }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void copyValues( |     private void copyValues( | ||||||
|             final Long institutionId, |             final Long institutionId, | ||||||
|             final Long fromConfigId, |             final Long fromConfigId, | ||||||
|             final Long toConfigId) { |             final Long toConfigId) { | ||||||
|  |  | ||||||
|  | @ -19,12 +19,7 @@ import java.util.function.Function; | ||||||
| import java.util.function.Predicate; | import java.util.function.Predicate; | ||||||
| import java.util.stream.Collectors; | 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.mybatis.dynamic.sql.SqlBuilder; | ||||||
| import org.springframework.beans.factory.annotation.Value; |  | ||||||
| import org.springframework.context.annotation.Lazy; | import org.springframework.context.annotation.Lazy; | ||||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||||
| import org.springframework.transaction.annotation.Transactional; | 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.ConfigurationValueRecordDynamicSqlSupport; | ||||||
| import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordMapper; | 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.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.bulkaction.BulkAction; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport; | ||||||
|  | @ -63,24 +57,18 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO { | ||||||
|     private final ConfigurationNodeRecordMapper configurationNodeRecordMapper; |     private final ConfigurationNodeRecordMapper configurationNodeRecordMapper; | ||||||
|     private final ConfigurationValueRecordMapper configurationValueRecordMapper; |     private final ConfigurationValueRecordMapper configurationValueRecordMapper; | ||||||
|     private final ConfigurationDAOBatchService configurationDAOBatchService; |     private final ConfigurationDAOBatchService configurationDAOBatchService; | ||||||
|     private final String copyNamePrefix; |  | ||||||
|     private final String copyNameSuffix; |  | ||||||
| 
 | 
 | ||||||
|     protected ConfigurationNodeDAOImpl( |     protected ConfigurationNodeDAOImpl( | ||||||
|             final ConfigurationRecordMapper configurationRecordMapper, |             final ConfigurationRecordMapper configurationRecordMapper, | ||||||
|             final ConfigurationNodeRecordMapper configurationNodeRecordMapper, |             final ConfigurationNodeRecordMapper configurationNodeRecordMapper, | ||||||
|             final ConfigurationValueRecordMapper configurationValueRecordMapper, |             final ConfigurationValueRecordMapper configurationValueRecordMapper, | ||||||
|             final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper, |             final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper, | ||||||
|             final ConfigurationDAOBatchService ConfigurationDAOBatchService, |             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) { |  | ||||||
| 
 | 
 | ||||||
|         this.configurationRecordMapper = configurationRecordMapper; |         this.configurationRecordMapper = configurationRecordMapper; | ||||||
|         this.configurationNodeRecordMapper = configurationNodeRecordMapper; |         this.configurationNodeRecordMapper = configurationNodeRecordMapper; | ||||||
|         this.configurationValueRecordMapper = configurationValueRecordMapper; |         this.configurationValueRecordMapper = configurationValueRecordMapper; | ||||||
|         this.configurationDAOBatchService = ConfigurationDAOBatchService; |         this.configurationDAOBatchService = ConfigurationDAOBatchService; | ||||||
|         this.copyNamePrefix = copyNamePrefix; |  | ||||||
|         this.copyNameSuffix = copyNameSuffix; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | @ -211,12 +199,7 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO { | ||||||
|             final String newOwner, |             final String newOwner, | ||||||
|             final ConfigCopyInfo copyInfo) { |             final ConfigCopyInfo copyInfo) { | ||||||
| 
 | 
 | ||||||
|         return this.recordById(copyInfo.configurationNodeId) |         return this.configurationDAOBatchService.createCopy(institutionId, newOwner, copyInfo); | ||||||
|                 .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); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @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) { |     static Result<ConfigurationNode> toDomainModel(final ConfigurationNodeRecord record) { | ||||||
|         return Result.tryCatch(() -> new ConfigurationNode( |         return Result.tryCatch(() -> new ConfigurationNode( | ||||||
|                 record.getId(), |                 record.getId(), | ||||||
|  |  | ||||||
|  | @ -257,6 +257,12 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex | ||||||
|                 .getOrThrow(); |                 .getOrThrow(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Result<Exam> validForCreate(final Exam entity) { | ||||||
|  |         return super.validForCreate(entity) | ||||||
|  |                 .map(this::checkExamSupporterRole); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected Result<Exam> validForSave(final Exam entity) { |     protected Result<Exam> validForSave(final Exam entity) { | ||||||
|         return super.validForSave(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.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY | ||||||
| sebserver.webservice.api.exam.enable-indicator-cache=true | sebserver.webservice.api.exam.enable-indicator-cache=true | ||||||
| sebserver.webservice.api.pagination.maxPageSize=500 | 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 | # 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.openedx.api.token.request.paths=/oauth2/access_token | ||||||
| sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias | sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias | ||||||
|  |  | ||||||
|  | @ -496,13 +496,8 @@ INSERT IGNORE INTO orientation VALUES | ||||||
|      |      | ||||||
|     ; |     ; | ||||||
| 
 | 
 | ||||||
|      |  | ||||||
|      |  | ||||||
|      |  | ||||||
|      |  | ||||||
|      |  | ||||||
| INSERT IGNORE INTO configuration_node VALUES  | 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  | 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.saveToHistory.success=Successfully saved in history | ||||||
| sebserver.examconfig.action.undo=Undo | sebserver.examconfig.action.undo=Undo | ||||||
| sebserver.examconfig.action.undo.success=Successfully reverted to last saved state | 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.export.plainxml=Export Configuration | ||||||
| sebserver.examconfig.action.get-config-key=Export Config-Key | sebserver.examconfig.action.get-config-key=Export Config-Key | ||||||
| sebserver.examconfig.action.import-config=Import Configuration | 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.title=Exam Configuration | ||||||
| sebserver.examconfig.form.name=Name | sebserver.examconfig.form.name=Name | ||||||
| sebserver.examconfig.form.description=Description | sebserver.examconfig.form.description=Description | ||||||
|  | sebserver.examconfig.form.with-history=With History | ||||||
| sebserver.examconfig.form.template=From Template | sebserver.examconfig.form.template=From Template | ||||||
| sebserver.examconfig.form.status=Status | sebserver.examconfig.form.status=Status | ||||||
| sebserver.examconfig.form.config-key.title=Config Key | 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) |                 .getBuilder(ImportAsExam.class) | ||||||
|                 .withFormParam(QuizData.QUIZ_ATTR_LMS_SETUP_ID, String.valueOf(quizData.lmsSetupId)) |                 .withFormParam(QuizData.QUIZ_ATTR_LMS_SETUP_ID, String.valueOf(quizData.lmsSetupId)) | ||||||
|                 .withFormParam(QuizData.QUIZ_ATTR_ID, quizData.id) |                 .withFormParam(QuizData.QUIZ_ATTR_ID, quizData.id) | ||||||
|  |                 .withFormParam(Domain.EXAM.ATTR_SUPPORTER, userId) | ||||||
|                 .call(); |                 .call(); | ||||||
| 
 | 
 | ||||||
|         assertNotNull(newExamResult); |         assertNotNull(newExamResult); | ||||||
|  | @ -687,7 +688,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { | ||||||
| 
 | 
 | ||||||
|         assertEquals("Demo Quiz 1", newExam.name); |         assertEquals("Demo Quiz 1", newExam.name); | ||||||
|         assertEquals(ExamType.UNDEFINED, newExam.type); |         assertEquals(ExamType.UNDEFINED, newExam.type); | ||||||
|         assertTrue(newExam.supporter.isEmpty()); |         assertFalse(newExam.supporter.isEmpty()); | ||||||
| 
 | 
 | ||||||
|         // create Exam with type and supporter examSupport2 |         // create Exam with type and supporter examSupport2 | ||||||
|         final Exam examForSave = new Exam( |         final Exam examForSave = new Exam( | ||||||
|  |  | ||||||
|  | @ -37,12 +37,13 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester { | ||||||
|                 sebAdminAccess, |                 sebAdminAccess, | ||||||
|                 "LmsSetupMock", |                 "LmsSetupMock", | ||||||
|                 "quiz2", |                 "quiz2", | ||||||
|                 ExamType.MANAGED); |                 ExamType.MANAGED, | ||||||
|  |                 "user5"); | ||||||
| 
 | 
 | ||||||
|         assertNotNull(exam); |         assertNotNull(exam); | ||||||
|         assertEquals("quiz2", exam.getExternalId()); |         assertEquals("quiz2", exam.getExternalId()); | ||||||
|         assertEquals(ExamType.MANAGED, exam.getType()); |         assertEquals(ExamType.MANAGED, exam.getType()); | ||||||
|         assertTrue(exam.getSupporter().isEmpty()); |         assertFalse(exam.getSupporter().isEmpty()); | ||||||
| 
 | 
 | ||||||
|         // add ExamSupporter |         // add ExamSupporter | ||||||
|         final Exam newExam = new RestAPITestHelper() |         final Exam newExam = new RestAPITestHelper() | ||||||
|  |  | ||||||
|  | @ -43,6 +43,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester { | ||||||
|                 .withMethod(HttpMethod.POST) |                 .withMethod(HttpMethod.POST) | ||||||
|                 .withAttribute(QuizData.QUIZ_ATTR_LMS_SETUP_ID, lmsSetup1.getModelId()) |                 .withAttribute(QuizData.QUIZ_ATTR_LMS_SETUP_ID, lmsSetup1.getModelId()) | ||||||
|                 .withAttribute(QuizData.QUIZ_ATTR_ID, "quiz1") |                 .withAttribute(QuizData.QUIZ_ATTR_ID, "quiz1") | ||||||
|  |                 .withAttribute(Domain.EXAM.ATTR_SUPPORTER, "user1") | ||||||
|                 .withExpectedStatus(HttpStatus.OK) |                 .withExpectedStatus(HttpStatus.OK) | ||||||
|                 .getAsObject(new TypeReference<Exam>() { |                 .getAsObject(new TypeReference<Exam>() { | ||||||
|                 }); |                 }); | ||||||
|  | @ -58,7 +59,8 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester { | ||||||
|                 getSebAdminAccess(), |                 getSebAdminAccess(), | ||||||
|                 "LmsSetupMock", |                 "LmsSetupMock", | ||||||
|                 "quiz2", |                 "quiz2", | ||||||
|                 ExamType.MANAGED); |                 ExamType.MANAGED, | ||||||
|  |                 "user5"); | ||||||
| 
 | 
 | ||||||
|         assertNotNull(exam2); |         assertNotNull(exam2); | ||||||
|         assertEquals("quiz2", exam2.getExternalId()); |         assertEquals("quiz2", exam2.getExternalId()); | ||||||
|  | @ -76,7 +78,8 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester { | ||||||
|                     getAdminInstitution2Access(), |                     getAdminInstitution2Access(), | ||||||
|                     "LmsSetupMock", |                     "LmsSetupMock", | ||||||
|                     "quiz2", |                     "quiz2", | ||||||
|                     ExamType.MANAGED); |                     ExamType.MANAGED, | ||||||
|  |                     "user7"); | ||||||
|             fail("AssertionError expected here"); |             fail("AssertionError expected here"); | ||||||
|         } catch (final AssertionError ae) { |         } catch (final AssertionError ae) { | ||||||
|             assertEquals("Response status expected:<200> but was:<403>", ae.getMessage()); |             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 |                 getExamAdmin1(), // this exam administrator is on Institution 2 | ||||||
|                 "LmsSetupMock2", |                 "LmsSetupMock2", | ||||||
|                 "quiz2", |                 "quiz2", | ||||||
|                 ExamType.MANAGED); |                 ExamType.MANAGED, | ||||||
|  |                 "user7"); | ||||||
| 
 | 
 | ||||||
|         assertNotNull(exam2); |         assertNotNull(exam2); | ||||||
|         assertEquals("quiz2", exam2.getExternalId()); |         assertEquals("quiz2", exam2.getExternalId()); | ||||||
|  | @ -106,7 +110,8 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester { | ||||||
|                     getExamAdmin1(), // this exam administrator is on Institution 2 |                     getExamAdmin1(), // this exam administrator is on Institution 2 | ||||||
|                     "LmsSetupMock", |                     "LmsSetupMock", | ||||||
|                     "quiz2", |                     "quiz2", | ||||||
|                     ExamType.MANAGED); |                     ExamType.MANAGED, | ||||||
|  |                     "user7"); | ||||||
|             fail("AssertionError expected here"); |             fail("AssertionError expected here"); | ||||||
|         } catch (final AssertionError ae) { |         } catch (final AssertionError ae) { | ||||||
|             assertEquals("Response status expected:<200> but was:<403>", ae.getMessage()); |             assertEquals("Response status expected:<200> but was:<403>", ae.getMessage()); | ||||||
|  | @ -119,7 +124,8 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester { | ||||||
|             final String tokenForExamImport, |             final String tokenForExamImport, | ||||||
|             final String lmsSetupName, |             final String lmsSetupName, | ||||||
|             final String importQuizName, |             final String importQuizName, | ||||||
|             final ExamType examType) throws Exception { |             final ExamType examType, | ||||||
|  |             final String supporter) throws Exception { | ||||||
| 
 | 
 | ||||||
|         // create new active LmsSetup Mock with seb-admin |         // create new active LmsSetup Mock with seb-admin | ||||||
|         final LmsSetup lmsSetup1 = QuizDataTest.createLmsSetupMock( |         final LmsSetup lmsSetup1 = QuizDataTest.createLmsSetupMock( | ||||||
|  | @ -135,6 +141,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester { | ||||||
|                 .withMethod(HttpMethod.POST) |                 .withMethod(HttpMethod.POST) | ||||||
|                 .withAttribute(QuizData.QUIZ_ATTR_LMS_SETUP_ID, lmsSetup1.getModelId()) |                 .withAttribute(QuizData.QUIZ_ATTR_LMS_SETUP_ID, lmsSetup1.getModelId()) | ||||||
|                 .withAttribute(QuizData.QUIZ_ATTR_ID, importQuizName) |                 .withAttribute(QuizData.QUIZ_ATTR_ID, importQuizName) | ||||||
|  |                 .withAttribute(Domain.EXAM.ATTR_SUPPORTER, supporter) | ||||||
|                 .withAttribute(Domain.EXAM.ATTR_TYPE, examType.name()) |                 .withAttribute(Domain.EXAM.ATTR_TYPE, examType.name()) | ||||||
|                 .withExpectedStatus(HttpStatus.OK) |                 .withExpectedStatus(HttpStatus.OK) | ||||||
|                 .getAsObject(new TypeReference<Exam>() { |                 .getAsObject(new TypeReference<Exam>() { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti