diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigImportPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigImportPopup.java index 0a3c4d86..4d75ba9b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigImportPopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigImportPopup.java @@ -14,16 +14,21 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.APIMessage; +import ch.ethz.seb.sebserver.gbl.api.APIMessageError; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; @@ -50,7 +55,9 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNodeNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfigOnExistingConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportNewExamConfig; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfigHistory; import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @Lazy @Component @@ -59,8 +66,18 @@ public class SEBExamConfigImportPopup { private static final Logger log = LoggerFactory.getLogger(SEBExamConfigImportPopup.class); + private static final LocTextKey EXAM_CONFIG_IMPORT_TEXT = + new LocTextKey("sebserver.examconfig.action.import.config.text"); + private static final LocTextKey SEB_SETTINGS_IMPORT_TEXT = + new LocTextKey("sebserver.examconfig.action.import.settings.text"); + private static final LocTextKey SEB_SETTINGS_IMPORT_AUTO_PUBLISH = + new LocTextKey("sebserver.examconfig.action.import.auto-publish"); private final static PageMessageException MISSING_PASSWORD = new PageMessageException( new LocTextKey("sebserver.examconfig.action.import.missing-password")); + private static final LocTextKey MESSAGE_SAVE_INTEGRITY_VIOLATION = + new LocTextKey("sebserver.examconfig.action.saveToHistory.integrity-violation"); + + private final static String AUTO_PUBLISH_FLAG = "AUTO_PUBLISH_FLAG"; private final PageService pageService; @@ -152,13 +169,20 @@ public class SEBExamConfigImportPopup { restCall.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId); } - final Result configuration = restCall - .call(); - - if (!configuration.hasError()) { + final Result importResult = restCall.call(); + if (!importResult.hasError()) { context.publishInfo(SEBExamConfigForm.FORM_IMPORT_CONFIRM_TEXT_KEY); + + // Auto publish? + if (!newConfig && BooleanUtils.toBoolean(form.getFieldValue(AUTO_PUBLISH_FLAG))) { + this.pageService.getRestService() + .getBuilder(SaveExamConfigHistory.class) + .withURIVariable(API.PARAM_MODEL_ID, importResult.get().getModelId()) + .call() + .onError(error -> notifyErrorOnSave(error, context)); + } } else { - handleImportError(formHandle, configuration); + handleImportError(formHandle, importResult); } reloadPage(newConfig, context); @@ -295,8 +319,17 @@ public class SEBExamConfigImportPopup { @Override public Supplier> compose(final Composite parent) { - final Composite grid = this.pageService.getWidgetFactory() - .createPopupScrollComposite(parent); + final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); + final Composite grid = widgetFactory.createPopupScrollComposite(parent); + + final Label info = widgetFactory.labelLocalized( + grid, + (this.newConfig) ? EXAM_CONFIG_IMPORT_TEXT : SEB_SETTINGS_IMPORT_TEXT, + true); + final GridData gridData = new GridData(0, 0, this.newConfig, this.newConfig); + gridData.horizontalIndent = 10; + gridData.verticalIndent = 10; + info.setLayoutData(gridData); final ResourceService resourceService = this.pageService.getResourceService(); final List> examConfigTemplateResources = resourceService.getExamConfigTemplateResources(); @@ -309,6 +342,13 @@ public class SEBExamConfigImportPopup { null, API.SEB_FILE_EXTENSION)) + .addFieldIf( + () -> !this.newConfig, + () -> FormBuilder.checkbox( + AUTO_PUBLISH_FLAG, + SEB_SETTINGS_IMPORT_AUTO_PUBLISH, + Constants.FALSE_STRING)) + .addFieldIf( () -> this.newConfig, () -> FormBuilder.text( @@ -348,4 +388,22 @@ public class SEBExamConfigImportPopup { } } + private static void notifyErrorOnSave(final Exception error, final PageContext context) { + if (error instanceof APIMessageError) { + try { + final List errorMessages = ((APIMessageError) error).getErrorMessages(); + final APIMessage apiMessage = errorMessages.get(0); + if (APIMessage.ErrorMessage.INTEGRITY_VALIDATION.isOf(apiMessage)) { + context.publishPageMessage(new PageMessageException(MESSAGE_SAVE_INTEGRITY_VIOLATION)); + } else { + context.notifyUnexpectedError(error); + } + } catch (final PageMessageException e) { + throw e; + } catch (final Exception e) { + throw new RuntimeException(error); + } + } + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBSettingsForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBSettingsForm.java index 296a812d..c709f762 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBSettingsForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBSettingsForm.java @@ -32,7 +32,6 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessageError; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.EntityKey; -import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; 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; @@ -51,7 +50,6 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService; import ch.ethz.seb.sebserver.gui.service.remote.download.SEBExamConfigPlaintextDownload; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; -import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurations; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetSettingsPublished; @@ -122,13 +120,6 @@ public class SEBSettingsForm implements TemplateComposer { .getOrThrow(); final boolean readonly = pageContext.isReadonly() || configNode.status == ConfigurationStatus.IN_USE; - final boolean isAttachedToExam = !readonly && this.restService - .getBuilder(GetExamConfigMappingNames.class) - .withQueryParam(ExamConfigurationMap.FILTER_ATTR_CONFIG_ID, configNode.getModelId()) - .call() - .map(names -> names != null && !names.isEmpty()) - .getOr(Boolean.FALSE); - final Composite warningPanelAnchor = new Composite(pageContext.getParent(), SWT.NONE); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); warningPanelAnchor.setLayoutData(gridData); @@ -251,7 +242,7 @@ public class SEBSettingsForm implements TemplateComposer { .withExec(this.sebExamConfigImportPopup.importFunction( () -> String.valueOf(tabFolder.getSelectionIndex()))) .noEventPropagation() - .publishIf(() -> examConfigGrant.iw() && !readonly && !isAttachedToExam) + .publishIf(() -> examConfigGrant.iw() && !readonly) .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP) .withEntityKey(entityKey) @@ -298,7 +289,8 @@ public class SEBSettingsForm implements TemplateComposer { return; } - final boolean settingsPublished = this.pageService.getRestService() + final boolean settingsPublished = this.pageService + .getRestService() .getBuilder(GetSettingsPublished.class) .withURIVariable(API.PARAM_MODEL_ID, this.nodeId) .call() @@ -322,34 +314,7 @@ public class SEBSettingsForm implements TemplateComposer { } } -// private Runnable publishedMessagePanelViewCallback( -// final Composite parent, -// final String nodeId) { -// return () -> { -// final boolean settingsPublished = this.restService.getBuilder(GetSettingsPublished.class) -// .withURIVariable(API.PARAM_MODEL_ID, nodeId) -// .call() -// .onError(error -> log.warn("Failed to verify published settings. Cause: ", error.getMessage())) -// .map(result -> result.settingsPublished) -// .getOr(false); -// -// if (!settingsPublished) { -// if (parent.getChildren() != null && parent.getChildren().length == 0) { -// final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); -// final Composite warningPanel = widgetFactory.createWarningPanel(parent); -// widgetFactory.labelLocalized( -// warningPanel, -// CustomVariant.MESSAGE, -// UNPUBLISHED_MESSAGE_KEY); -// } -// } else if (parent.getChildren() != null && parent.getChildren().length > 0) { -// parent.getChildren()[0].dispose(); -// } -// parent.getParent().layout(); -// }; -// } - - private void notifyErrorOnSave(final Exception error, final PageContext context) { + public void notifyErrorOnSave(final Exception error, final PageContext context) { if (error instanceof APIMessageError) { try { final List errorMessages = ((APIMessageError) error).getErrorMessages(); 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 607249e7..eb199d52 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 @@ -231,6 +231,17 @@ public final class ViewContext { inputField); } + public String getValue(final String name) { + try { + final ConfigurationAttribute attributeByName = getAttributeByName(name); + final InputField inputField = this.inputFieldMapping.get(attributeByName.id); + return inputField.getValue(); + } catch (final Exception e) { + log.error("Failed to get attribute value: {}, cause {}", name, e.getMessage()); + return null; + } + } + public void setValue(final String name, final String value) { try { final ConfigurationAttribute attributeByName = getAttributeByName(name); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/FileUploadSelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/FileUploadSelection.java index 8ceb9253..0e172db9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/FileUploadSelection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/FileUploadSelection.java @@ -27,6 +27,7 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,7 +67,7 @@ public class FileUploadSelection extends Composite { super(parent, SWT.NONE); final GridLayout gridLayout = new GridLayout(2, false); - gridLayout.horizontalSpacing = 0; + gridLayout.horizontalSpacing = 5; gridLayout.marginHeight = 0; gridLayout.marginWidth = 0; gridLayout.verticalSpacing = 0; @@ -78,14 +79,15 @@ public class FileUploadSelection extends Composite { if (readonly) { this.fileName = new Label(this, SWT.NONE); this.fileName.setText(i18nSupport.getText(PLEASE_SELECT_TEXT)); - this.fileName.setLayoutData(new GridData()); + this.fileName.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, true)); this.fileUpload = null; this.uploadHandler = null; this.inputReceiver = null; } else { this.fileUpload = new FileUpload(this, SWT.NONE); + this.fileUpload.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, true)); this.fileUpload.setImage(WidgetFactory.ImageIcon.IMPORT.getImage(parent.getDisplay())); - this.fileUpload.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); + this.fileUpload.setToolTipText(Utils.formatLineBreaks(this.i18nSupport.getText(PLEASE_SELECT_TEXT))); this.inputReceiver = new InputReceiver(); this.uploadHandler = new FileUploadHandler(this.inputReceiver); @@ -96,29 +98,31 @@ public class FileUploadSelection extends Composite { this.fileName = new Label(this, SWT.NONE); this.fileName.setText(i18nSupport.getText(PLEASE_SELECT_TEXT)); - this.fileName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - - this.fileUpload.addListener(SWT.Selection, event -> { - this.selection = true; - final String fileName = FileUploadSelection.this.fileUpload.getFileName(); - if (fileName == null || !fileSupported(fileName)) { - if (FileUploadSelection.this.errorHandler != null) { - final String text = i18nSupport.getText(new LocTextKey( - "sebserver.overall.upload.unsupported.file", - this.supportedFileExtensions.toString()), - "Unsupported image file type selected"); - FileUploadSelection.this.errorHandler.accept(text); - } - return; - } - FileUploadSelection.this.fileUpload.submit(this.uploadHandler.getUploadUrl()); - FileUploadSelection.this.fileName.setText(fileName); - FileUploadSelection.this.errorHandler.accept(null); - }); + this.fileName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true)); + this.fileUpload.addListener(SWT.Selection, this::selectFile); + this.fileName.addListener(SWT.Selection, this::selectFile); } } + private void selectFile(final Event event) { + this.selection = true; + final String fileName = FileUploadSelection.this.fileUpload.getFileName(); + if (fileName == null || !fileSupported(fileName)) { + if (FileUploadSelection.this.errorHandler != null) { + final String text = this.i18nSupport.getText(new LocTextKey( + "sebserver.overall.upload.unsupported.file", + this.supportedFileExtensions.toString()), + "Unsupported image file type selected"); + FileUploadSelection.this.errorHandler.accept(text); + } + return; + } + FileUploadSelection.this.fileUpload.submit(this.uploadHandler.getUploadUrl()); + FileUploadSelection.this.fileName.setText(fileName); + FileUploadSelection.this.errorHandler.accept(null); + } + public void close() { if (this.inputReceiver != null) { this.inputReceiver.close(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java index 5e0e919f..bb72ee90 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java @@ -105,8 +105,8 @@ public class ExamSessionCacheService { key = "#exam.id") public Exam evict(final Exam exam) { - if (log.isDebugEnabled()) { - log.debug("Conditional eviction of running Exam from cache: {}", isRunning(exam)); + if (log.isTraceEnabled()) { + log.trace("Conditional eviction of running Exam from cache: {}", isRunning(exam)); } return exam; @@ -117,8 +117,8 @@ public class ExamSessionCacheService { key = "#examId") public Long evict(final Long examId) { - if (log.isDebugEnabled()) { - log.debug("Conditional eviction of running Exam from cache: {}", examId); + if (log.isTraceEnabled()) { + log.trace("Conditional eviction of running Exam from cache: {}", examId); } return examId; @@ -167,8 +167,8 @@ public class ExamSessionCacheService { cacheNames = CACHE_NAME_ACTIVE_CLIENT_CONNECTION, key = "#connectionToken") public void evictClientConnection(final String connectionToken) { - if (log.isDebugEnabled()) { - log.debug("Eviction of ClientConnectionData from cache: {}", connectionToken); + if (log.isTraceEnabled()) { + log.trace("Eviction of ClientConnectionData from cache: {}", connectionToken); } } @@ -197,8 +197,8 @@ public class ExamSessionCacheService { cacheNames = CACHE_NAME_SEB_CONFIG_EXAM, key = "#examId") public void evictDefaultSEBConfig(final Long examId) { - if (log.isDebugEnabled()) { - log.debug("Eviction of default SEB Configuration from cache for exam: {}", examId); + if (log.isTraceEnabled()) { + log.trace("Eviction of default SEB Configuration from cache for exam: {}", examId); } } @@ -208,6 +208,7 @@ public class ExamSessionCacheService { unless = "#result == null") @Transactional public ClientEventRecord getPingRecord(final String connectionToken) { + if (log.isDebugEnabled()) { log.debug("Verify ClientConnection for ping record to cache by connectionToken: {}", connectionToken); } @@ -240,8 +241,8 @@ public class ExamSessionCacheService { cacheNames = CACHE_NAME_PING_RECORD, key = "#connectionToken") public void evictPingRecord(final String connectionToken) { - if (log.isDebugEnabled()) { - log.debug("Eviction of ReusableClientEventRecord from cache for connection token: {}", connectionToken); + if (log.isTraceEnabled()) { + log.trace("Eviction of ReusableClientEventRecord from cache for connection token: {}", connectionToken); } } 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 2f81e6cd..d4957572 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 @@ -324,17 +324,19 @@ public class ConfigurationNodeController extends EntityController doImport = doImport(password, request, newConfig); if (doImport.hasError()) { - // rollback of the existing values this.configurationDAO.undo(newConfig.configurationNodeId); - } return doImport diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 66454897..c58c1caf 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -803,6 +803,10 @@ sebserver.examconfig.action.import-file-select=Import From File sebserver.examconfig.action.import-file-password=Password sebserver.examconfig.action.import-config.confirm=Exam Configuration successfully imported sebserver.examconfig.action.import.missing-password=Missing Password: The chosen exam configuration is password-protected.

Please choose it again and provide the correct password within the password field. +sebserver.examconfig.action.import.config.text=To import a valid SEB settings configuration file (.seb) as whole new exam configuration for SEB Server,
please select the file from the local directory and provide a password if the file is protected.
Please also give a name for the new exam configuration. +sebserver.examconfig.action.import.settings.text=To import a valid SEB settings configuration file (.seb) into an existing exam configuration,
please select the file from the local directory and provide a password if the file is protected.
Please note that this import will override the existing SEB settings of this exam configuration
and also try to publish the changes if selected. +sebserver.examconfig.action.import.auto-publish=Publish +sebserver.examconfig.action.import.auto-publish.tooltip=Try to automatically publish the imported changes sebserver.examconfig.action.state-change.confirm=This configuration is already attached to an exam.
Please note that changing an attached configuration will take effect on the exam when the configuration changes are saved

Are you sure to change this configuration to an editable state? sebserver.examconfig.message.error.file=Please select a valid SEB Exam Configuration File