SEBSERV-137 implementation added texts and auto publish selection

This commit is contained in:
anhefti 2021-06-17 21:46:30 +02:00
parent 921e1959ce
commit ee0960602c
7 changed files with 127 additions and 82 deletions

View file

@ -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> configuration = restCall
.call();
if (!configuration.hasError()) {
final Result<Configuration> 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<FormHandle<ConfigurationNode>> 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<Tuple<String>> 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<APIMessage> 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);
}
}
}
}

View file

@ -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<APIMessage> errorMessages = ((APIMessageError) error).getErrorMessages();

View file

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

View file

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

View file

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

View file

@ -324,17 +324,19 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
this.entityDAO.byPK(modelId)
.flatMap(this.authorization::checkModify);
// final Configuration newConfig = this.configurationDAO
// .saveToHistory(modelId)
// .flatMap(this.configurationDAO::restoreToDefaultValues)
// .getOrThrow();
final Configuration newConfig = this.configurationDAO
.saveToHistory(modelId)
.flatMap(this.configurationDAO::restoreToDefaultValues)
.getFollowupConfiguration(modelId)
.getOrThrow();
final Result<Configuration> doImport = doImport(password, request, newConfig);
if (doImport.hasError()) {
// rollback of the existing values
this.configurationDAO.undo(newConfig.configurationNodeId);
}
return doImport

View file

@ -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.<br/><br/>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,<br/>please select the file from the local directory and provide a password if the file is protected.<br/>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,<br/>please select the file from the local directory and provide a password if the file is protected.<br/>Please note that this import will override the existing SEB settings of this exam configuration<br/>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.<br/>Please note that changing an attached configuration will take effect on the exam when the configuration changes are saved<br/><br/>Are you sure to change this configuration to an editable state?
sebserver.examconfig.message.error.file=Please select a valid SEB Exam Configuration File