From 6eaef577d2e95706082f9dbc8f139a68a6239db6 Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 22 Oct 2019 16:42:33 +0200 Subject: [PATCH] SEBSERV-73 adapt new config handling with states in front- and back-end --- .../ch/ethz/seb/sebserver/gbl/api/API.java | 1 + .../model/sebconfig/ConfigurationNode.java | 1 + .../content/ConfigTemplateAttributeForm.java | 2 +- .../gui/content/SebExamConfigPropForm.java | 23 +++- .../content/SebExamConfigSettingsForm.java | 6 +- .../gui/content/action/ActionDefinition.java | 19 ++-- .../activity/PageStateDefinitionImpl.java | 5 +- .../sebserver/gui/form/TextFieldBuilder.java | 3 +- .../gui/service/ResourceService.java | 14 +++ .../examconfig/ExamConfigurationService.java | 3 +- .../impl/AbstractTableFieldBuilder.java | 3 +- .../impl/CellFieldBuilderAdapter.java | 1 - .../examconfig/impl/CheckBoxBuilder.java | 18 +-- .../impl/CompositeTableFieldBuilder.java | 27 +++-- .../impl/ExamConfigurationServiceImpl.java | 6 +- .../impl/InlineTableFieldBuilder.java | 16 ++- .../impl/MultiCheckboxSelection.java | 20 ++-- .../examconfig/impl/PassworFieldBuilder.java | 67 ++++++----- .../impl/RadioSelectionFieldBuilder.java | 20 ++-- .../impl/SingleSelectionFieldBuilder.java | 20 ++-- .../examconfig/impl/SliderFieldBuilder.java | 24 ++-- .../examconfig/impl/TableFieldBuilder.java | 42 +++---- .../examconfig/impl/TextFieldBuilder.java | 33 ++++-- .../service/examconfig/impl/ViewContext.java | 5 +- .../service/page/impl/ModalInputDialog.java | 2 - .../api/seb/examconfig/CopyConfiguration.java | 42 +++++++ .../seb/examconfig/GetExamConfigNodes.java | 38 +++++++ .../seb/sebserver/gui/widget/GridTable.java | 3 + .../sebserver/gui/widget/WidgetFactory.java | 8 +- .../servicelayer/dao/ConfigurationDAO.java | 8 +- .../dao/ConfigurationNodeDAO.java | 7 ++ .../dao/ExamConfigurationMapDAO.java | 8 +- .../impl/ConfigurationDAOBatchService.java | 104 +++++++++++++++--- .../dao/impl/ConfigurationDAOImpl.java | 23 ++++ .../dao/impl/ConfigurationNodeDAOImpl.java | 92 +++++++++++++++- .../dao/impl/ExamConfigurationMapDAOImpl.java | 31 ++++-- .../sebconfig/impl/ExamConfigIO.java | 2 +- .../weblayer/api/ConfigurationController.java | 59 +++++++++- .../api/ConfigurationNodeController.java | 33 +++++- .../api/ConfigurationValueController.java | 24 +--- .../config/application-dev-ws.properties | 2 + src/main/resources/messages.properties | 8 +- src/main/resources/static/css/sebserver.css | 37 +++++-- src/main/resources/static/css/sms.css | 15 ++- 44 files changed, 713 insertions(+), 212 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/CopyConfiguration.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigNodes.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 72d112a3..e0ff73c4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -119,6 +119,7 @@ public final class API { public static final String CONFIGURATION_ENDPOINT = "/configuration"; public static final String CONFIGURATION_SAVE_TO_HISTORY_PATH_SEGMENT = "/save_to_history"; public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo"; + public static final String CONFIGURATION_COPY_PATH_SEGMENT = "/copy"; public static final String CONFIGURATION_RESTORE_FROM_HISTORY_PATH_SEGMENT = "/restore"; public static final String CONFIGURATION_VALUE_ENDPOINT = "/configuration_value"; public static final String CONFIGURATION_TABLE_VALUE_PATH_SEGMENT = "/table"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationNode.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationNode.java index f2640c23..d7f59eb3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationNode.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationNode.java @@ -25,6 +25,7 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity; public final class ConfigurationNode implements GrantEntity { public static final Long DEFAULT_TEMPLATE_ID = 0L; + public static final String ATTR_COPY_WITH_HISTORY = "with-history"; public static final String FILTER_ATTR_TEMPLATE_ID = "templateId"; public static final String FILTER_ATTR_DESCRIPTION = "description"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ConfigTemplateAttributeForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ConfigTemplateAttributeForm.java index 6bf5cb29..87a45257 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ConfigTemplateAttributeForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ConfigTemplateAttributeForm.java @@ -176,7 +176,7 @@ public class ConfigTemplateAttributeForm implements TemplateComposer { configuration, new View(-1L, "template", 10, 0, templateId), attributeMapping, - 1); + 1, false); final InputFieldBuilder inputFieldBuilder = this.examConfigurationService.getInputFieldBuilder( attribute.getConfigAttribute(), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java index 8b7e6f80..68f13bcf 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java @@ -30,6 +30,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType; import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; @@ -74,6 +75,8 @@ public class SebExamConfigPropForm implements TemplateComposer { new LocTextKey("sebserver.examconfig.form.name"); private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY = new LocTextKey("sebserver.examconfig.form.description"); + private static final LocTextKey FORM_TEMPLATE_TEXT_KEY = + new LocTextKey("sebserver.examconfig.form.template"); private static final LocTextKey FORM_STATUS_TEXT_KEY = new LocTextKey("sebserver.examconfig.form.status"); private static final LocTextKey FORM_IMPORT_TEXT_KEY = @@ -113,6 +116,7 @@ public class SebExamConfigPropForm implements TemplateComposer { final UserInfo user = this.currentUser.get(); final EntityKey entityKey = pageContext.getEntityKey(); + final EntityKey parentEntityKey = pageContext.getParentEntityKey(); final boolean isNew = entityKey == null; // get data or create new. Handle error if happen @@ -169,6 +173,14 @@ public class SebExamConfigPropForm implements TemplateComposer { FORM_DESCRIPTION_TEXT_KEY, examConfig.description) .asArea()) + .addField(FormBuilder.singleSelection( + Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID, + FORM_TEMPLATE_TEXT_KEY, + (parentEntityKey != null) + ? parentEntityKey.modelId + : String.valueOf(examConfig.templateId), + resourceService::getExamConfigTemplateResources) + .readonly(!isNew)) .addField(FormBuilder.singleSelection( Domain.CONFIGURATION_NODE.ATTR_STATUS, FORM_STATUS_TEXT_KEY, @@ -178,6 +190,7 @@ public class SebExamConfigPropForm implements TemplateComposer { ? this.restService.getRestCall(NewExamConfig.class) : this.restService.getRestCall(SaveExamConfig.class)); + final boolean settingsReadonly = examConfig.status == ConfigurationStatus.IN_USE; final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class); this.pageService.pageActionBuilder(formContext.clearEntityKeys()) @@ -190,7 +203,11 @@ public class SebExamConfigPropForm implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_MODIFY) .withEntityKey(entityKey) - .publishIf(() -> modifyGrant && isReadonly) + .publishIf(() -> modifyGrant && isReadonly && !settingsReadonly) + + .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW) + .withEntityKey(entityKey) + .publishIf(() -> modifyGrant && isReadonly && settingsReadonly) .newAction(ActionDefinition.SEB_EXAM_CONFIG_EXPORT_PLAIN_XML) .withEntityKey(entityKey) @@ -216,13 +233,13 @@ public class SebExamConfigPropForm implements TemplateComposer { .noEventPropagation() .publishIf(() -> modifyGrant && isReadonly) - .newAction(ActionDefinition.SEB_EXAM_CONFIG_SAVE) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_SAVE) .withEntityKey(entityKey) .withExec(formHandle::processFormSave) .ignoreMoveAwayFromEdit() .publishIf(() -> !isReadonly) - .newAction(ActionDefinition.SEB_EXAM_CONFIG_CANCEL_MODIFY) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY) .withEntityKey(entityKey) .withExec(this.pageService.backToCurrentFunction()) .publishIf(() -> !isReadonly); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigSettingsForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigSettingsForm.java index 9671cfdc..73b7d3c7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigSettingsForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigSettingsForm.java @@ -113,7 +113,6 @@ public class SebExamConfigSettingsForm implements TemplateComposer { .getOrThrow(); final List views = this.examConfigurationService.getViews(attributes); - final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content); tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); @@ -124,7 +123,8 @@ public class SebExamConfigSettingsForm implements TemplateComposer { configuration, view, attributes, - 20); + 20, + pageContext.isReadonly()); viewContexts.add(viewContext); final Composite viewGrid = this.examConfigurationService.createViewGrid( @@ -141,6 +141,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer { final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE); this.pageService.pageActionBuilder(pageContext.clearEntityKeys()) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_SAVE_TO_HISTORY) .withEntityKey(entityKey) .withExec(action -> { @@ -169,6 +170,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP) .withEntityKey(entityKey) + .ignoreMoveAwayFromEdit() .publish() ; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 022e74e0..f1ca1651 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -351,12 +351,12 @@ public enum ActionDefinition { SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST( new LocTextKey("sebserver.examconfig.action.list.view"), ImageIcon.SHOW, - PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW, + PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW, ActionCategory.SEB_EXAM_CONFIG_LIST), SEB_EXAM_CONFIG_VIEW_PROP( new LocTextKey("sebserver.examconfig.action.view"), ImageIcon.SHOW, - PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW, + PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW, ActionCategory.FORM), SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST( @@ -374,15 +374,20 @@ public enum ActionDefinition { ImageIcon.EDIT_SETTINGS, PageStateDefinitionImpl.SEB_EXAM_CONFIG_EDIT, ActionCategory.FORM), - SEB_EXAM_CONFIG_CANCEL_MODIFY( - new LocTextKey("sebserver.overall.action.modify.cancel"), - ImageIcon.CANCEL, + SEB_EXAM_CONFIG_VIEW( + new LocTextKey("sebserver.examconfig.action.view"), + ImageIcon.EDIT_SETTINGS, PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW, ActionCategory.FORM), - SEB_EXAM_CONFIG_SAVE( + SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY( + new LocTextKey("sebserver.overall.action.modify.cancel"), + ImageIcon.CANCEL, + PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW, + ActionCategory.FORM), + SEB_EXAM_CONFIG_PROP_SAVE( new LocTextKey("sebserver.examconfig.action.save"), ImageIcon.SAVE, - PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW, + PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW, ActionCategory.FORM), SEB_EXAM_CONFIG_EXPORT_PLAIN_XML( new LocTextKey("sebserver.examconfig.action.export.plainxml"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java index e17df9af..68348eba 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java @@ -65,9 +65,10 @@ public enum PageStateDefinitionImpl implements PageStateDefinition { SEB_CLIENT_CONFIG_EDIT(Type.FORM_EDIT, SebClientConfigForm.class, ActivityDefinition.SEB_CLIENT_CONFIG), SEB_EXAM_CONFIG_LIST(Type.LIST_VIEW, SebExamConfigList.class, ActivityDefinition.SEB_EXAM_CONFIG), - SEB_EXAM_CONFIG_VIEW(Type.FORM_VIEW, SebExamConfigPropForm.class, ActivityDefinition.SEB_EXAM_CONFIG), + SEB_EXAM_CONFIG_PROP_VIEW(Type.FORM_VIEW, SebExamConfigPropForm.class, ActivityDefinition.SEB_EXAM_CONFIG), SEB_EXAM_CONFIG_PROP_EDIT(Type.FORM_EDIT, SebExamConfigPropForm.class, ActivityDefinition.SEB_EXAM_CONFIG), - SEB_EXAM_CONFIG_EDIT(Type.FORM_VIEW, SebExamConfigSettingsForm.class, ActivityDefinition.SEB_EXAM_CONFIG), + SEB_EXAM_CONFIG_EDIT(Type.FORM_EDIT, SebExamConfigSettingsForm.class, ActivityDefinition.SEB_EXAM_CONFIG), + SEB_EXAM_CONFIG_VIEW(Type.FORM_VIEW, SebExamConfigSettingsForm.class, ActivityDefinition.SEB_EXAM_CONFIG), SEB_EXAM_CONFIG_TEMPLATE_VIEW(Type.FORM_VIEW, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG), SEB_EXAM_CONFIG_TEMPLATE_EDIT(Type.FORM_EDIT, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java index 09f53e3f..df42fb04 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java @@ -17,6 +17,7 @@ import org.eclipse.swt.widgets.Text; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; public final class TextFieldBuilder extends FieldBuilder { @@ -61,7 +62,7 @@ public final class TextFieldBuilder extends FieldBuilder { final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true); if (this.isArea) { - gridData.minimumHeight = 35; + gridData.minimumHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT; } textInput.setLayoutData(gridData); if (StringUtils.isNoneBlank(this.value)) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index b39a5c43..7082596c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -62,6 +62,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExams; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupNames; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNodes; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetViews; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; @@ -551,6 +552,19 @@ public class ResourceService { k -> k.name)); } + public List> getExamConfigTemplateResources() { + final UserInfo userInfo = this.currentUser.get(); + return this.restService.getBuilder(GetExamConfigNodes.class) + .withQueryParam(Entity.FILTER_ATTR_INSTITUTION, String.valueOf(userInfo.getInstitutionId())) + .withQueryParam(ConfigurationNode.FILTER_ATTR_TYPE, ConfigurationType.TEMPLATE.name()) + .call() + .getOr(Collections.emptyList()) + .stream() + .map(node -> new Tuple<>(node.getModelId(), node.name)) + .sorted(RESOURCE_COMPARATOR) + .collect(Collectors.toList()); + } + private Result> getExamConfigurationSelection() { return this.restService.getBuilder(GetExamConfigMappingNames.class) .withQueryParam( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ExamConfigurationService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ExamConfigurationService.java index a617e432..93579253 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ExamConfigurationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ExamConfigurationService.java @@ -54,7 +54,8 @@ public interface ExamConfigurationService { Configuration configuration, View view, AttributeMapping attributeMapping, - int rows); + int rows, + boolean readonly); Composite createViewGrid( Composite parent, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractTableFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractTableFieldBuilder.java index 4d0b55ae..3f761668 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractTableFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractTableFieldBuilder.java @@ -104,6 +104,7 @@ public abstract class AbstractTableFieldBuilder implements InputFieldBuilder { final TableContext tableContext) { try { + final boolean readonly = tableContext.getViewContext().readonly; final int currentTableWidth = table.getClientArea().width - TABLE_WIDTH_SPACE; final TableColumn[] columns = table.getColumns(); final List orientations = tableContext @@ -116,7 +117,7 @@ public abstract class AbstractTableFieldBuilder implements InputFieldBuilder { .map(o -> o.width) .reduce(0, (acc, val) -> acc + val); final int widthUnit = currentTableWidth / div; - for (int i = 0; i < columns.length - 2; i++) { + for (int i = 0; i < columns.length - ((readonly) ? 0 : 2); i++) { columns[i].setWidth(widthUnit * orientations.get(i).width); } } catch (final Exception e) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CellFieldBuilderAdapter.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CellFieldBuilderAdapter.java index d258338e..5ff3cbcd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CellFieldBuilderAdapter.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CellFieldBuilderAdapter.java @@ -154,7 +154,6 @@ interface CellFieldBuilderAdapter { label.setAlignment(SWT.LEFT); gridData.verticalIndent = 20; label.setLayoutData(gridData); - //label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key); } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CheckBoxBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CheckBoxBuilder.java index fcc19879..2107cda8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CheckBoxBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CheckBoxBuilder.java @@ -85,13 +85,17 @@ public class CheckBoxBuilder implements InputFieldBuilder { viewContext.getOrientation(attribute.id), checkbox); - checkbox.addListener( - SWT.Selection, - event -> viewContext.getValueChangeListener().valueChanged( - viewContext, - attribute, - checkboxField.getValue(), - checkboxField.listIndex)); + if (viewContext.readonly) { + checkbox.setEnabled(false); + } else { + checkbox.addListener( + SWT.Selection, + event -> viewContext.getValueChangeListener().valueChanged( + viewContext, + attribute, + checkboxField.getValue(), + checkboxField.listIndex)); + } return checkboxField; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CompositeTableFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CompositeTableFieldBuilder.java index d2f63e34..e79944df 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CompositeTableFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CompositeTableFieldBuilder.java @@ -201,16 +201,27 @@ public class CompositeTableFieldBuilder extends AbstractTableFieldBuilder { rowValues, row); - new ModalInputDialog>( + final ModalInputDialog> dialog = new ModalInputDialog>( this.control.getShell(), this.tableContext.getWidgetFactory()) - .setDialogWidth(500) - .open( - new LocTextKey(ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + row), - rowVals -> applyFormValues(this.values, rowVals, selectionIndex), - () -> this.tableContext.getValueChangeListener() - .tableChanged(extractTableValue(this.values)), - builder); + .setDialogWidth(500); + + if (this.tableContext.getViewContext().readonly) { + dialog.open( + new LocTextKey(ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + row), + rowVals -> { + }, + () -> { + }, + builder); + } else { + dialog.open( + new LocTextKey(ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + row), + rowVals -> applyFormValues(this.values, rowVals, selectionIndex), + () -> this.tableContext.getValueChangeListener() + .tableChanged(extractTableValue(this.values)), + builder); + } } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ExamConfigurationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ExamConfigurationServiceImpl.java index cc5ba825..f0ab6973 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ExamConfigurationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ExamConfigurationServiceImpl.java @@ -177,7 +177,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService { final Configuration configuration, final View view, final AttributeMapping attributeMapping, - final int rows) { + final int rows, + final boolean readonly) { return new ViewContext( configuration, @@ -189,7 +190,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService { this.restService, this.jsonMapper, this.valueChangeRules), - this.widgetFactory.getI18nSupport()); + this.widgetFactory.getI18nSupport(), + readonly); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/InlineTableFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/InlineTableFieldBuilder.java index 9c744087..b87da3ad 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/InlineTableFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/InlineTableFieldBuilder.java @@ -92,11 +92,17 @@ public class InlineTableFieldBuilder implements InputFieldBuilder { viewContext.getOrientation(attribute.id), gridTable); - gridTable.setListener(event -> viewContext.getValueChangeListener().valueChanged( - viewContext, - attribute, - inlineTableInputField.getValue(), - inlineTableInputField.listIndex)); + if (viewContext.readonly) { + gridTable.setEnabled(false); + gridTable.setListener(event -> { + }); + } else { + gridTable.setListener(event -> viewContext.getValueChangeListener().valueChanged( + viewContext, + attribute, + inlineTableInputField.getValue(), + inlineTableInputField.listIndex)); + } return inlineTableInputField; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/MultiCheckboxSelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/MultiCheckboxSelection.java index e4ee274e..60356191 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/MultiCheckboxSelection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/MultiCheckboxSelection.java @@ -68,14 +68,18 @@ public class MultiCheckboxSelection extends SelectionFieldBuilder implements Inp orientation, selection); - selection.setSelectionListener(event -> { - multiSelectionCheckboxInputField.clearError(); - viewContext.getValueChangeListener().valueChanged( - viewContext, - attribute, - multiSelectionCheckboxInputField.getValue(), - multiSelectionCheckboxInputField.listIndex); - }); + if (viewContext.readonly) { + selection.setEnabled(false); + } else { + selection.setSelectionListener(event -> { + multiSelectionCheckboxInputField.clearError(); + viewContext.getValueChangeListener().valueChanged( + viewContext, + attribute, + multiSelectionCheckboxInputField.getValue(), + multiSelectionCheckboxInputField.listIndex); + }); + } return multiSelectionCheckboxInputField; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java index 7bb21176..6e01655b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java @@ -13,6 +13,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.apache.commons.codec.binary.Hex; +import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; @@ -32,6 +33,8 @@ import ch.ethz.seb.sebserver.gui.form.Form; import ch.ethz.seb.sebserver.gui.service.examconfig.InputField; import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; @Lazy @Component @@ -67,7 +70,8 @@ public class PassworFieldBuilder implements InputFieldBuilder { .createInnerGrid(parent, attribute, orientation); final Text passwordInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD); - passwordInput.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + final GridData passwordInputLD = new GridData(SWT.FILL, SWT.FILL, true, false); + passwordInput.setLayoutData(passwordInputLD); final Text confirmInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); gridData.verticalIndent = 14; @@ -80,37 +84,46 @@ public class PassworFieldBuilder implements InputFieldBuilder { confirmInput, Form.createErrorLabel(innerGrid)); - final Listener valueChangeEventListener = event -> { - passwordInputField.clearError(); + if (viewContext.readonly) { + passwordInput.setEditable(false); + passwordInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key); + passwordInputLD.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT; + confirmInput.setEditable(false); + confirmInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key); + gridData.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT; + } else { + final Listener valueChangeEventListener = event -> { + passwordInputField.clearError(); - final String pwd = passwordInput.getText(); - final String confirm = confirmInput.getText(); + final String pwd = passwordInput.getText(); + final String confirm = confirmInput.getText(); - if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) { - return; - } + if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) { + return; + } - if (!pwd.equals(confirm)) { - passwordInputField.showError(viewContext - .getI18nSupport() - .getText(VAL_CONFIRM_PWD_TEXT_KEY)); - return; - } + if (!pwd.equals(confirm)) { + passwordInputField.showError(viewContext + .getI18nSupport() + .getText(VAL_CONFIRM_PWD_TEXT_KEY)); + return; + } - final String hashedPWD = passwordInputField.getValue(); - if (hashedPWD != null) { - viewContext.getValueChangeListener().valueChanged( - viewContext, - attribute, - hashedPWD, - passwordInputField.listIndex); - } - }; + final String hashedPWD = passwordInputField.getValue(); + if (hashedPWD != null) { + viewContext.getValueChangeListener().valueChanged( + viewContext, + attribute, + hashedPWD, + passwordInputField.listIndex); + } + }; - passwordInput.addListener(SWT.FocusOut, valueChangeEventListener); - passwordInput.addListener(SWT.Traverse, valueChangeEventListener); - confirmInput.addListener(SWT.FocusOut, valueChangeEventListener); - confirmInput.addListener(SWT.Traverse, valueChangeEventListener); + passwordInput.addListener(SWT.FocusOut, valueChangeEventListener); + passwordInput.addListener(SWT.Traverse, valueChangeEventListener); + confirmInput.addListener(SWT.FocusOut, valueChangeEventListener); + confirmInput.addListener(SWT.Traverse, valueChangeEventListener); + } return passwordInputField; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/RadioSelectionFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/RadioSelectionFieldBuilder.java index 9659a9dd..0aab8d41 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/RadioSelectionFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/RadioSelectionFieldBuilder.java @@ -71,14 +71,18 @@ public class RadioSelectionFieldBuilder extends SelectionFieldBuilder implements selection, null); - selection.setSelectionListener(event -> { - radioSelectionInputField.clearError(); - viewContext.getValueChangeListener().valueChanged( - viewContext, - attribute, - radioSelectionInputField.getValue(), - radioSelectionInputField.listIndex); - }); + if (viewContext.readonly) { + selection.setEnabled(false); + } else { + selection.setSelectionListener(event -> { + radioSelectionInputField.clearError(); + viewContext.getValueChangeListener().valueChanged( + viewContext, + attribute, + radioSelectionInputField.getValue(), + radioSelectionInputField.listIndex); + }); + } return radioSelectionInputField; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SingleSelectionFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SingleSelectionFieldBuilder.java index d876f4ae..2b2a8aa6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SingleSelectionFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SingleSelectionFieldBuilder.java @@ -76,14 +76,18 @@ public class SingleSelectionFieldBuilder extends SelectionFieldBuilder implement selection, Form.createErrorLabel(innerGrid)); - selection.setSelectionListener(event -> { - singleSelectionInputField.clearError(); - viewContext.getValueChangeListener().valueChanged( - viewContext, - attribute, - singleSelectionInputField.getValue(), - singleSelectionInputField.listIndex); - }); + if (viewContext.readonly) { + selection.setEnabled(false); + } else { + selection.setSelectionListener(event -> { + singleSelectionInputField.clearError(); + viewContext.getValueChangeListener().valueChanged( + viewContext, + attribute, + singleSelectionInputField.getValue(), + singleSelectionInputField.listIndex); + }); + } return singleSelectionInputField; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SliderFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SliderFieldBuilder.java index 4ada0d1a..63486312 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SliderFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SliderFieldBuilder.java @@ -73,17 +73,21 @@ public class SliderFieldBuilder implements InputFieldBuilder { orientation, slider); - final Listener valueChangeEventListener = event -> { - inputField.clearError(); - viewContext.getValueChangeListener().valueChanged( - viewContext, - attribute, - inputField.getValue(), - inputField.listIndex); - }; + if (viewContext.readonly) { + slider.setEnabled(false); + } else { + final Listener valueChangeEventListener = event -> { + inputField.clearError(); + viewContext.getValueChangeListener().valueChanged( + viewContext, + attribute, + inputField.getValue(), + inputField.listIndex); + }; - slider.addListener(SWT.FocusOut, valueChangeEventListener); - slider.addListener(SWT.Traverse, valueChangeEventListener); + slider.addListener(SWT.FocusOut, valueChangeEventListener); + slider.addListener(SWT.Traverse, valueChangeEventListener); + } return inputField; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableFieldBuilder.java index 143e6a90..4bd1a072 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TableFieldBuilder.java @@ -85,30 +85,32 @@ public class TableFieldBuilder extends AbstractTableFieldBuilder { tableContext, table); - TableColumn column = new TableColumn(table, SWT.NONE); - column.setImage(ImageIcon.ADD_BOX.getImage(parent.getDisplay())); + if (!viewContext.readonly) { + TableColumn column = new TableColumn(table, SWT.NONE); + column.setImage(ImageIcon.ADD_BOX.getImage(parent.getDisplay())); - column.setWidth(20); - column.setResizable(false); - column.setMoveable(false); + column.setWidth(20); + column.setResizable(false); + column.setMoveable(false); - column.addListener(SWT.Selection, event -> { - tableField.addRow(); - }); + column.addListener(SWT.Selection, event -> { + tableField.addRow(); + }); - column = new TableColumn(table, SWT.NONE); - column.setImage(ImageIcon.REMOVE_BOX.getImage(parent.getDisplay())); + column = new TableColumn(table, SWT.NONE); + column.setImage(ImageIcon.REMOVE_BOX.getImage(parent.getDisplay())); - column.setWidth(20); - column.setResizable(false); - column.setMoveable(false); + column.setWidth(20); + column.setResizable(false); + column.setMoveable(false); - column.addListener(SWT.Selection, event -> { - final int selectionIndex = table.getSelectionIndex(); - if (selectionIndex >= 0) { - tableField.deleteRow(selectionIndex); - } - }); + column.addListener(SWT.Selection, event -> { + final int selectionIndex = table.getSelectionIndex(); + if (selectionIndex >= 0) { + tableField.deleteRow(selectionIndex); + } + }); + } setSelectionListener(table, tableField); return tableField; @@ -163,8 +165,6 @@ public class TableFieldBuilder extends AbstractTableFieldBuilder { protected void addTableRow(final int index, final Map rowValues) { new TableItem(this.control, SWT.NONE); applyTableRowValues(index); - - // TODO try to add delete button within table row? } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldBuilder.java index c2804a2a..052a6984 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldBuilder.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.examconfig.impl; import java.util.function.Consumer; import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; @@ -31,6 +32,8 @@ import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; @Lazy @Component @@ -74,7 +77,7 @@ public class TextFieldBuilder implements InputFieldBuilder { } case TEXT_AREA: { text = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.MULTI); - gridData.heightHint = 50; + gridData.minimumHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT; break; } default: { @@ -101,17 +104,25 @@ public class TextFieldBuilder implements InputFieldBuilder { text, Form.createErrorLabel(innerGrid)); - final Listener valueChangeEventListener = event -> { - textInputField.clearError(); - viewContext.getValueChangeListener().valueChanged( - viewContext, - attribute, - textInputField.getValue(), - textInputField.listIndex); - }; + if (viewContext.readonly) { + text.setEditable(false); + text.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key); + gridData.heightHint = (attribute.type == AttributeType.TEXT_AREA) + ? WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT + : WidgetFactory.TEXT_INPUT_MIN_HEIGHT; + } else { + final Listener valueChangeEventListener = event -> { + textInputField.clearError(); + viewContext.getValueChangeListener().valueChanged( + viewContext, + attribute, + textInputField.getValue(), + textInputField.listIndex); + }; - text.addListener(SWT.FocusOut, valueChangeEventListener); - text.addListener(SWT.Traverse, valueChangeEventListener); + text.addListener(SWT.FocusOut, valueChangeEventListener); + text.addListener(SWT.Traverse, valueChangeEventListener); + } return textInputField; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewContext.java index d12f3dc6..03f5c123 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewContext.java @@ -37,6 +37,7 @@ public final class ViewContext { final Map inputFieldMapping; final ValueChangeListener valueChangeListener; final I18nSupport i18nSupport; + final boolean readonly; ViewContext( final Configuration configuration, @@ -44,7 +45,8 @@ public final class ViewContext { final int rows, final AttributeMapping attributeContext, final ValueChangeListener valueChangeListener, - final I18nSupport i18nSupport) { + final I18nSupport i18nSupport, + final boolean readonly) { Objects.requireNonNull(configuration); Objects.requireNonNull(view); @@ -59,6 +61,7 @@ public final class ViewContext { this.inputFieldMapping = new HashMap<>(); this.valueChangeListener = valueChangeListener; this.i18nSupport = i18nSupport; + this.readonly = readonly; } public I18nSupport getI18nSupport() { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java index 48aa1a2d..5379607f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java @@ -49,7 +49,6 @@ public class ModalInputDialog extends Dialog { super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL | SWT.CLOSE); this.widgetFactory = widgetFactory; - } public ModalInputDialog setDialogWidth(final int dialogWidth) { @@ -161,7 +160,6 @@ public class ModalInputDialog extends Dialog { shell.pack(); final Rectangle bounds = shell.getBounds(); final Rectangle bounds2 = super.getParent().getDisplay().getBounds(); - //bounds.width = bounds.width; bounds.x = (bounds2.width - bounds.width) / 2; bounds.y = (bounds2.height - bounds.height) / 2; shell.setBounds(bounds); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/CopyConfiguration.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/CopyConfiguration.java new file mode 100644 index 00000000..b40077f5 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/CopyConfiguration.java @@ -0,0 +1,42 @@ +/* + * 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.service.remote.webservice.api.seb.examconfig; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class CopyConfiguration extends RestCall { + + public CopyConfiguration() { + super(new TypeKey<>( + CallType.SAVE, + EntityType.CONFIGURATION_NODE, + new TypeReference() { + }), + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.CONFIGURATION_NODE_ENDPOINT + + API.MODEL_ID_VAR_PATH_SEGMENT + + API.CONFIGURATION_COPY_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigNodes.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigNodes.java new file mode 100644 index 00000000..5b1b7681 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/GetExamConfigNodes.java @@ -0,0 +1,38 @@ +/* + * 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.service.remote.webservice.api.seb.examconfig; + +import java.util.List; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.PageToListCallAdapter; + +@Lazy +@Component +@GuiProfile +public class GetExamConfigNodes extends PageToListCallAdapter { + + public GetExamConfigNodes() { + super( + GetExamConfigNodePage.class, + EntityType.CONFIGURATION_NODE, + new TypeReference>() { + }, + API.CONFIGURATION_NODE_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/GridTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/GridTable.java index 4c2d5aa4..82b5c743 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/GridTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/GridTable.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; 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.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -34,6 +35,7 @@ import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageService; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; public class GridTable extends Composite { @@ -394,6 +396,7 @@ public class GridTable extends Composite { TextField(final Composite parent, final ColumnDef columnDef, final Listener listener) { this._textField = new Text(parent, SWT.LEFT | SWT.BORDER); + this._textField.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key); this._textField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); this.columnDef = columnDef; this._textField.addListener(SWT.FocusOut, listener); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index f5b4b9f0..f3b86a22 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -58,6 +58,9 @@ public class WidgetFactory { private static final Logger log = LoggerFactory.getLogger(WidgetFactory.class); + public static final int TEXT_AREA_INPUT_MIN_HEIGHT = 50; + public static final int TEXT_INPUT_MIN_HEIGHT = 24; + public enum ImageIcon { MAXIMIZE("maximize.png"), MINIMIZE("minimize.png"), @@ -127,7 +130,8 @@ public class WidgetFactory { TITLE_LABEL("head"), MESSAGE("message"), - ERROR("error") + ERROR("error"), + CONFIG_INPUT_READONLY("inputreadonly") ; @@ -327,7 +331,7 @@ public class WidgetFactory { public Text textInput(final Composite content, final boolean password, final boolean readonly) { return readonly - ? new Text(content, SWT.LEFT | SWT.BORDER) + ? new Text(content, SWT.LEFT) : new Text(content, (password) ? SWT.LEFT | SWT.BORDER | SWT.PASSWORD : SWT.LEFT | SWT.BORDER); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java index fc04f059..a5107ea6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java @@ -43,7 +43,7 @@ public interface ConfigurationDAO extends EntityDAO saveToHistory(Long configurationNodeId); /** Can be used to reset the current follow-up configuration back to the last saved version in the history - * + * * @param configurationNodeId ConfigurationNode identifier to apply the undo on * @return the current and reseted follow-up version */ Result undo(Long configurationNodeId); @@ -62,4 +62,10 @@ public interface ConfigurationDAO extends EntityDAO getFollowupConfiguration(Long configNodeId); + /** Use this to get the last version of a configuration that is not the follow-up. + * + * @param configNodeId ConfigurationNode identifier to get the last version of configuration from + * @return the last version of configuration */ + Result getConfigurationLastStableVersion(Long configNodeId); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationNodeDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationNodeDAO.java index e8ec5272..c52cd3db 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationNodeDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationNodeDAO.java @@ -9,10 +9,17 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; +import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO; public interface ConfigurationNodeDAO extends EntityDAO, BulkActionSupportDAO { + Result createCopy( + Long institutionId, + String newOwner, + Long configurationNodeId, + boolean withHistory); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamConfigurationMapDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamConfigurationMapDAO.java index be79ed9f..f7f2168d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamConfigurationMapDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamConfigurationMapDAO.java @@ -49,10 +49,16 @@ public interface ExamConfigurationMapDAO extends * the Exam for a specified user identifier */ Result getUserConfigurationIdForExam(final Long examId, final String userId); + /** Get all id of Exams that has a relation to the given configuration id. + * + * @param configurationNodeId the configuration node identifier (PK) + * @return Result referencing the List of exam identifiers (PK) for a given configuration node identifier */ + Result> getExamIdsForConfigNodeId(Long configurationNodeId); + /** Get all id of Exams that has a relation to the given configuration id. * * @param configurationId - * @return */ + * @return Result referencing the List of exam identifiers (PK) for a given configuration identifier */ Result> getExamIdsForConfigId(Long configurationId); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOBatchService.java index 271fbe23..f380db1c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOBatchService.java @@ -66,6 +66,8 @@ class ConfigurationDAOBatchService { private static final Logger log = LoggerFactory.getLogger(ConfigurationDAOBatchService.class); + public static final String INITIAL_VERSION_NAME = "v0"; + private final ConfigurationNodeRecordMapper batchConfigurationNodeRecordMapper; private final ConfigurationValueRecordMapper batchConfigurationValueRecordMapper; private final ConfigurationAttributeRecordMapper batchConfigurationAttributeRecordMapper; @@ -187,23 +189,13 @@ class ConfigurationDAOBatchService { .execute(); // get current versions count - final Long versions = this.batchConfigurationRecordMapper - .countByExample() - .where( - ConfigurationRecordDynamicSqlSupport.configurationNodeId, - isEqualTo(configurationNodeId)) - .and( - ConfigurationRecordDynamicSqlSupport.followup, - isNotEqualTo(BooleanUtils.toInteger(true))) - .build() - .execute(); // close follow-up configuration to save in history final ConfigurationRecord configUpdate = new ConfigurationRecord( followupConfig.getId(), null, null, - "v" + versions, + generateVersionName(configurationNodeId), DateTime.now(DateTimeZone.UTC), BooleanUtils.toInteger(false)); this.batchConfigurationRecordMapper.updateByPrimaryKeySelective(configUpdate); @@ -240,6 +232,20 @@ class ConfigurationDAOBatchService { .flatMap(ConfigurationDAOImpl::toDomainModel); } + private String generateVersionName(final Long configurationNodeId) { + final Long versions = this.batchConfigurationRecordMapper + .countByExample() + .where( + ConfigurationRecordDynamicSqlSupport.configurationNodeId, + isEqualTo(configurationNodeId)) + .and( + ConfigurationRecordDynamicSqlSupport.followup, + isNotEqualTo(BooleanUtils.toInteger(true))) + .build() + .execute(); + return "v" + versions; + } + Result undo(final Long configurationNodeId) { return Result.tryCatch(() -> { // get all configurations of the node @@ -311,6 +317,69 @@ class ConfigurationDAOBatchService { .flatMap(ConfigurationDAOImpl::toDomainModel); } + Result copyConfiguration( + final Long institutionId, + final Long fromConfigurationId, + final Long toConfigurationNodeId) { + + return Result.tryCatch(() -> { + final ConfigurationRecord fromRecord = this.batchConfigurationRecordMapper + .selectByPrimaryKey(fromConfigurationId); + + if (!fromRecord.getInstitutionId().equals(institutionId)) { + throw new IllegalArgumentException("Institution integrity violation"); + } + + final ConfigurationRecord configurationRecord = new ConfigurationRecord( + null, + fromRecord.getInstitutionId(), + toConfigurationNodeId, + fromRecord.getVersion(), + fromRecord.getVersionDate(), + fromRecord.getFollowup()); + this.batchConfigurationRecordMapper.insert(configurationRecord); + return configurationRecord; + }) + .flatMap(ConfigurationDAOImpl::toDomainModel) + .map(newConfig -> { + this.copyValues( + institutionId, + fromConfigurationId, + newConfig.getId()); + return newConfig; + }) + .map(config -> { + this.batchSqlSessionTemplate.flushStatements(); + return config; + }); + } + + void copyValues( + final Long institutionId, + final Long fromConfigId, + final Long toConfigId) { + + this.batchConfigurationValueRecordMapper + .selectByExample() + .where( + ConfigurationValueRecordDynamicSqlSupport.institutionId, + isEqualTo(institutionId)) + .and( + ConfigurationValueRecordDynamicSqlSupport.configurationId, + isEqualTo(fromConfigId)) + .build() + .execute() + .stream() + .map(fromRec -> new ConfigurationValueRecord( + null, + fromRec.getInstitutionId(), + toConfigId, + fromRec.getConfigurationAttributeId(), + fromRec.getListIndex(), + fromRec.getValue())) + .forEach(this.batchConfigurationValueRecordMapper::insert); + } + private ConfigurationRecord getFollowupConfigurationRecord(final Long configurationNodeId) { return this.batchConfigurationRecordMapper .selectByExample() @@ -454,7 +523,7 @@ class ConfigurationDAOBatchService { null, config.institutionId, config.id, - "v0", // TODO? + INITIAL_VERSION_NAME, DateTime.now(DateTimeZone.UTC), BooleanUtils.toInteger(false)); @@ -547,7 +616,7 @@ class ConfigurationDAOBatchService { } final Long configurationId = this.batchConfigurationRecordMapper.selectByExample() - .where(ConfigurationRecordDynamicSqlSupport.configurationNodeId, isEqualTo(configNode.id)) + .where(ConfigurationRecordDynamicSqlSupport.configurationNodeId, isEqualTo(configNode.templateId)) .and(ConfigurationRecordDynamicSqlSupport.followup, isEqualTo(BooleanUtils.toIntegerObject(true))) .build() .execute() @@ -555,14 +624,15 @@ class ConfigurationDAOBatchService { .collect(Utils.toSingleton()) .getId(); - return this.batchConfigurationValueRecordMapper.selectByExample() + final List values = this.batchConfigurationValueRecordMapper.selectByExample() .where(ConfigurationValueRecordDynamicSqlSupport.configurationId, isEqualTo(configurationId)) .build() - .execute() - .stream() + .execute(); + + return values.stream() .collect(Collectors.toMap( valRec -> valRec.getConfigurationAttributeId(), - valRec -> valRec.getValue())); + valRec -> (valRec.getValue() != null) ? valRec.getValue() : "")); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java index 4d098679..4835c7a4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java @@ -12,6 +12,8 @@ import static org.mybatis.dynamic.sql.SqlBuilder.*; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -131,6 +133,27 @@ public class ConfigurationDAOImpl implements ConfigurationDAO { } + @Override + @Transactional(readOnly = true) + public Result getConfigurationLastStableVersion(final Long configNodeId) { + return Result.tryCatch(() -> { + final List configs = this.configurationRecordMapper.selectByExample() + .where( + ConfigurationRecordDynamicSqlSupport.configurationNodeId, + isEqualTo(configNodeId)) + .and( + ConfigurationRecordDynamicSqlSupport.followup, + isEqualTo(BooleanUtils.toInteger(false))) + .build() + .execute(); + Collections.sort( + configs, + (c1, c2) -> c1.getVersionDate().compareTo(c2.getVersionDate())); + final ConfigurationRecord configurationRecord = configs.get(0); + return configurationRecord; + }).flatMap(ConfigurationDAOImpl::toDomainModel); + } + @Override @Transactional public Result save(final Configuration data) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationNodeDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationNodeDAOImpl.java index c1e326c4..6069ceec 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationNodeDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationNodeDAOImpl.java @@ -19,7 +19,12 @@ 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; @@ -40,6 +45,7 @@ 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; @@ -56,18 +62,24 @@ 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) { + 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.configurationNodeRecordMapper = configurationNodeRecordMapper; this.configurationValueRecordMapper = configurationValueRecordMapper; this.configurationDAOBatchService = ConfigurationDAOBatchService; + this.copyNamePrefix = copyNamePrefix; + this.copyNameSuffix = copyNameSuffix; } @Override @@ -191,6 +203,84 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO { .onError(TransactionHandler::rollback); } + @Override + @Transactional + public Result createCopy( + final Long institutionId, + final String newOwner, + final Long configurationNodeId, + final boolean withHistory) { + + return this.recordById(configurationNodeId) + .flatMap(nodeRec -> (nodeRec.getInstitutionId().equals(institutionId) + ? Result.of(nodeRec) + : Result.ofError(new IllegalArgumentException("Institution integrity violation")))) + .map(nodeRec -> this.copyNodeRecord(nodeRec, newOwner, withHistory)) + .flatMap(ConfigurationNodeDAOImpl::toDomainModel); + } + + private ConfigurationNodeRecord copyNodeRecord( + final ConfigurationNodeRecord nodeRec, + final String newOwner, + final boolean withHistory) { + + 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 configs = this.configurationRecordMapper + .selectByExample() + .where( + ConfigurationRecordDynamicSqlSupport.configurationNodeId, + isEqualTo(nodeRec.getId())) + .build() + .execute(); + + if (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; + } + @Override @Transactional public Result> delete(final Set all) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java index b62e005d..23e3aaea 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java @@ -288,11 +288,27 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO { return getDependencies(bulkAction, selectionFunction); } + @Override + @Transactional(readOnly = true) + public Result> getExamIdsForConfigNodeId(final Long configurationNodeId) { + return Result.tryCatch(() -> { + return this.examConfigurationMapRecordMapper.selectByExample() + .where( + ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId, + isEqualTo(configurationNodeId)) + .build() + .execute() + .stream() + .map(record -> record.getExamId()) + .collect(Collectors.toList()); + }); + } + @Override @Transactional(readOnly = true) public Result> getExamIdsForConfigId(final Long configurationId) { return Result.tryCatch(() -> { - final Long configNodeId = this.configurationNodeRecordMapper.selectIdsByExample() + return this.configurationNodeRecordMapper.selectIdsByExample() .leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord) .on( ConfigurationRecordDynamicSqlSupport.configurationNodeId, @@ -304,17 +320,8 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO { .execute() .stream() .collect(Utils.toSingleton()); - - return this.examConfigurationMapRecordMapper.selectByExample() - .where( - ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId, - isEqualTo(configNodeId)) - .build() - .execute() - .stream() - .map(record -> record.getExamId()) - .collect(Collectors.toList()); - }); + }) + .flatMap(this::getExamIdsForConfigNodeId); } private Result recordById(final Long id) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java index 8a14c061..dd1230f3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigIO.java @@ -111,7 +111,7 @@ public class ExamConfigIO { // get follow-up configurationId for given configurationNodeId final Long configurationId = this.configurationDAO - .getFollowupConfiguration(configurationNodeId) + .getConfigurationLastStableVersion(configurationNodeId) .getOrThrow().id; final Function configurationValueSupplier = diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationController.java index 76884c6a..fbd5960c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationController.java @@ -19,19 +19,25 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; @WebServiceProfile @@ -40,7 +46,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe public class ConfigurationController extends ReadonlyEntityController { private final ConfigurationDAO configurationDAO; + private final ExamConfigurationMapDAO examConfigurationMapDAO; private final ApplicationEventPublisher applicationEventPublisher; + private final ExamSessionService examSessionService; protected ConfigurationController( final AuthorizationService authorization, @@ -49,7 +57,9 @@ public class ConfigurationController extends ReadonlyEntityController this.configurationDAO.saveToHistory(config.configurationNodeId)) .flatMap(this.userActivityLogDAO::logSaveToHistory) .flatMap(this::publishConfigChanged) @@ -118,9 +131,53 @@ public class ConfigurationController extends ReadonlyEntityController publishConfigChanged(final Configuration config) { this.applicationEventPublisher.publishEvent(new ConfigurationChangedEvent(config.id)); return Result.of(config); } + private Result checkRunningExamIntegrity(final Configuration config) { + // check if the configuration is attached to an exam + final long activeConnections = this.examConfigurationMapDAO + .getExamIdsForConfigNodeId(config.configurationNodeId) + .getOrThrow() + .stream() + .flatMap(examId -> { + return this.examSessionService + .getConnectionData(examId) + .getOrThrow() + .stream(); + }) + .filter(this::isActiveConnection) + .count(); + + if (activeConnections > 0) { + throw new IllegalStateException("Integrity violation: There are currently active SEB Client connection."); + } else { + return Result.of(config); + } + } + + private boolean isActiveConnection(final ClientConnectionData connection) { + if (connection.clientConnection.status == ConnectionStatus.ESTABLISHED + || connection.clientConnection.status == ConnectionStatus.AUTHENTICATED) { + return true; + } + + if (connection.clientConnection.status == ConnectionStatus.CONNECTION_REQUESTED) { + final Long creationTime = connection.clientConnection.getCreationTime(); + final long millisecondsNow = Utils.getMillisecondsNow(); + if (millisecondsNow - creationTime < 30 * Constants.SECOND_IN_MILLIS) { + return true; + } + } + + return false; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java index 7ac42f57..27568c86 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java @@ -19,6 +19,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.BooleanUtils; import org.mybatis.dynamic.sql.SqlTable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,11 +72,11 @@ public class ConfigurationNodeController extends EntityController notifySaved(final ConfigurationValue entity) { - if (entity == null) { - return super.notifySaved(entity); - } - - this.applicationEventPublisher.publishEvent( - new ConfigurationChangedEvent(entity.configurationId)); - - return super.notifySaved(entity); } @Override @@ -141,11 +124,6 @@ public class ConfigurationValueController extends EntityController this.configurationValueDAO.saveTableValues(tableValue)) - .map(config -> { - this.applicationEventPublisher.publishEvent( - new ConfigurationChangedEvent(config.configurationId)); - return config; - }) .getOrThrow(); } diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index 19223ba0..11aaeeb6 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -34,6 +34,8 @@ 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 diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 0caf180c..e882efc1 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -435,12 +435,13 @@ sebserver.examconfig.list.action.no.modify.privilege=No Access: An Exam Configur sebserver.examconfig.action.list.new=Add Exam Configuration sebserver.examconfig.action.list.view=View Configuration sebserver.examconfig.action.list.modify=Edit Settings +sebserver.examconfig.action.list.modify=View Settings sebserver.examconfig.action.list.modify.properties=Edit Configuration sebserver.examconfig.action.view=View Configuration sebserver.examconfig.action.modify=Edit Settings sebserver.examconfig.action.modify.properties=Edit Configuration sebserver.examconfig.action.save=Save -sebserver.examconfig.action.saveToHistory=Save In History +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 @@ -455,6 +456,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.template=From Template sebserver.examconfig.form.status=Status sebserver.examconfig.form.config-key.title=Config Key @@ -935,13 +937,13 @@ sebserver.configtemplate.list.actions=Selected Template sebserver.configtemplate.info.pleaseSelect=Please select an exam configuration template first -sebserver.exam.configtemplate.action.list.new=New Template +sebserver.exam.configtemplate.action.list.new=Add Template sebserver.configtemplate.action.list.view=View Template sebserver.configtemplate.action.view=View Template sebserver.configtemplate.action.list.modify=Edit Template sebserver.configtemplate.action.modify=Edit Template -sebserver.configtemplate.form.title.new=New Template +sebserver.configtemplate.form.title.new=Add Template sebserver.configtemplate.form.title=Configuration Template sebserver.configtemplate.form.name=Name sebserver.configtemplate.form.description=Description diff --git a/src/main/resources/static/css/sebserver.css b/src/main/resources/static/css/sebserver.css index ffe90ca0..5998f173 100644 --- a/src/main/resources/static/css/sebserver.css +++ b/src/main/resources/static/css/sebserver.css @@ -244,17 +244,32 @@ Text[BORDER].error, Text[MULTI][BORDER].error { Text[BORDER]:focused, Text[MULTI][BORDER]:focused { border: 1px solid #4f7cb1; + border-radius: 0; box-shadow: none; } -Text:disabled, + +Text[BORDER]:disabled.inputreadonly, +Text[BORDER]:read-only.inputreadonly, +Text[MULTI][BORDER]:disabled.inputreadonly, +Text[MULTI][BORDER]:read-only.inputreadonly { + font: 12px Arial, Helvetica, sans-serif; + border: 1px solid #aaaaaa; + border-radius: 0; + padding: 3px 10px 3px 10px; + color: #aaaaaa; + background-repeat: repeat; + background-position: left top; + background-color: #ffffff; + background-image: none; + text-shadow: none; + box-shadow: none; +} + +Text:disabled, Text:read-only, -Text[BORDER]:disabled, -Text[BORDER]:read-only, -Text[MULTI]:disabled, -Text[MULTI]:read-only, -Text[MULTI][BORDER]:disabled, -Text[MULTI][BORDER]:read-only { +Text[MULTI]:disabled, +Text[MULTI]:read-only { box-shadow: none; background-color: #ffffff; border: none; @@ -460,6 +475,14 @@ Button[PUSH]:hover.header { cursor: pointer; } +Button[CHECK]:disabled { + color: #4a4a4a; +} + +Button[RADIO]:disabled { + color: #4a4a4a; +} + FileUpload, FileUpload:default, FileUpload:hover, diff --git a/src/main/resources/static/css/sms.css b/src/main/resources/static/css/sms.css index e774b541..343895a2 100644 --- a/src/main/resources/static/css/sms.css +++ b/src/main/resources/static/css/sms.css @@ -247,13 +247,18 @@ Text[BORDER]:focused, Text[MULTI][BORDER]:focused { box-shadow: none; } -Text:disabled, +Text[BORDER]:disabled, +Text[MULTI][BORDER]:disabled { + border: 1px solid #4a4a4a; + border-radius: 0; + box-shadow: none; +} + +Text:disabled, Text:read-only, -Text[BORDER]:disabled, +Text[MULTI]:disabled, Text[BORDER]:read-only, -Text[MULTI]:disabled, -Text[MULTI]:read-only, -Text[MULTI][BORDER]:disabled, +Text[MULTI]:read-only, Text[MULTI][BORDER]:read-only { box-shadow: none; background-color: #ffffff;