SEBSERV-97 implementation
This commit is contained in:
parent
eec4392f78
commit
c5008ad5c2
13 changed files with 266 additions and 38 deletions
|
@ -141,6 +141,7 @@ public final class API {
|
|||
public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration-node";
|
||||
public static final String CONFIGURATION_FOLLOWUP_PATH_SEGMENT = "/followup";
|
||||
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_SAVE_TO_HISTORY_PATH_SEGMENT = "/save-to-history";
|
||||
public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -183,7 +183,7 @@ public class ConfigTemplateAttributeForm implements TemplateComposer {
|
|||
configuration,
|
||||
new View(-1L, "template", 10, 0, templateId),
|
||||
attributeMapping,
|
||||
1, false);
|
||||
1, false, null);
|
||||
|
||||
final InputFieldBuilder inputFieldBuilder = this.examConfigurationService.getInputFieldBuilder(
|
||||
attribute.getConfigAttribute(),
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.List;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
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.TabFolder;
|
||||
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.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;
|
||||
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.auth.CurrentUser;
|
||||
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.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
|
@ -67,6 +70,8 @@ public class SEBSettingsForm implements TemplateComposer {
|
|||
"sebserver.examconfig.action.undo.success";
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
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 =
|
||||
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))
|
||||
.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(
|
||||
pageContext.getParent(),
|
||||
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))
|
||||
.getOrThrow();
|
||||
|
||||
final boolean readonly = pageContext.isReadonly() || configNode.status == ConfigurationStatus.IN_USE;
|
||||
final List<View> views = this.examConfigurationService.getViews(attributes);
|
||||
final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content);
|
||||
tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
|
||||
|
@ -133,7 +159,8 @@ public class SEBSettingsForm implements TemplateComposer {
|
|||
view,
|
||||
attributes,
|
||||
20,
|
||||
readonly);
|
||||
readonly,
|
||||
publishedMessagePanelViewCallback);
|
||||
viewContexts.add(viewContext);
|
||||
|
||||
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) {
|
||||
if (error instanceof APIMessageError) {
|
||||
try {
|
||||
|
|
|
@ -70,7 +70,7 @@ public interface ExamConfigurationService {
|
|||
/** Create to ViewContext to compose 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.
|
||||
*
|
||||
*
|
||||
* @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 view The View of the context
|
||||
|
@ -84,7 +84,8 @@ public interface ExamConfigurationService {
|
|||
View view,
|
||||
AttributeMapping attributeMapping,
|
||||
int rows,
|
||||
boolean readonly);
|
||||
boolean readonly,
|
||||
Runnable valueChageCallback);
|
||||
|
||||
Composite createViewGrid(
|
||||
Composite parent,
|
||||
|
|
|
@ -176,7 +176,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
|
|||
final View view,
|
||||
final AttributeMapping attributeMapping,
|
||||
final int rows,
|
||||
final boolean readonly) {
|
||||
final boolean readonly,
|
||||
final Runnable valueChageCallback) {
|
||||
|
||||
return new ViewContext(
|
||||
configuration,
|
||||
|
@ -187,7 +188,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
|
|||
pageContext,
|
||||
this.restService,
|
||||
this.jsonMapper,
|
||||
this.valueChangeRules),
|
||||
this.valueChangeRules,
|
||||
valueChageCallback),
|
||||
this.widgetFactory.getI18nSupport(),
|
||||
readonly);
|
||||
|
||||
|
@ -323,17 +325,20 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
|
|||
private final RestService restService;
|
||||
private final JSONMapper jsonMapper;
|
||||
private final Collection<ValueChangeRule> valueChangeRules;
|
||||
private final Runnable valueChageCallback;
|
||||
|
||||
protected ValueChangeListenerImpl(
|
||||
final PageContext pageContext,
|
||||
final RestService restService,
|
||||
final JSONMapper jsonMapper,
|
||||
final Collection<ValueChangeRule> valueChangeRules) {
|
||||
final Collection<ValueChangeRule> valueChangeRules,
|
||||
final Runnable valueChageCallback) {
|
||||
|
||||
this.pageContext = pageContext;
|
||||
this.restService = restService;
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.valueChangeRules = valueChangeRules;
|
||||
this.valueChageCallback = valueChageCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -367,6 +372,10 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
|
|||
} catch (final Exception e) {
|
||||
this.pageContext.notifySaveError(EntityType.CONFIGURATION_VALUE, e);
|
||||
}
|
||||
|
||||
if (this.valueChageCallback != null) {
|
||||
this.valueChageCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -330,12 +330,16 @@ public class WidgetFactory {
|
|||
}
|
||||
|
||||
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 GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||
composite.setLayoutData(gridData);
|
||||
final GridLayout gridLayout = new GridLayout(1, true);
|
||||
gridLayout.marginWidth = 20;
|
||||
gridLayout.marginHeight = 20;
|
||||
gridLayout.marginWidth = margin;
|
||||
gridLayout.marginHeight = margin;
|
||||
composite.setLayout(gridLayout);
|
||||
composite.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key);
|
||||
return composite;
|
||||
|
|
|
@ -124,4 +124,15 @@ public interface ExamConfigService {
|
|||
* @return The newly created Configuration instance */
|
||||
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);
|
||||
|
||||
}
|
||||
|
|
|
@ -98,11 +98,37 @@ public class ExamConfigIO {
|
|||
final Long institutionId,
|
||||
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()) {
|
||||
log.debug("Start export SEB plain XML configuration asynconously");
|
||||
}
|
||||
|
||||
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
|
||||
final List<ConfigurationAttribute> sortedAttributes = this.configurationAttributeDAO.getAllRootAttributes()
|
||||
|
@ -113,11 +139,6 @@ public class ExamConfigIO {
|
|||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// get follow-up configurationId for given configurationNodeId
|
||||
final Long configurationId = this.configurationDAO
|
||||
.getConfigurationLastStableVersion(configurationNodeId)
|
||||
.getOrThrow().id;
|
||||
|
||||
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier =
|
||||
getConfigurationValueSupplier(institutionId, configurationId);
|
||||
|
||||
|
|
|
@ -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.util.Result;
|
||||
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.sebconfig.ConfigurationFormat;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator;
|
||||
|
@ -61,6 +62,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
|
|||
private final ClientCredentialService clientCredentialService;
|
||||
private final ZipService zipService;
|
||||
private final SEBConfigEncryptionService sebConfigEncryptionService;
|
||||
private final ConfigurationDAO configurationDAO;
|
||||
|
||||
protected ExamConfigServiceImpl(
|
||||
final ExamConfigIO examConfigIO,
|
||||
|
@ -69,7 +71,8 @@ public class ExamConfigServiceImpl implements ExamConfigService {
|
|||
final Collection<ConfigurationValueValidator> validators,
|
||||
final ClientCredentialService clientCredentialService,
|
||||
final ZipService zipService,
|
||||
final SEBConfigEncryptionService sebConfigEncryptionService) {
|
||||
final SEBConfigEncryptionService sebConfigEncryptionService,
|
||||
final ConfigurationDAO configurationDAO) {
|
||||
|
||||
this.examConfigIO = examConfigIO;
|
||||
this.configurationAttributeDAO = configurationAttributeDAO;
|
||||
|
@ -78,6 +81,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
|
|||
this.clientCredentialService = clientCredentialService;
|
||||
this.zipService = zipService;
|
||||
this.sebConfigEncryptionService = sebConfigEncryptionService;
|
||||
this.configurationDAO = configurationDAO;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -249,29 +253,39 @@ public class ExamConfigServiceImpl implements ExamConfigService {
|
|||
final Long institutionId,
|
||||
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()) {
|
||||
log.debug("Start to stream plain JSON SEB Configuration data for Config-Key generation");
|
||||
}
|
||||
|
||||
if (true) {
|
||||
PipedOutputStream pout;
|
||||
PipedInputStream pin;
|
||||
try {
|
||||
pout = new PipedOutputStream();
|
||||
pin = new PipedInputStream(pout);
|
||||
this.examConfigIO.exportPlain(
|
||||
ConfigurationFormat.JSON,
|
||||
pout,
|
||||
institutionId,
|
||||
configurationNodeId);
|
||||
|
||||
final String json = IOUtils.toString(pin, "UTF-8");
|
||||
|
||||
log.trace("SEB Configuration JSON to create Config-Key: {}", json);
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to trace SEB Configuration JSON: ", e);
|
||||
}
|
||||
}
|
||||
// if (true) {
|
||||
// PipedOutputStream pout;
|
||||
// PipedInputStream pin;
|
||||
// try {
|
||||
// pout = new PipedOutputStream();
|
||||
// pin = new PipedInputStream(pout);
|
||||
// this.examConfigIO.exportPlain(
|
||||
// ConfigurationFormat.JSON,
|
||||
// pout,
|
||||
// institutionId,
|
||||
// configurationNodeId);
|
||||
//
|
||||
// final String json = IOUtils.toString(pin, "UTF-8");
|
||||
//
|
||||
// log.trace("SEB Configuration JSON to create Config-Key: {}", json);
|
||||
// } catch (final Exception e) {
|
||||
// log.error("Failed to trace SEB Configuration JSON: ", e);
|
||||
// }
|
||||
// }
|
||||
|
||||
PipedOutputStream pout = null;
|
||||
PipedInputStream pin = null;
|
||||
|
@ -279,11 +293,11 @@ public class ExamConfigServiceImpl implements ExamConfigService {
|
|||
pout = new PipedOutputStream();
|
||||
pin = new PipedInputStream(pout);
|
||||
|
||||
this.examConfigIO.exportPlain(
|
||||
ConfigurationFormat.JSON,
|
||||
this.examConfigIO.exportForConfigKeyGeneration(
|
||||
pout,
|
||||
institutionId,
|
||||
configurationNodeId);
|
||||
configurationNodeId,
|
||||
configId);
|
||||
|
||||
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(
|
||||
final ConfigurationFormat exportFormat,
|
||||
final OutputStream out,
|
||||
|
|
|
@ -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.ConfigurationStatus;
|
||||
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.user.UserLogActivityType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
|
@ -142,6 +143,28 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
.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(
|
||||
path = API.CONFIGURATION_COPY_PATH_SEGMENT,
|
||||
method = RequestMethod.PUT,
|
||||
|
|
|
@ -789,6 +789,7 @@ sebserver.examconfig.status.CONSTRUCTION=Under Construction
|
|||
sebserver.examconfig.status.READY_TO_USE=Ready To 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.subtitle=
|
||||
sebserver.examconfig.props.form.views.general=General
|
||||
|
|
Loading…
Reference in a new issue