SEBSERV-97 implementation

This commit is contained in:
anhefti 2021-01-12 15:14:05 +01:00
parent eec4392f78
commit c5008ad5c2
13 changed files with 266 additions and 38 deletions

View file

@ -141,6 +141,7 @@ public final class API {
public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration-node"; public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration-node";
public static final String CONFIGURATION_FOLLOWUP_PATH_SEGMENT = "/followup"; public static final String CONFIGURATION_FOLLOWUP_PATH_SEGMENT = "/followup";
public static final String CONFIGURATION_CONFIG_KEY_PATH_SEGMENT = "/configkey"; public static final String CONFIGURATION_CONFIG_KEY_PATH_SEGMENT = "/configkey";
public static final String CONFIGURATION_SETTINGS_PUBLISHED_PATH_SEGMENT = "/settings_published";
public static final String CONFIGURATION_ENDPOINT = "/configuration"; 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_SAVE_TO_HISTORY_PATH_SEGMENT = "/save-to-history";
public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo"; public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo";

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2021 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.gbl.model.sebconfig;
import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class SettingsPublished {
public static final String ATTR_SETTINGS_PUBLISHED = "settingsPublished";
@NotNull
@JsonProperty(ATTR_SETTINGS_PUBLISHED)
public final Boolean settingsPublished;
@JsonCreator
public SettingsPublished(@JsonProperty(ATTR_SETTINGS_PUBLISHED) final Boolean settingsPublished) {
this.settingsPublished = settingsPublished;
}
}

View file

@ -183,7 +183,7 @@ public class ConfigTemplateAttributeForm implements TemplateComposer {
configuration, configuration,
new View(-1L, "template", 10, 0, templateId), new View(-1L, "template", 10, 0, templateId),
attributeMapping, attributeMapping,
1, false); 1, false, null);
final InputFieldBuilder inputFieldBuilder = this.examConfigurationService.getInputFieldBuilder( final InputFieldBuilder inputFieldBuilder = this.examConfigurationService.getInputFieldBuilder(
attribute.getConfigAttribute(), attribute.getConfigAttribute(),

View file

@ -14,6 +14,7 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem; import org.eclipse.swt.widgets.TabItem;
@ -46,11 +47,13 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
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.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.GetExamConfigNode;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetSettingsPublished;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SEBExamConfigUndo; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SEBExamConfigUndo;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfigHistory; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfigHistory;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@Lazy @Lazy
@Component @Component
@ -67,6 +70,8 @@ public class SEBSettingsForm implements TemplateComposer {
"sebserver.examconfig.action.undo.success"; "sebserver.examconfig.action.undo.success";
private static final LocTextKey TITLE_TEXT_KEY = private static final LocTextKey TITLE_TEXT_KEY =
new LocTextKey("sebserver.examconfig.props.from.title"); new LocTextKey("sebserver.examconfig.props.from.title");
private static final LocTextKey UNPUBLISHED_MESSAGE_KEY =
new LocTextKey("sebserver.examconfig.props.from.unpublished.message");
private static final LocTextKey MESSAGE_SAVE_INTEGRITY_VIOLATION = private static final LocTextKey MESSAGE_SAVE_INTEGRITY_VIOLATION =
new LocTextKey("sebserver.examconfig.action.saveToHistory.integrity-violation"); new LocTextKey("sebserver.examconfig.action.saveToHistory.integrity-violation");
@ -101,6 +106,28 @@ public class SEBSettingsForm implements TemplateComposer {
.onError(error -> pageContext.notifyLoadError(EntityType.CONFIGURATION_NODE, error)) .onError(error -> pageContext.notifyLoadError(EntityType.CONFIGURATION_NODE, error))
.getOrThrow(); .getOrThrow();
final boolean settingsPublished = this.restService.getBuilder(GetSettingsPublished.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.onError(error -> log.warn("Failed to verify published settings. Cause: ", error.getMessage()))
.map(result -> result.settingsPublished)
.getOr(false);
final boolean readonly = pageContext.isReadonly() || configNode.status == ConfigurationStatus.IN_USE;
final Composite warningPanelAnchor = new Composite(pageContext.getParent(), SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
warningPanelAnchor.setLayoutData(gridData);
final GridLayout gridLayout = new GridLayout(1, true);
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
warningPanelAnchor.setLayout(gridLayout);
final Runnable publishedMessagePanelViewCallback = this.publishedMessagePanelViewCallback(
warningPanelAnchor,
entityKey.modelId);
if (!settingsPublished) {
publishedMessagePanelViewCallback.run();
}
final Composite content = widgetFactory.defaultPageLayout( final Composite content = widgetFactory.defaultPageLayout(
pageContext.getParent(), pageContext.getParent(),
new LocTextKey(TITLE_TEXT_KEY.name, Utils.truncateText(configNode.name, 30))); new LocTextKey(TITLE_TEXT_KEY.name, Utils.truncateText(configNode.name, 30)));
@ -120,7 +147,6 @@ public class SEBSettingsForm implements TemplateComposer {
.onError(error -> pageContext.notifyLoadError(EntityType.CONFIGURATION_ATTRIBUTE, error)) .onError(error -> pageContext.notifyLoadError(EntityType.CONFIGURATION_ATTRIBUTE, error))
.getOrThrow(); .getOrThrow();
final boolean readonly = pageContext.isReadonly() || configNode.status == ConfigurationStatus.IN_USE;
final List<View> views = this.examConfigurationService.getViews(attributes); final List<View> views = this.examConfigurationService.getViews(attributes);
final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content); final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content);
tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
@ -133,7 +159,8 @@ public class SEBSettingsForm implements TemplateComposer {
view, view,
attributes, attributes,
20, 20,
readonly); readonly,
publishedMessagePanelViewCallback);
viewContexts.add(viewContext); viewContexts.add(viewContext);
final Composite viewGrid = this.examConfigurationService.createViewGrid( final Composite viewGrid = this.examConfigurationService.createViewGrid(
@ -218,6 +245,32 @@ public class SEBSettingsForm implements TemplateComposer {
} }
} }
private Runnable publishedMessagePanelViewCallback(final Composite parent, final String nodeId) {
return () -> {
if (parent.getChildren() != null && parent.getChildren().length > 0) {
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) {
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
final Composite warningPanel = widgetFactory.createWarningPanel(parent);
widgetFactory.labelLocalized(
warningPanel,
CustomVariant.MESSAGE,
UNPUBLISHED_MESSAGE_KEY);
parent.getParent().layout();
}
};
}
private void notifyErrorOnSave(final Exception error, final PageContext context) { private void notifyErrorOnSave(final Exception error, final PageContext context) {
if (error instanceof APIMessageError) { if (error instanceof APIMessageError) {
try { try {

View file

@ -70,7 +70,7 @@ public interface ExamConfigurationService {
/** Create to ViewContext to compose a exam configuration property page, /** Create to ViewContext to compose a exam configuration property page,
* The ViewContext is the internal state of a exam configuration property page * The ViewContext is the internal state of a exam configuration property page
* and is passed through all composers while composing the page. * and is passed through all composers while composing the page.
* *
* @param pageContext The original PageContext that holds the state of the overall page. * @param pageContext The original PageContext that holds the state of the overall page.
* @param configuration The configuration on which the exam configuration property page is based on. * @param configuration The configuration on which the exam configuration property page is based on.
* @param view The View of the context * @param view The View of the context
@ -84,7 +84,8 @@ public interface ExamConfigurationService {
View view, View view,
AttributeMapping attributeMapping, AttributeMapping attributeMapping,
int rows, int rows,
boolean readonly); boolean readonly,
Runnable valueChageCallback);
Composite createViewGrid( Composite createViewGrid(
Composite parent, Composite parent,

View file

@ -176,7 +176,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
final View view, final View view,
final AttributeMapping attributeMapping, final AttributeMapping attributeMapping,
final int rows, final int rows,
final boolean readonly) { final boolean readonly,
final Runnable valueChageCallback) {
return new ViewContext( return new ViewContext(
configuration, configuration,
@ -187,7 +188,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
pageContext, pageContext,
this.restService, this.restService,
this.jsonMapper, this.jsonMapper,
this.valueChangeRules), this.valueChangeRules,
valueChageCallback),
this.widgetFactory.getI18nSupport(), this.widgetFactory.getI18nSupport(),
readonly); readonly);
@ -323,17 +325,20 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
private final RestService restService; private final RestService restService;
private final JSONMapper jsonMapper; private final JSONMapper jsonMapper;
private final Collection<ValueChangeRule> valueChangeRules; private final Collection<ValueChangeRule> valueChangeRules;
private final Runnable valueChageCallback;
protected ValueChangeListenerImpl( protected ValueChangeListenerImpl(
final PageContext pageContext, final PageContext pageContext,
final RestService restService, final RestService restService,
final JSONMapper jsonMapper, final JSONMapper jsonMapper,
final Collection<ValueChangeRule> valueChangeRules) { final Collection<ValueChangeRule> valueChangeRules,
final Runnable valueChageCallback) {
this.pageContext = pageContext; this.pageContext = pageContext;
this.restService = restService; this.restService = restService;
this.jsonMapper = jsonMapper; this.jsonMapper = jsonMapper;
this.valueChangeRules = valueChangeRules; this.valueChangeRules = valueChangeRules;
this.valueChageCallback = valueChageCallback;
} }
@Override @Override
@ -367,6 +372,10 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
} catch (final Exception e) { } catch (final Exception e) {
this.pageContext.notifySaveError(EntityType.CONFIGURATION_VALUE, e); this.pageContext.notifySaveError(EntityType.CONFIGURATION_VALUE, e);
} }
if (this.valueChageCallback != null) {
this.valueChageCallback.run();
}
} }
@Override @Override

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2021 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.SettingsPublished;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetSettingsPublished extends RestCall<SettingsPublished> {
public GetSettingsPublished() {
super(new TypeKey<>(
CallType.UNDEFINED,
EntityType.CONFIGURATION_NODE,
new TypeReference<SettingsPublished>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.CONFIGURATION_NODE_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.CONFIGURATION_SETTINGS_PUBLISHED_PATH_SEGMENT);
}
}

View file

@ -330,12 +330,16 @@ public class WidgetFactory {
} }
public Composite createWarningPanel(final Composite parent) { public Composite createWarningPanel(final Composite parent) {
return createWarningPanel(parent, 20);
}
public Composite createWarningPanel(final Composite parent, final int margin) {
final Composite composite = new Composite(parent, SWT.NONE); final Composite composite = new Composite(parent, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
composite.setLayoutData(gridData); composite.setLayoutData(gridData);
final GridLayout gridLayout = new GridLayout(1, true); final GridLayout gridLayout = new GridLayout(1, true);
gridLayout.marginWidth = 20; gridLayout.marginWidth = margin;
gridLayout.marginHeight = 20; gridLayout.marginHeight = margin;
composite.setLayout(gridLayout); composite.setLayout(gridLayout);
composite.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key); composite.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key);
return composite; return composite;

View file

@ -124,4 +124,15 @@ public interface ExamConfigService {
* @return The newly created Configuration instance */ * @return The newly created Configuration instance */
Result<Configuration> importFromSEBFile(Configuration config, InputStream input, CharSequence password); Result<Configuration> importFromSEBFile(Configuration config, InputStream input, CharSequence password);
/** Use this to check whether a specified ConfigurationNode has unpublished changes within the settings.
*
* This uses the Config Key of the actual and the follow-up settings to verify if there are changes made that
* are not published yet.
*
* @param institutionId the institutional id
* @param configurationNodeId the id if the ConfigurationNode
* @return true if there are unpublished changed in the SEB setting of the follow-up for the specified
* ConfigurationNode */
Result<Boolean> hasUnpublishedChanged(Long institutionId, Long configurationNodeId);
} }

View file

@ -98,11 +98,37 @@ public class ExamConfigIO {
final Long institutionId, final Long institutionId,
final Long configurationNodeId) throws Exception { final Long configurationNodeId) throws Exception {
exportPlain(exportFormat, out, institutionId, configurationNodeId, null);
}
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
void exportForConfigKeyGeneration(
final OutputStream out,
final Long institutionId,
final Long configurationNodeId,
final Long configId) throws Exception {
exportPlain(ConfigurationFormat.JSON, out, institutionId, configurationNodeId, configId);
}
private void exportPlain(
final ConfigurationFormat exportFormat,
final OutputStream out,
final Long institutionId,
final Long configurationNodeId,
final Long configId) throws Exception {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Start export SEB plain XML configuration asynconously"); log.debug("Start export SEB plain XML configuration asynconously");
} }
try { try {
// get configurationId for given configId or configurationNodeId last stable if null
final Long configurationId = (configId == null)
? this.configurationDAO
.getConfigurationLastStableVersion(configurationNodeId)
.getOrThrow().id
: configId;
// get all defined root configuration attributes prepared and sorted // get all defined root configuration attributes prepared and sorted
final List<ConfigurationAttribute> sortedAttributes = this.configurationAttributeDAO.getAllRootAttributes() final List<ConfigurationAttribute> sortedAttributes = this.configurationAttributeDAO.getAllRootAttributes()
@ -113,11 +139,6 @@ public class ExamConfigIO {
.sorted() .sorted()
.collect(Collectors.toList()); .collect(Collectors.toList());
// get follow-up configurationId for given configurationNodeId
final Long configurationId = this.configurationDAO
.getConfigurationLastStableVersion(configurationNodeId)
.getOrThrow().id;
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier = final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier =
getConfigurationValueSupplier(institutionId, configurationId); getConfigurationValueSupplier(institutionId, configurationId);

View file

@ -38,6 +38,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
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.ExamConfigurationMapDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator;
@ -61,6 +62,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
private final ClientCredentialService clientCredentialService; private final ClientCredentialService clientCredentialService;
private final ZipService zipService; private final ZipService zipService;
private final SEBConfigEncryptionService sebConfigEncryptionService; private final SEBConfigEncryptionService sebConfigEncryptionService;
private final ConfigurationDAO configurationDAO;
protected ExamConfigServiceImpl( protected ExamConfigServiceImpl(
final ExamConfigIO examConfigIO, final ExamConfigIO examConfigIO,
@ -69,7 +71,8 @@ public class ExamConfigServiceImpl implements ExamConfigService {
final Collection<ConfigurationValueValidator> validators, final Collection<ConfigurationValueValidator> validators,
final ClientCredentialService clientCredentialService, final ClientCredentialService clientCredentialService,
final ZipService zipService, final ZipService zipService,
final SEBConfigEncryptionService sebConfigEncryptionService) { final SEBConfigEncryptionService sebConfigEncryptionService,
final ConfigurationDAO configurationDAO) {
this.examConfigIO = examConfigIO; this.examConfigIO = examConfigIO;
this.configurationAttributeDAO = configurationAttributeDAO; this.configurationAttributeDAO = configurationAttributeDAO;
@ -78,6 +81,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
this.clientCredentialService = clientCredentialService; this.clientCredentialService = clientCredentialService;
this.zipService = zipService; this.zipService = zipService;
this.sebConfigEncryptionService = sebConfigEncryptionService; this.sebConfigEncryptionService = sebConfigEncryptionService;
this.configurationDAO = configurationDAO;
} }
@Override @Override
@ -249,29 +253,39 @@ public class ExamConfigServiceImpl implements ExamConfigService {
final Long institutionId, final Long institutionId,
final Long configurationNodeId) { final Long configurationNodeId) {
return this.configurationDAO
.getConfigurationLastStableVersion(configurationNodeId)
.flatMap(config -> generateConfigKey(institutionId, configurationNodeId, config.id));
}
private Result<String> generateConfigKey(
final Long institutionId,
final Long configurationNodeId,
final Long configId) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Start to stream plain JSON SEB Configuration data for Config-Key generation"); log.debug("Start to stream plain JSON SEB Configuration data for Config-Key generation");
} }
if (true) { // if (true) {
PipedOutputStream pout; // PipedOutputStream pout;
PipedInputStream pin; // PipedInputStream pin;
try { // try {
pout = new PipedOutputStream(); // pout = new PipedOutputStream();
pin = new PipedInputStream(pout); // pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain( // this.examConfigIO.exportPlain(
ConfigurationFormat.JSON, // ConfigurationFormat.JSON,
pout, // pout,
institutionId, // institutionId,
configurationNodeId); // configurationNodeId);
//
final String json = IOUtils.toString(pin, "UTF-8"); // final String json = IOUtils.toString(pin, "UTF-8");
//
log.trace("SEB Configuration JSON to create Config-Key: {}", json); // log.trace("SEB Configuration JSON to create Config-Key: {}", json);
} catch (final Exception e) { // } catch (final Exception e) {
log.error("Failed to trace SEB Configuration JSON: ", e); // log.error("Failed to trace SEB Configuration JSON: ", e);
} // }
} // }
PipedOutputStream pout = null; PipedOutputStream pout = null;
PipedInputStream pin = null; PipedInputStream pin = null;
@ -279,11 +293,11 @@ public class ExamConfigServiceImpl implements ExamConfigService {
pout = new PipedOutputStream(); pout = new PipedOutputStream();
pin = new PipedInputStream(pout); pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain( this.examConfigIO.exportForConfigKeyGeneration(
ConfigurationFormat.JSON,
pout, pout,
institutionId, institutionId,
configurationNodeId); configurationNodeId,
configId);
final String configKey = DigestUtils.sha256Hex(pin); final String configKey = DigestUtils.sha256Hex(pin);
@ -380,6 +394,23 @@ public class ExamConfigServiceImpl implements ExamConfigService {
}); });
} }
@Override
public Result<Boolean> hasUnpublishedChanged(final Long institutionId, final Long configurationNodeId) {
return Result.tryCatch(() -> {
final String followupKey = this.configurationDAO
.getFollowupConfiguration(configurationNodeId)
.flatMap(config -> generateConfigKey(institutionId, configurationNodeId, config.id))
.getOrThrow();
final String stableKey = this.configurationDAO
.getConfigurationLastStableVersion(configurationNodeId)
.flatMap(config -> generateConfigKey(institutionId, configurationNodeId, config.id))
.getOrThrow();
return !followupKey.equals(stableKey);
});
}
private void exportPlainOnly( private void exportPlainOnly(
final ConfigurationFormat exportFormat, final ConfigurationFormat exportFormat,
final OutputStream out, final OutputStream out,

View file

@ -50,6 +50,7 @@ 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;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus; 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.sebconfig.ConfigurationNode.ConfigurationType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SettingsPublished;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType; import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@ -142,6 +143,28 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
.getOrThrow(); .getOrThrow();
} }
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_SETTINGS_PUBLISHED_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SettingsPublished settingsPublished(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable final Long modelId) {
this.entityDAO
.byPK(modelId)
.flatMap(this::checkReadAccess)
.getOrThrow();
return this.sebExamConfigService.hasUnpublishedChanged(institutionId, modelId)
.map(flag -> new SettingsPublished(!flag))
.getOrThrow();
}
@RequestMapping( @RequestMapping(
path = API.CONFIGURATION_COPY_PATH_SEGMENT, path = API.CONFIGURATION_COPY_PATH_SEGMENT,
method = RequestMethod.PUT, method = RequestMethod.PUT,

View file

@ -789,6 +789,7 @@ sebserver.examconfig.status.CONSTRUCTION=Under Construction
sebserver.examconfig.status.READY_TO_USE=Ready To Use sebserver.examconfig.status.READY_TO_USE=Ready To Use
sebserver.examconfig.status.IN_USE=In Use sebserver.examconfig.status.IN_USE=In Use
sebserver.examconfig.props.from.unpublished.message=Note: There are unpublished changes to this Settings. Use 'Save/Publish Settings' to make sure the settings are active.
sebserver.examconfig.props.from.title=Exam Configuration Settings ({0}) sebserver.examconfig.props.from.title=Exam Configuration Settings ({0})
sebserver.examconfig.props.from.title.subtitle= sebserver.examconfig.props.from.title.subtitle=
sebserver.examconfig.props.form.views.general=General sebserver.examconfig.props.form.views.general=General