SEBSERV-73 adapt new config handling with states in front- and back-end
This commit is contained in:
parent
555d9a34e0
commit
6eaef577d2
44 changed files with 713 additions and 212 deletions
|
@ -119,6 +119,7 @@ public final class API {
|
||||||
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";
|
||||||
|
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_RESTORE_FROM_HISTORY_PATH_SEGMENT = "/restore";
|
||||||
public static final String CONFIGURATION_VALUE_ENDPOINT = "/configuration_value";
|
public static final String CONFIGURATION_VALUE_ENDPOINT = "/configuration_value";
|
||||||
public static final String CONFIGURATION_TABLE_VALUE_PATH_SEGMENT = "/table";
|
public static final String CONFIGURATION_TABLE_VALUE_PATH_SEGMENT = "/table";
|
||||||
|
|
|
@ -25,6 +25,7 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||||
public final class ConfigurationNode implements GrantEntity {
|
public final class ConfigurationNode implements GrantEntity {
|
||||||
|
|
||||||
public static final Long DEFAULT_TEMPLATE_ID = 0L;
|
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_TEMPLATE_ID = "templateId";
|
||||||
public static final String FILTER_ATTR_DESCRIPTION = "description";
|
public static final String FILTER_ATTR_DESCRIPTION = "description";
|
||||||
|
|
|
@ -176,7 +176,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);
|
1, false);
|
||||||
|
|
||||||
final InputFieldBuilder inputFieldBuilder = this.examConfigurationService.getInputFieldBuilder(
|
final InputFieldBuilder inputFieldBuilder = this.examConfigurationService.getInputFieldBuilder(
|
||||||
attribute.getConfigAttribute(),
|
attribute.getConfigAttribute(),
|
||||||
|
|
|
@ -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.ConfigKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
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.ConfigurationType;
|
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.model.user.UserInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
@ -74,6 +75,8 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
new LocTextKey("sebserver.examconfig.form.name");
|
new LocTextKey("sebserver.examconfig.form.name");
|
||||||
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.form.description");
|
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 =
|
private static final LocTextKey FORM_STATUS_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.form.status");
|
new LocTextKey("sebserver.examconfig.form.status");
|
||||||
private static final LocTextKey FORM_IMPORT_TEXT_KEY =
|
private static final LocTextKey FORM_IMPORT_TEXT_KEY =
|
||||||
|
@ -113,6 +116,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
|
|
||||||
final UserInfo user = this.currentUser.get();
|
final UserInfo user = this.currentUser.get();
|
||||||
final EntityKey entityKey = pageContext.getEntityKey();
|
final EntityKey entityKey = pageContext.getEntityKey();
|
||||||
|
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||||
final boolean isNew = entityKey == null;
|
final boolean isNew = entityKey == null;
|
||||||
|
|
||||||
// get data or create new. Handle error if happen
|
// get data or create new. Handle error if happen
|
||||||
|
@ -169,6 +173,14 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
FORM_DESCRIPTION_TEXT_KEY,
|
FORM_DESCRIPTION_TEXT_KEY,
|
||||||
examConfig.description)
|
examConfig.description)
|
||||||
.asArea())
|
.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(
|
.addField(FormBuilder.singleSelection(
|
||||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||||
FORM_STATUS_TEXT_KEY,
|
FORM_STATUS_TEXT_KEY,
|
||||||
|
@ -178,6 +190,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
? this.restService.getRestCall(NewExamConfig.class)
|
? this.restService.getRestCall(NewExamConfig.class)
|
||||||
: this.restService.getRestCall(SaveExamConfig.class));
|
: this.restService.getRestCall(SaveExamConfig.class));
|
||||||
|
|
||||||
|
final boolean settingsReadonly = examConfig.status == ConfigurationStatus.IN_USE;
|
||||||
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
|
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
|
||||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||||
|
|
||||||
|
@ -190,7 +203,11 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
|
|
||||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_MODIFY)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_MODIFY)
|
||||||
.withEntityKey(entityKey)
|
.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)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_EXPORT_PLAIN_XML)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
|
@ -216,13 +233,13 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
.noEventPropagation()
|
.noEventPropagation()
|
||||||
.publishIf(() -> modifyGrant && isReadonly)
|
.publishIf(() -> modifyGrant && isReadonly)
|
||||||
|
|
||||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_SAVE)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_SAVE)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(formHandle::processFormSave)
|
.withExec(formHandle::processFormSave)
|
||||||
.ignoreMoveAwayFromEdit()
|
.ignoreMoveAwayFromEdit()
|
||||||
.publishIf(() -> !isReadonly)
|
.publishIf(() -> !isReadonly)
|
||||||
|
|
||||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_CANCEL_MODIFY)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(this.pageService.backToCurrentFunction())
|
.withExec(this.pageService.backToCurrentFunction())
|
||||||
.publishIf(() -> !isReadonly);
|
.publishIf(() -> !isReadonly);
|
||||||
|
|
|
@ -113,7 +113,6 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
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));
|
||||||
|
|
||||||
|
@ -124,7 +123,8 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
||||||
configuration,
|
configuration,
|
||||||
view,
|
view,
|
||||||
attributes,
|
attributes,
|
||||||
20);
|
20,
|
||||||
|
pageContext.isReadonly());
|
||||||
viewContexts.add(viewContext);
|
viewContexts.add(viewContext);
|
||||||
|
|
||||||
final Composite viewGrid = this.examConfigurationService.createViewGrid(
|
final Composite viewGrid = this.examConfigurationService.createViewGrid(
|
||||||
|
@ -141,6 +141,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
||||||
|
|
||||||
final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE);
|
final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE);
|
||||||
this.pageService.pageActionBuilder(pageContext.clearEntityKeys())
|
this.pageService.pageActionBuilder(pageContext.clearEntityKeys())
|
||||||
|
|
||||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_SAVE_TO_HISTORY)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_SAVE_TO_HISTORY)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(action -> {
|
.withExec(action -> {
|
||||||
|
@ -169,6 +170,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
||||||
|
|
||||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
|
.ignoreMoveAwayFromEdit()
|
||||||
.publish()
|
.publish()
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
|
@ -351,12 +351,12 @@ public enum ActionDefinition {
|
||||||
SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST(
|
SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST(
|
||||||
new LocTextKey("sebserver.examconfig.action.list.view"),
|
new LocTextKey("sebserver.examconfig.action.list.view"),
|
||||||
ImageIcon.SHOW,
|
ImageIcon.SHOW,
|
||||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW,
|
||||||
ActionCategory.SEB_EXAM_CONFIG_LIST),
|
ActionCategory.SEB_EXAM_CONFIG_LIST),
|
||||||
SEB_EXAM_CONFIG_VIEW_PROP(
|
SEB_EXAM_CONFIG_VIEW_PROP(
|
||||||
new LocTextKey("sebserver.examconfig.action.view"),
|
new LocTextKey("sebserver.examconfig.action.view"),
|
||||||
ImageIcon.SHOW,
|
ImageIcon.SHOW,
|
||||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW,
|
||||||
ActionCategory.FORM),
|
ActionCategory.FORM),
|
||||||
|
|
||||||
SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST(
|
SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST(
|
||||||
|
@ -374,15 +374,20 @@ public enum ActionDefinition {
|
||||||
ImageIcon.EDIT_SETTINGS,
|
ImageIcon.EDIT_SETTINGS,
|
||||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_EDIT,
|
PageStateDefinitionImpl.SEB_EXAM_CONFIG_EDIT,
|
||||||
ActionCategory.FORM),
|
ActionCategory.FORM),
|
||||||
SEB_EXAM_CONFIG_CANCEL_MODIFY(
|
SEB_EXAM_CONFIG_VIEW(
|
||||||
new LocTextKey("sebserver.overall.action.modify.cancel"),
|
new LocTextKey("sebserver.examconfig.action.view"),
|
||||||
ImageIcon.CANCEL,
|
ImageIcon.EDIT_SETTINGS,
|
||||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
||||||
ActionCategory.FORM),
|
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"),
|
new LocTextKey("sebserver.examconfig.action.save"),
|
||||||
ImageIcon.SAVE,
|
ImageIcon.SAVE,
|
||||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW,
|
||||||
ActionCategory.FORM),
|
ActionCategory.FORM),
|
||||||
SEB_EXAM_CONFIG_EXPORT_PLAIN_XML(
|
SEB_EXAM_CONFIG_EXPORT_PLAIN_XML(
|
||||||
new LocTextKey("sebserver.examconfig.action.export.plainxml"),
|
new LocTextKey("sebserver.examconfig.action.export.plainxml"),
|
||||||
|
|
|
@ -65,9 +65,10 @@ public enum PageStateDefinitionImpl implements PageStateDefinition {
|
||||||
SEB_CLIENT_CONFIG_EDIT(Type.FORM_EDIT, SebClientConfigForm.class, ActivityDefinition.SEB_CLIENT_CONFIG),
|
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_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_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_VIEW(Type.FORM_VIEW, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG),
|
||||||
SEB_EXAM_CONFIG_TEMPLATE_EDIT(Type.FORM_EDIT, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG),
|
SEB_EXAM_CONFIG_TEMPLATE_EDIT(Type.FORM_EDIT, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG),
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.eclipse.swt.widgets.Text;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||||
|
|
||||||
public final class TextFieldBuilder extends FieldBuilder<String> {
|
public final class TextFieldBuilder extends FieldBuilder<String> {
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
|
||||||
|
|
||||||
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
|
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
|
||||||
if (this.isArea) {
|
if (this.isArea) {
|
||||||
gridData.minimumHeight = 35;
|
gridData.minimumHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
|
||||||
}
|
}
|
||||||
textInput.setLayoutData(gridData);
|
textInput.setLayoutData(gridData);
|
||||||
if (StringUtils.isNoneBlank(this.value)) {
|
if (StringUtils.isNoneBlank(this.value)) {
|
||||||
|
|
|
@ -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.exam.GetExams;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames;
|
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.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.seb.examconfig.GetViews;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||||
|
@ -551,6 +552,19 @@ public class ResourceService {
|
||||||
k -> k.name));
|
k -> k.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Tuple<String>> 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<List<EntityName>> getExamConfigurationSelection() {
|
private Result<List<EntityName>> getExamConfigurationSelection() {
|
||||||
return this.restService.getBuilder(GetExamConfigMappingNames.class)
|
return this.restService.getBuilder(GetExamConfigMappingNames.class)
|
||||||
.withQueryParam(
|
.withQueryParam(
|
||||||
|
|
|
@ -54,7 +54,8 @@ public interface ExamConfigurationService {
|
||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
View view,
|
View view,
|
||||||
AttributeMapping attributeMapping,
|
AttributeMapping attributeMapping,
|
||||||
int rows);
|
int rows,
|
||||||
|
boolean readonly);
|
||||||
|
|
||||||
Composite createViewGrid(
|
Composite createViewGrid(
|
||||||
Composite parent,
|
Composite parent,
|
||||||
|
|
|
@ -104,6 +104,7 @@ public abstract class AbstractTableFieldBuilder implements InputFieldBuilder {
|
||||||
final TableContext tableContext) {
|
final TableContext tableContext) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final boolean readonly = tableContext.getViewContext().readonly;
|
||||||
final int currentTableWidth = table.getClientArea().width - TABLE_WIDTH_SPACE;
|
final int currentTableWidth = table.getClientArea().width - TABLE_WIDTH_SPACE;
|
||||||
final TableColumn[] columns = table.getColumns();
|
final TableColumn[] columns = table.getColumns();
|
||||||
final List<Orientation> orientations = tableContext
|
final List<Orientation> orientations = tableContext
|
||||||
|
@ -116,7 +117,7 @@ public abstract class AbstractTableFieldBuilder implements InputFieldBuilder {
|
||||||
.map(o -> o.width)
|
.map(o -> o.width)
|
||||||
.reduce(0, (acc, val) -> acc + val);
|
.reduce(0, (acc, val) -> acc + val);
|
||||||
final int widthUnit = currentTableWidth / div;
|
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);
|
columns[i].setWidth(widthUnit * orientations.get(i).width);
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
|
|
|
@ -154,7 +154,6 @@ interface CellFieldBuilderAdapter {
|
||||||
label.setAlignment(SWT.LEFT);
|
label.setAlignment(SWT.LEFT);
|
||||||
gridData.verticalIndent = 20;
|
gridData.verticalIndent = 20;
|
||||||
label.setLayoutData(gridData);
|
label.setLayoutData(gridData);
|
||||||
//label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -85,6 +85,9 @@ public class CheckBoxBuilder implements InputFieldBuilder {
|
||||||
viewContext.getOrientation(attribute.id),
|
viewContext.getOrientation(attribute.id),
|
||||||
checkbox);
|
checkbox);
|
||||||
|
|
||||||
|
if (viewContext.readonly) {
|
||||||
|
checkbox.setEnabled(false);
|
||||||
|
} else {
|
||||||
checkbox.addListener(
|
checkbox.addListener(
|
||||||
SWT.Selection,
|
SWT.Selection,
|
||||||
event -> viewContext.getValueChangeListener().valueChanged(
|
event -> viewContext.getValueChangeListener().valueChanged(
|
||||||
|
@ -92,6 +95,7 @@ public class CheckBoxBuilder implements InputFieldBuilder {
|
||||||
attribute,
|
attribute,
|
||||||
checkboxField.getValue(),
|
checkboxField.getValue(),
|
||||||
checkboxField.listIndex));
|
checkboxField.listIndex));
|
||||||
|
}
|
||||||
|
|
||||||
return checkboxField;
|
return checkboxField;
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,17 +201,28 @@ public class CompositeTableFieldBuilder extends AbstractTableFieldBuilder {
|
||||||
rowValues,
|
rowValues,
|
||||||
row);
|
row);
|
||||||
|
|
||||||
new ModalInputDialog<Map<Long, TableValue>>(
|
final ModalInputDialog<Map<Long, TableValue>> dialog = new ModalInputDialog<Map<Long, TableValue>>(
|
||||||
this.control.getShell(),
|
this.control.getShell(),
|
||||||
this.tableContext.getWidgetFactory())
|
this.tableContext.getWidgetFactory())
|
||||||
.setDialogWidth(500)
|
.setDialogWidth(500);
|
||||||
.open(
|
|
||||||
|
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),
|
new LocTextKey(ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + row),
|
||||||
rowVals -> applyFormValues(this.values, rowVals, selectionIndex),
|
rowVals -> applyFormValues(this.values, rowVals, selectionIndex),
|
||||||
() -> this.tableContext.getValueChangeListener()
|
() -> this.tableContext.getValueChangeListener()
|
||||||
.tableChanged(extractTableValue(this.values)),
|
.tableChanged(extractTableValue(this.values)),
|
||||||
builder);
|
builder);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<Integer, Map<Long, TableValue>> createRowIndexMap(final List<TableValue> tableValues) {
|
protected Map<Integer, Map<Long, TableValue>> createRowIndexMap(final List<TableValue> tableValues) {
|
||||||
|
|
|
@ -177,7 +177,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
|
||||||
final Configuration configuration,
|
final Configuration configuration,
|
||||||
final View view,
|
final View view,
|
||||||
final AttributeMapping attributeMapping,
|
final AttributeMapping attributeMapping,
|
||||||
final int rows) {
|
final int rows,
|
||||||
|
final boolean readonly) {
|
||||||
|
|
||||||
return new ViewContext(
|
return new ViewContext(
|
||||||
configuration,
|
configuration,
|
||||||
|
@ -189,7 +190,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
|
||||||
this.restService,
|
this.restService,
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
this.valueChangeRules),
|
this.valueChangeRules),
|
||||||
this.widgetFactory.getI18nSupport());
|
this.widgetFactory.getI18nSupport(),
|
||||||
|
readonly);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,11 +92,17 @@ public class InlineTableFieldBuilder implements InputFieldBuilder {
|
||||||
viewContext.getOrientation(attribute.id),
|
viewContext.getOrientation(attribute.id),
|
||||||
gridTable);
|
gridTable);
|
||||||
|
|
||||||
|
if (viewContext.readonly) {
|
||||||
|
gridTable.setEnabled(false);
|
||||||
|
gridTable.setListener(event -> {
|
||||||
|
});
|
||||||
|
} else {
|
||||||
gridTable.setListener(event -> viewContext.getValueChangeListener().valueChanged(
|
gridTable.setListener(event -> viewContext.getValueChangeListener().valueChanged(
|
||||||
viewContext,
|
viewContext,
|
||||||
attribute,
|
attribute,
|
||||||
inlineTableInputField.getValue(),
|
inlineTableInputField.getValue(),
|
||||||
inlineTableInputField.listIndex));
|
inlineTableInputField.listIndex));
|
||||||
|
}
|
||||||
|
|
||||||
return inlineTableInputField;
|
return inlineTableInputField;
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,9 @@ public class MultiCheckboxSelection extends SelectionFieldBuilder implements Inp
|
||||||
orientation,
|
orientation,
|
||||||
selection);
|
selection);
|
||||||
|
|
||||||
|
if (viewContext.readonly) {
|
||||||
|
selection.setEnabled(false);
|
||||||
|
} else {
|
||||||
selection.setSelectionListener(event -> {
|
selection.setSelectionListener(event -> {
|
||||||
multiSelectionCheckboxInputField.clearError();
|
multiSelectionCheckboxInputField.clearError();
|
||||||
viewContext.getValueChangeListener().valueChanged(
|
viewContext.getValueChangeListener().valueChanged(
|
||||||
|
@ -76,6 +79,7 @@ public class MultiCheckboxSelection extends SelectionFieldBuilder implements Inp
|
||||||
multiSelectionCheckboxInputField.getValue(),
|
multiSelectionCheckboxInputField.getValue(),
|
||||||
multiSelectionCheckboxInputField.listIndex);
|
multiSelectionCheckboxInputField.listIndex);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return multiSelectionCheckboxInputField;
|
return multiSelectionCheckboxInputField;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Hex;
|
import org.apache.commons.codec.binary.Hex;
|
||||||
|
import org.eclipse.rap.rwt.RWT;
|
||||||
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.widgets.Composite;
|
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.InputField;
|
||||||
import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder;
|
import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
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
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
|
@ -67,7 +70,8 @@ public class PassworFieldBuilder implements InputFieldBuilder {
|
||||||
.createInnerGrid(parent, attribute, orientation);
|
.createInnerGrid(parent, attribute, orientation);
|
||||||
|
|
||||||
final Text passwordInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD);
|
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 Text confirmInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD);
|
||||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||||
gridData.verticalIndent = 14;
|
gridData.verticalIndent = 14;
|
||||||
|
@ -80,6 +84,14 @@ public class PassworFieldBuilder implements InputFieldBuilder {
|
||||||
confirmInput,
|
confirmInput,
|
||||||
Form.createErrorLabel(innerGrid));
|
Form.createErrorLabel(innerGrid));
|
||||||
|
|
||||||
|
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 -> {
|
final Listener valueChangeEventListener = event -> {
|
||||||
passwordInputField.clearError();
|
passwordInputField.clearError();
|
||||||
|
|
||||||
|
@ -111,6 +123,7 @@ public class PassworFieldBuilder implements InputFieldBuilder {
|
||||||
passwordInput.addListener(SWT.Traverse, valueChangeEventListener);
|
passwordInput.addListener(SWT.Traverse, valueChangeEventListener);
|
||||||
confirmInput.addListener(SWT.FocusOut, valueChangeEventListener);
|
confirmInput.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||||
confirmInput.addListener(SWT.Traverse, valueChangeEventListener);
|
confirmInput.addListener(SWT.Traverse, valueChangeEventListener);
|
||||||
|
}
|
||||||
return passwordInputField;
|
return passwordInputField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,9 @@ public class RadioSelectionFieldBuilder extends SelectionFieldBuilder implements
|
||||||
selection,
|
selection,
|
||||||
null);
|
null);
|
||||||
|
|
||||||
|
if (viewContext.readonly) {
|
||||||
|
selection.setEnabled(false);
|
||||||
|
} else {
|
||||||
selection.setSelectionListener(event -> {
|
selection.setSelectionListener(event -> {
|
||||||
radioSelectionInputField.clearError();
|
radioSelectionInputField.clearError();
|
||||||
viewContext.getValueChangeListener().valueChanged(
|
viewContext.getValueChangeListener().valueChanged(
|
||||||
|
@ -79,6 +82,7 @@ public class RadioSelectionFieldBuilder extends SelectionFieldBuilder implements
|
||||||
radioSelectionInputField.getValue(),
|
radioSelectionInputField.getValue(),
|
||||||
radioSelectionInputField.listIndex);
|
radioSelectionInputField.listIndex);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return radioSelectionInputField;
|
return radioSelectionInputField;
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,9 @@ public class SingleSelectionFieldBuilder extends SelectionFieldBuilder implement
|
||||||
selection,
|
selection,
|
||||||
Form.createErrorLabel(innerGrid));
|
Form.createErrorLabel(innerGrid));
|
||||||
|
|
||||||
|
if (viewContext.readonly) {
|
||||||
|
selection.setEnabled(false);
|
||||||
|
} else {
|
||||||
selection.setSelectionListener(event -> {
|
selection.setSelectionListener(event -> {
|
||||||
singleSelectionInputField.clearError();
|
singleSelectionInputField.clearError();
|
||||||
viewContext.getValueChangeListener().valueChanged(
|
viewContext.getValueChangeListener().valueChanged(
|
||||||
|
@ -84,6 +87,7 @@ public class SingleSelectionFieldBuilder extends SelectionFieldBuilder implement
|
||||||
singleSelectionInputField.getValue(),
|
singleSelectionInputField.getValue(),
|
||||||
singleSelectionInputField.listIndex);
|
singleSelectionInputField.listIndex);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return singleSelectionInputField;
|
return singleSelectionInputField;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,9 @@ public class SliderFieldBuilder implements InputFieldBuilder {
|
||||||
orientation,
|
orientation,
|
||||||
slider);
|
slider);
|
||||||
|
|
||||||
|
if (viewContext.readonly) {
|
||||||
|
slider.setEnabled(false);
|
||||||
|
} else {
|
||||||
final Listener valueChangeEventListener = event -> {
|
final Listener valueChangeEventListener = event -> {
|
||||||
inputField.clearError();
|
inputField.clearError();
|
||||||
viewContext.getValueChangeListener().valueChanged(
|
viewContext.getValueChangeListener().valueChanged(
|
||||||
|
@ -84,6 +87,7 @@ public class SliderFieldBuilder implements InputFieldBuilder {
|
||||||
|
|
||||||
slider.addListener(SWT.FocusOut, valueChangeEventListener);
|
slider.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||||
slider.addListener(SWT.Traverse, valueChangeEventListener);
|
slider.addListener(SWT.Traverse, valueChangeEventListener);
|
||||||
|
}
|
||||||
return inputField;
|
return inputField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@ public class TableFieldBuilder extends AbstractTableFieldBuilder {
|
||||||
tableContext,
|
tableContext,
|
||||||
table);
|
table);
|
||||||
|
|
||||||
|
if (!viewContext.readonly) {
|
||||||
TableColumn column = new TableColumn(table, SWT.NONE);
|
TableColumn column = new TableColumn(table, SWT.NONE);
|
||||||
column.setImage(ImageIcon.ADD_BOX.getImage(parent.getDisplay()));
|
column.setImage(ImageIcon.ADD_BOX.getImage(parent.getDisplay()));
|
||||||
|
|
||||||
|
@ -109,6 +110,7 @@ public class TableFieldBuilder extends AbstractTableFieldBuilder {
|
||||||
tableField.deleteRow(selectionIndex);
|
tableField.deleteRow(selectionIndex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setSelectionListener(table, tableField);
|
setSelectionListener(table, tableField);
|
||||||
return tableField;
|
return tableField;
|
||||||
|
@ -163,8 +165,6 @@ public class TableFieldBuilder extends AbstractTableFieldBuilder {
|
||||||
protected void addTableRow(final int index, final Map<Long, TableValue> rowValues) {
|
protected void addTableRow(final int index, final Map<Long, TableValue> rowValues) {
|
||||||
new TableItem(this.control, SWT.NONE);
|
new TableItem(this.control, SWT.NONE);
|
||||||
applyTableRowValues(index);
|
applyTableRowValues(index);
|
||||||
|
|
||||||
// TODO try to add delete button within table row?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.examconfig.impl;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.eclipse.rap.rwt.RWT;
|
||||||
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.widgets.Composite;
|
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.I18nSupport;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
|
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
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
|
@ -74,7 +77,7 @@ public class TextFieldBuilder implements InputFieldBuilder {
|
||||||
}
|
}
|
||||||
case TEXT_AREA: {
|
case TEXT_AREA: {
|
||||||
text = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.MULTI);
|
text = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.MULTI);
|
||||||
gridData.heightHint = 50;
|
gridData.minimumHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -101,6 +104,13 @@ public class TextFieldBuilder implements InputFieldBuilder {
|
||||||
text,
|
text,
|
||||||
Form.createErrorLabel(innerGrid));
|
Form.createErrorLabel(innerGrid));
|
||||||
|
|
||||||
|
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 -> {
|
final Listener valueChangeEventListener = event -> {
|
||||||
textInputField.clearError();
|
textInputField.clearError();
|
||||||
viewContext.getValueChangeListener().valueChanged(
|
viewContext.getValueChangeListener().valueChanged(
|
||||||
|
@ -112,6 +122,7 @@ public class TextFieldBuilder implements InputFieldBuilder {
|
||||||
|
|
||||||
text.addListener(SWT.FocusOut, valueChangeEventListener);
|
text.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||||
text.addListener(SWT.Traverse, valueChangeEventListener);
|
text.addListener(SWT.Traverse, valueChangeEventListener);
|
||||||
|
}
|
||||||
return textInputField;
|
return textInputField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ public final class ViewContext {
|
||||||
final Map<Long, InputField> inputFieldMapping;
|
final Map<Long, InputField> inputFieldMapping;
|
||||||
final ValueChangeListener valueChangeListener;
|
final ValueChangeListener valueChangeListener;
|
||||||
final I18nSupport i18nSupport;
|
final I18nSupport i18nSupport;
|
||||||
|
final boolean readonly;
|
||||||
|
|
||||||
ViewContext(
|
ViewContext(
|
||||||
final Configuration configuration,
|
final Configuration configuration,
|
||||||
|
@ -44,7 +45,8 @@ public final class ViewContext {
|
||||||
final int rows,
|
final int rows,
|
||||||
final AttributeMapping attributeContext,
|
final AttributeMapping attributeContext,
|
||||||
final ValueChangeListener valueChangeListener,
|
final ValueChangeListener valueChangeListener,
|
||||||
final I18nSupport i18nSupport) {
|
final I18nSupport i18nSupport,
|
||||||
|
final boolean readonly) {
|
||||||
|
|
||||||
Objects.requireNonNull(configuration);
|
Objects.requireNonNull(configuration);
|
||||||
Objects.requireNonNull(view);
|
Objects.requireNonNull(view);
|
||||||
|
@ -59,6 +61,7 @@ public final class ViewContext {
|
||||||
this.inputFieldMapping = new HashMap<>();
|
this.inputFieldMapping = new HashMap<>();
|
||||||
this.valueChangeListener = valueChangeListener;
|
this.valueChangeListener = valueChangeListener;
|
||||||
this.i18nSupport = i18nSupport;
|
this.i18nSupport = i18nSupport;
|
||||||
|
this.readonly = readonly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public I18nSupport getI18nSupport() {
|
public I18nSupport getI18nSupport() {
|
||||||
|
|
|
@ -49,7 +49,6 @@ public class ModalInputDialog<T> extends Dialog {
|
||||||
|
|
||||||
super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL | SWT.CLOSE);
|
super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL | SWT.CLOSE);
|
||||||
this.widgetFactory = widgetFactory;
|
this.widgetFactory = widgetFactory;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModalInputDialog<T> setDialogWidth(final int dialogWidth) {
|
public ModalInputDialog<T> setDialogWidth(final int dialogWidth) {
|
||||||
|
@ -161,7 +160,6 @@ public class ModalInputDialog<T> extends Dialog {
|
||||||
shell.pack();
|
shell.pack();
|
||||||
final Rectangle bounds = shell.getBounds();
|
final Rectangle bounds = shell.getBounds();
|
||||||
final Rectangle bounds2 = super.getParent().getDisplay().getBounds();
|
final Rectangle bounds2 = super.getParent().getDisplay().getBounds();
|
||||||
//bounds.width = bounds.width;
|
|
||||||
bounds.x = (bounds2.width - bounds.width) / 2;
|
bounds.x = (bounds2.width - bounds.width) / 2;
|
||||||
bounds.y = (bounds2.height - bounds.height) / 2;
|
bounds.y = (bounds2.height - bounds.height) / 2;
|
||||||
shell.setBounds(bounds);
|
shell.setBounds(bounds);
|
||||||
|
|
|
@ -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<ConfigurationNode> {
|
||||||
|
|
||||||
|
public CopyConfiguration() {
|
||||||
|
super(new TypeKey<>(
|
||||||
|
CallType.SAVE,
|
||||||
|
EntityType.CONFIGURATION_NODE,
|
||||||
|
new TypeReference<ConfigurationNode>() {
|
||||||
|
}),
|
||||||
|
HttpMethod.POST,
|
||||||
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
|
API.CONFIGURATION_NODE_ENDPOINT
|
||||||
|
+ API.MODEL_ID_VAR_PATH_SEGMENT
|
||||||
|
+ API.CONFIGURATION_COPY_PATH_SEGMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<ConfigurationNode> {
|
||||||
|
|
||||||
|
public GetExamConfigNodes() {
|
||||||
|
super(
|
||||||
|
GetExamConfigNodePage.class,
|
||||||
|
EntityType.CONFIGURATION_NODE,
|
||||||
|
new TypeReference<List<ConfigurationNode>>() {
|
||||||
|
},
|
||||||
|
API.CONFIGURATION_NODE_ENDPOINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.eclipse.rap.rwt.RWT;
|
||||||
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.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.gbl.model.sebconfig.AttributeType;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
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;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
|
||||||
|
|
||||||
public class GridTable extends Composite {
|
public class GridTable extends Composite {
|
||||||
|
@ -394,6 +396,7 @@ public class GridTable extends Composite {
|
||||||
|
|
||||||
TextField(final Composite parent, final ColumnDef columnDef, final Listener listener) {
|
TextField(final Composite parent, final ColumnDef columnDef, final Listener listener) {
|
||||||
this._textField = new Text(parent, SWT.LEFT | SWT.BORDER);
|
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._textField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
|
||||||
this.columnDef = columnDef;
|
this.columnDef = columnDef;
|
||||||
this._textField.addListener(SWT.FocusOut, listener);
|
this._textField.addListener(SWT.FocusOut, listener);
|
||||||
|
|
|
@ -58,6 +58,9 @@ public class WidgetFactory {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(WidgetFactory.class);
|
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 {
|
public enum ImageIcon {
|
||||||
MAXIMIZE("maximize.png"),
|
MAXIMIZE("maximize.png"),
|
||||||
MINIMIZE("minimize.png"),
|
MINIMIZE("minimize.png"),
|
||||||
|
@ -127,7 +130,8 @@ public class WidgetFactory {
|
||||||
TITLE_LABEL("head"),
|
TITLE_LABEL("head"),
|
||||||
|
|
||||||
MESSAGE("message"),
|
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) {
|
public Text textInput(final Composite content, final boolean password, final boolean readonly) {
|
||||||
return readonly
|
return readonly
|
||||||
? new Text(content, SWT.LEFT | SWT.BORDER)
|
? new Text(content, SWT.LEFT)
|
||||||
: new Text(content, (password)
|
: new Text(content, (password)
|
||||||
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
|
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
|
||||||
: SWT.LEFT | SWT.BORDER);
|
: SWT.LEFT | SWT.BORDER);
|
||||||
|
|
|
@ -62,4 +62,10 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
|
||||||
* @return the current follow-up configuration */
|
* @return the current follow-up configuration */
|
||||||
Result<Configuration> getFollowupConfiguration(Long configNodeId);
|
Result<Configuration> 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<Configuration> getConfigurationLastStableVersion(Long configNodeId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,17 @@
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
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;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
|
||||||
|
|
||||||
public interface ConfigurationNodeDAO extends
|
public interface ConfigurationNodeDAO extends
|
||||||
EntityDAO<ConfigurationNode, ConfigurationNode>,
|
EntityDAO<ConfigurationNode, ConfigurationNode>,
|
||||||
BulkActionSupportDAO<ConfigurationNode> {
|
BulkActionSupportDAO<ConfigurationNode> {
|
||||||
|
|
||||||
|
Result<ConfigurationNode> createCopy(
|
||||||
|
Long institutionId,
|
||||||
|
String newOwner,
|
||||||
|
Long configurationNodeId,
|
||||||
|
boolean withHistory);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,16 @@ public interface ExamConfigurationMapDAO extends
|
||||||
* the Exam for a specified user identifier */
|
* the Exam for a specified user identifier */
|
||||||
Result<Long> getUserConfigurationIdForExam(final Long examId, final String userId);
|
Result<Long> 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<Collection<Long>> getExamIdsForConfigNodeId(Long configurationNodeId);
|
||||||
|
|
||||||
/** Get all id of Exams that has a relation to the given configuration id.
|
/** Get all id of Exams that has a relation to the given configuration id.
|
||||||
*
|
*
|
||||||
* @param configurationId
|
* @param configurationId
|
||||||
* @return */
|
* @return Result referencing the List of exam identifiers (PK) for a given configuration identifier */
|
||||||
Result<Collection<Long>> getExamIdsForConfigId(Long configurationId);
|
Result<Collection<Long>> getExamIdsForConfigId(Long configurationId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ class ConfigurationDAOBatchService {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ConfigurationDAOBatchService.class);
|
private static final Logger log = LoggerFactory.getLogger(ConfigurationDAOBatchService.class);
|
||||||
|
|
||||||
|
public static final String INITIAL_VERSION_NAME = "v0";
|
||||||
|
|
||||||
private final ConfigurationNodeRecordMapper batchConfigurationNodeRecordMapper;
|
private final ConfigurationNodeRecordMapper batchConfigurationNodeRecordMapper;
|
||||||
private final ConfigurationValueRecordMapper batchConfigurationValueRecordMapper;
|
private final ConfigurationValueRecordMapper batchConfigurationValueRecordMapper;
|
||||||
private final ConfigurationAttributeRecordMapper batchConfigurationAttributeRecordMapper;
|
private final ConfigurationAttributeRecordMapper batchConfigurationAttributeRecordMapper;
|
||||||
|
@ -187,23 +189,13 @@ class ConfigurationDAOBatchService {
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// get current versions count
|
// 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
|
// close follow-up configuration to save in history
|
||||||
final ConfigurationRecord configUpdate = new ConfigurationRecord(
|
final ConfigurationRecord configUpdate = new ConfigurationRecord(
|
||||||
followupConfig.getId(),
|
followupConfig.getId(),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
"v" + versions,
|
generateVersionName(configurationNodeId),
|
||||||
DateTime.now(DateTimeZone.UTC),
|
DateTime.now(DateTimeZone.UTC),
|
||||||
BooleanUtils.toInteger(false));
|
BooleanUtils.toInteger(false));
|
||||||
this.batchConfigurationRecordMapper.updateByPrimaryKeySelective(configUpdate);
|
this.batchConfigurationRecordMapper.updateByPrimaryKeySelective(configUpdate);
|
||||||
|
@ -240,6 +232,20 @@ class ConfigurationDAOBatchService {
|
||||||
.flatMap(ConfigurationDAOImpl::toDomainModel);
|
.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<Configuration> undo(final Long configurationNodeId) {
|
Result<Configuration> undo(final Long configurationNodeId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
// get all configurations of the node
|
// get all configurations of the node
|
||||||
|
@ -311,6 +317,69 @@ class ConfigurationDAOBatchService {
|
||||||
.flatMap(ConfigurationDAOImpl::toDomainModel);
|
.flatMap(ConfigurationDAOImpl::toDomainModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<Configuration> 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) {
|
private ConfigurationRecord getFollowupConfigurationRecord(final Long configurationNodeId) {
|
||||||
return this.batchConfigurationRecordMapper
|
return this.batchConfigurationRecordMapper
|
||||||
.selectByExample()
|
.selectByExample()
|
||||||
|
@ -454,7 +523,7 @@ class ConfigurationDAOBatchService {
|
||||||
null,
|
null,
|
||||||
config.institutionId,
|
config.institutionId,
|
||||||
config.id,
|
config.id,
|
||||||
"v0", // TODO?
|
INITIAL_VERSION_NAME,
|
||||||
DateTime.now(DateTimeZone.UTC),
|
DateTime.now(DateTimeZone.UTC),
|
||||||
BooleanUtils.toInteger(false));
|
BooleanUtils.toInteger(false));
|
||||||
|
|
||||||
|
@ -547,7 +616,7 @@ class ConfigurationDAOBatchService {
|
||||||
}
|
}
|
||||||
|
|
||||||
final Long configurationId = this.batchConfigurationRecordMapper.selectByExample()
|
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)))
|
.and(ConfigurationRecordDynamicSqlSupport.followup, isEqualTo(BooleanUtils.toIntegerObject(true)))
|
||||||
.build()
|
.build()
|
||||||
.execute()
|
.execute()
|
||||||
|
@ -555,14 +624,15 @@ class ConfigurationDAOBatchService {
|
||||||
.collect(Utils.toSingleton())
|
.collect(Utils.toSingleton())
|
||||||
.getId();
|
.getId();
|
||||||
|
|
||||||
return this.batchConfigurationValueRecordMapper.selectByExample()
|
final List<ConfigurationValueRecord> values = this.batchConfigurationValueRecordMapper.selectByExample()
|
||||||
.where(ConfigurationValueRecordDynamicSqlSupport.configurationId, isEqualTo(configurationId))
|
.where(ConfigurationValueRecordDynamicSqlSupport.configurationId, isEqualTo(configurationId))
|
||||||
.build()
|
.build()
|
||||||
.execute()
|
.execute();
|
||||||
.stream()
|
|
||||||
|
return values.stream()
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
valRec -> valRec.getConfigurationAttributeId(),
|
valRec -> valRec.getConfigurationAttributeId(),
|
||||||
valRec -> valRec.getValue()));
|
valRec -> (valRec.getValue() != null) ? valRec.getValue() : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ import static org.mybatis.dynamic.sql.SqlBuilder.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -131,6 +133,27 @@ public class ConfigurationDAOImpl implements ConfigurationDAO {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Configuration> getConfigurationLastStableVersion(final Long configNodeId) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
final List<ConfigurationRecord> 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
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<Configuration> save(final Configuration data) {
|
public Result<Configuration> save(final Configuration data) {
|
||||||
|
|
|
@ -19,7 +19,12 @@ import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
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.mybatis.dynamic.sql.SqlBuilder;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.ConfigurationValueRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordMapper;
|
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.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.bulkaction.BulkAction;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
|
||||||
|
@ -56,18 +62,24 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
||||||
private final ConfigurationNodeRecordMapper configurationNodeRecordMapper;
|
private final ConfigurationNodeRecordMapper configurationNodeRecordMapper;
|
||||||
private final ConfigurationValueRecordMapper configurationValueRecordMapper;
|
private final ConfigurationValueRecordMapper configurationValueRecordMapper;
|
||||||
private final ConfigurationDAOBatchService configurationDAOBatchService;
|
private final ConfigurationDAOBatchService configurationDAOBatchService;
|
||||||
|
private final String copyNamePrefix;
|
||||||
|
private final String copyNameSuffix;
|
||||||
|
|
||||||
protected ConfigurationNodeDAOImpl(
|
protected ConfigurationNodeDAOImpl(
|
||||||
final ConfigurationRecordMapper configurationRecordMapper,
|
final ConfigurationRecordMapper configurationRecordMapper,
|
||||||
final ConfigurationNodeRecordMapper configurationNodeRecordMapper,
|
final ConfigurationNodeRecordMapper configurationNodeRecordMapper,
|
||||||
final ConfigurationValueRecordMapper configurationValueRecordMapper,
|
final ConfigurationValueRecordMapper configurationValueRecordMapper,
|
||||||
final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper,
|
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.configurationRecordMapper = configurationRecordMapper;
|
||||||
this.configurationNodeRecordMapper = configurationNodeRecordMapper;
|
this.configurationNodeRecordMapper = configurationNodeRecordMapper;
|
||||||
this.configurationValueRecordMapper = configurationValueRecordMapper;
|
this.configurationValueRecordMapper = configurationValueRecordMapper;
|
||||||
this.configurationDAOBatchService = ConfigurationDAOBatchService;
|
this.configurationDAOBatchService = ConfigurationDAOBatchService;
|
||||||
|
this.copyNamePrefix = copyNamePrefix;
|
||||||
|
this.copyNameSuffix = copyNameSuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -191,6 +203,84 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
||||||
.onError(TransactionHandler::rollback);
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Result<ConfigurationNode> 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<ConfigurationRecord> 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
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
|
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
|
||||||
|
|
|
@ -288,11 +288,27 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
||||||
return getDependencies(bulkAction, selectionFunction);
|
return getDependencies(bulkAction, selectionFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<Long>> 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
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<Collection<Long>> getExamIdsForConfigId(final Long configurationId) {
|
public Result<Collection<Long>> getExamIdsForConfigId(final Long configurationId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
final Long configNodeId = this.configurationNodeRecordMapper.selectIdsByExample()
|
return this.configurationNodeRecordMapper.selectIdsByExample()
|
||||||
.leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord)
|
.leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord)
|
||||||
.on(
|
.on(
|
||||||
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
|
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
|
||||||
|
@ -304,17 +320,8 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
||||||
.execute()
|
.execute()
|
||||||
.stream()
|
.stream()
|
||||||
.collect(Utils.toSingleton());
|
.collect(Utils.toSingleton());
|
||||||
|
})
|
||||||
return this.examConfigurationMapRecordMapper.selectByExample()
|
.flatMap(this::getExamIdsForConfigNodeId);
|
||||||
.where(
|
|
||||||
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
|
|
||||||
isEqualTo(configNodeId))
|
|
||||||
.build()
|
|
||||||
.execute()
|
|
||||||
.stream()
|
|
||||||
.map(record -> record.getExamId())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<ExamConfigurationMapRecord> recordById(final Long id) {
|
private Result<ExamConfigurationMapRecord> recordById(final Long id) {
|
||||||
|
|
|
@ -111,7 +111,7 @@ public class ExamConfigIO {
|
||||||
|
|
||||||
// get follow-up configurationId for given configurationNodeId
|
// get follow-up configurationId for given configurationNodeId
|
||||||
final Long configurationId = this.configurationDAO
|
final Long configurationId = this.configurationDAO
|
||||||
.getFollowupConfiguration(configurationNodeId)
|
.getConfigurationLastStableVersion(configurationNodeId)
|
||||||
.getOrThrow().id;
|
.getOrThrow().id;
|
||||||
|
|
||||||
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier =
|
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier =
|
||||||
|
|
|
@ -19,19 +19,25 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
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;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
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.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
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.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
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.bulkaction.BulkActionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
|
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.dao.UserActivityLogDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent;
|
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;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||||
|
|
||||||
@WebServiceProfile
|
@WebServiceProfile
|
||||||
|
@ -40,7 +46,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
||||||
public class ConfigurationController extends ReadonlyEntityController<Configuration, Configuration> {
|
public class ConfigurationController extends ReadonlyEntityController<Configuration, Configuration> {
|
||||||
|
|
||||||
private final ConfigurationDAO configurationDAO;
|
private final ConfigurationDAO configurationDAO;
|
||||||
|
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||||
private final ApplicationEventPublisher applicationEventPublisher;
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
private final ExamSessionService examSessionService;
|
||||||
|
|
||||||
protected ConfigurationController(
|
protected ConfigurationController(
|
||||||
final AuthorizationService authorization,
|
final AuthorizationService authorization,
|
||||||
|
@ -49,7 +57,9 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
||||||
final UserActivityLogDAO userActivityLogDAO,
|
final UserActivityLogDAO userActivityLogDAO,
|
||||||
final PaginationService paginationService,
|
final PaginationService paginationService,
|
||||||
final BeanValidationService beanValidationService,
|
final BeanValidationService beanValidationService,
|
||||||
final ApplicationEventPublisher applicationEventPublisher) {
|
final ApplicationEventPublisher applicationEventPublisher,
|
||||||
|
final ExamSessionService examSessionService,
|
||||||
|
final ExamConfigurationMapDAO examConfigurationMapDAO) {
|
||||||
|
|
||||||
super(authorization,
|
super(authorization,
|
||||||
bulkActionService,
|
bulkActionService,
|
||||||
|
@ -60,6 +70,8 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
||||||
|
|
||||||
this.configurationDAO = entityDAO;
|
this.configurationDAO = entityDAO;
|
||||||
this.applicationEventPublisher = applicationEventPublisher;
|
this.applicationEventPublisher = applicationEventPublisher;
|
||||||
|
this.examSessionService = examSessionService;
|
||||||
|
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
|
@ -71,6 +83,7 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
||||||
|
|
||||||
return this.entityDAO.byModelId(modelId)
|
return this.entityDAO.byModelId(modelId)
|
||||||
.flatMap(this.authorization::checkModify)
|
.flatMap(this.authorization::checkModify)
|
||||||
|
.flatMap(this::checkRunningExamIntegrity)
|
||||||
.flatMap(config -> this.configurationDAO.saveToHistory(config.configurationNodeId))
|
.flatMap(config -> this.configurationDAO.saveToHistory(config.configurationNodeId))
|
||||||
.flatMap(this.userActivityLogDAO::logSaveToHistory)
|
.flatMap(this.userActivityLogDAO::logSaveToHistory)
|
||||||
.flatMap(this::publishConfigChanged)
|
.flatMap(this::publishConfigChanged)
|
||||||
|
@ -118,9 +131,53 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
||||||
return ConfigurationRecordDynamicSqlSupport.configurationRecord;
|
return ConfigurationRecordDynamicSqlSupport.configurationRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: This will not properly work within a distributed setup since other instances
|
||||||
|
// are not notified about the configuration change
|
||||||
|
// TODO: find a way to manage the notification of a changed configuration that works
|
||||||
|
// also on distributed environments. For example use the database to store configuration
|
||||||
|
// changed information and check before getting a configuration from cache if it is still valid
|
||||||
private Result<Configuration> publishConfigChanged(final Configuration config) {
|
private Result<Configuration> publishConfigChanged(final Configuration config) {
|
||||||
this.applicationEventPublisher.publishEvent(new ConfigurationChangedEvent(config.id));
|
this.applicationEventPublisher.publishEvent(new ConfigurationChangedEvent(config.id));
|
||||||
return Result.of(config);
|
return Result.of(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Result<Configuration> 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.mybatis.dynamic.sql.SqlTable;
|
import org.mybatis.dynamic.sql.SqlTable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -71,11 +72,11 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ConfigurationNodeController.class);
|
private static final Logger log = LoggerFactory.getLogger(ConfigurationNodeController.class);
|
||||||
|
|
||||||
|
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||||
private final ConfigurationDAO configurationDAO;
|
private final ConfigurationDAO configurationDAO;
|
||||||
private final ViewDAO viewDAO;
|
private final ViewDAO viewDAO;
|
||||||
private final OrientationDAO orientationDAO;
|
private final OrientationDAO orientationDAO;
|
||||||
private final SebExamConfigService sebExamConfigService;
|
private final SebExamConfigService sebExamConfigService;
|
||||||
|
|
||||||
private final SebExamConfigTemplateService sebExamConfigTemplateService;
|
private final SebExamConfigTemplateService sebExamConfigTemplateService;
|
||||||
|
|
||||||
protected ConfigurationNodeController(
|
protected ConfigurationNodeController(
|
||||||
|
@ -99,6 +100,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
||||||
beanValidationService);
|
beanValidationService);
|
||||||
|
|
||||||
this.configurationDAO = configurationDAO;
|
this.configurationDAO = configurationDAO;
|
||||||
|
this.configurationNodeDAO = entityDAO;
|
||||||
this.viewDAO = viewDAO;
|
this.viewDAO = viewDAO;
|
||||||
this.orientationDAO = orientationDAO;
|
this.orientationDAO = orientationDAO;
|
||||||
this.sebExamConfigService = sebExamConfigService;
|
this.sebExamConfigService = sebExamConfigService;
|
||||||
|
@ -135,6 +137,35 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping(
|
||||||
|
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_COPY_PATH_SEGMENT,
|
||||||
|
method = RequestMethod.POST,
|
||||||
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
|
public ConfigurationNode copyConfiguration(
|
||||||
|
@PathVariable final Long modelId,
|
||||||
|
@RequestParam(
|
||||||
|
name = API.PARAM_INSTITUTION_ID,
|
||||||
|
required = true,
|
||||||
|
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||||
|
@RequestParam(name = ConfigurationNode.ATTR_COPY_WITH_HISTORY,
|
||||||
|
required = false) final Boolean withHistory) {
|
||||||
|
|
||||||
|
this.entityDAO.byPK(modelId)
|
||||||
|
.flatMap(this.authorization::checkWrite);
|
||||||
|
|
||||||
|
final SEBServerUser currentUser = this.authorization
|
||||||
|
.getUserService()
|
||||||
|
.getCurrentUser();
|
||||||
|
|
||||||
|
return this.configurationNodeDAO.createCopy(
|
||||||
|
institutionId,
|
||||||
|
currentUser.getUserInfo().uuid,
|
||||||
|
modelId,
|
||||||
|
BooleanUtils.toBoolean(withHistory))
|
||||||
|
.getOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_CONFIG_KEY_PATH_SEGMENT,
|
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_CONFIG_KEY_PATH_SEGMENT,
|
||||||
method = RequestMethod.GET,
|
method = RequestMethod.GET,
|
||||||
|
|
|
@ -13,7 +13,6 @@ import java.util.Objects;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
|
||||||
import org.mybatis.dynamic.sql.SqlTable;
|
import org.mybatis.dynamic.sql.SqlTable;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
@ -36,7 +35,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
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.sebconfig.SebExamConfigService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||||
|
|
||||||
|
@ -48,7 +46,6 @@ public class ConfigurationValueController extends EntityController<Configuration
|
||||||
private final ConfigurationDAO configurationDAO;
|
private final ConfigurationDAO configurationDAO;
|
||||||
private final ConfigurationValueDAO configurationValueDAO;
|
private final ConfigurationValueDAO configurationValueDAO;
|
||||||
private final SebExamConfigService sebExamConfigService;
|
private final SebExamConfigService sebExamConfigService;
|
||||||
private final ApplicationEventPublisher applicationEventPublisher;
|
|
||||||
|
|
||||||
protected ConfigurationValueController(
|
protected ConfigurationValueController(
|
||||||
final AuthorizationService authorization,
|
final AuthorizationService authorization,
|
||||||
|
@ -58,8 +55,7 @@ public class ConfigurationValueController extends EntityController<Configuration
|
||||||
final PaginationService paginationService,
|
final PaginationService paginationService,
|
||||||
final BeanValidationService beanValidationService,
|
final BeanValidationService beanValidationService,
|
||||||
final ConfigurationDAO configurationDAO,
|
final ConfigurationDAO configurationDAO,
|
||||||
final SebExamConfigService sebExamConfigService,
|
final SebExamConfigService sebExamConfigService) {
|
||||||
final ApplicationEventPublisher applicationEventPublisher) {
|
|
||||||
|
|
||||||
super(authorization,
|
super(authorization,
|
||||||
bulkActionService,
|
bulkActionService,
|
||||||
|
@ -71,19 +67,6 @@ public class ConfigurationValueController extends EntityController<Configuration
|
||||||
this.configurationDAO = configurationDAO;
|
this.configurationDAO = configurationDAO;
|
||||||
this.configurationValueDAO = entityDAO;
|
this.configurationValueDAO = entityDAO;
|
||||||
this.sebExamConfigService = sebExamConfigService;
|
this.sebExamConfigService = sebExamConfigService;
|
||||||
this.applicationEventPublisher = applicationEventPublisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Result<ConfigurationValue> notifySaved(final ConfigurationValue entity) {
|
|
||||||
if (entity == null) {
|
|
||||||
return super.notifySaved(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.applicationEventPublisher.publishEvent(
|
|
||||||
new ConfigurationChangedEvent(entity.configurationId));
|
|
||||||
|
|
||||||
return super.notifySaved(entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -141,11 +124,6 @@ public class ConfigurationValueController extends EntityController<Configuration
|
||||||
return this.configurationDAO.byPK(tableValue.configurationId)
|
return this.configurationDAO.byPK(tableValue.configurationId)
|
||||||
.flatMap(this.authorization::checkModify)
|
.flatMap(this.authorization::checkModify)
|
||||||
.flatMap(config -> this.configurationValueDAO.saveTableValues(tableValue))
|
.flatMap(config -> this.configurationValueDAO.saveTableValues(tableValue))
|
||||||
.map(config -> {
|
|
||||||
this.applicationEventPublisher.publishEvent(
|
|
||||||
new ConfigurationChangedEvent(config.configurationId));
|
|
||||||
return config;
|
|
||||||
})
|
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.event-handling-strategy=ASYNC_BATCH_STORE_STRATEGY
|
||||||
sebserver.webservice.api.exam.enable-indicator-cache=true
|
sebserver.webservice.api.exam.enable-indicator-cache=true
|
||||||
sebserver.webservice.api.pagination.maxPageSize=500
|
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
|
# 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.openedx.api.token.request.paths=/oauth2/access_token
|
||||||
sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias
|
sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias
|
||||||
|
|
|
@ -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.new=Add Exam Configuration
|
||||||
sebserver.examconfig.action.list.view=View Configuration
|
sebserver.examconfig.action.list.view=View Configuration
|
||||||
sebserver.examconfig.action.list.modify=Edit Settings
|
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.list.modify.properties=Edit Configuration
|
||||||
sebserver.examconfig.action.view=View Configuration
|
sebserver.examconfig.action.view=View Configuration
|
||||||
sebserver.examconfig.action.modify=Edit Settings
|
sebserver.examconfig.action.modify=Edit Settings
|
||||||
sebserver.examconfig.action.modify.properties=Edit Configuration
|
sebserver.examconfig.action.modify.properties=Edit Configuration
|
||||||
sebserver.examconfig.action.save=Save
|
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.saveToHistory.success=Successfully saved in history
|
||||||
sebserver.examconfig.action.undo=Undo
|
sebserver.examconfig.action.undo=Undo
|
||||||
sebserver.examconfig.action.undo.success=Successfully reverted to last saved state
|
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.title=Exam Configuration
|
||||||
sebserver.examconfig.form.name=Name
|
sebserver.examconfig.form.name=Name
|
||||||
sebserver.examconfig.form.description=Description
|
sebserver.examconfig.form.description=Description
|
||||||
|
sebserver.examconfig.form.template=From Template
|
||||||
sebserver.examconfig.form.status=Status
|
sebserver.examconfig.form.status=Status
|
||||||
sebserver.examconfig.form.config-key.title=Config Key
|
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.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.list.view=View Template
|
||||||
sebserver.configtemplate.action.view=View Template
|
sebserver.configtemplate.action.view=View Template
|
||||||
sebserver.configtemplate.action.list.modify=Edit Template
|
sebserver.configtemplate.action.list.modify=Edit Template
|
||||||
sebserver.configtemplate.action.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.title=Configuration Template
|
||||||
sebserver.configtemplate.form.name=Name
|
sebserver.configtemplate.form.name=Name
|
||||||
sebserver.configtemplate.form.description=Description
|
sebserver.configtemplate.form.description=Description
|
||||||
|
|
|
@ -244,17 +244,32 @@ Text[BORDER].error, Text[MULTI][BORDER].error {
|
||||||
|
|
||||||
Text[BORDER]:focused, Text[MULTI][BORDER]:focused {
|
Text[BORDER]:focused, Text[MULTI][BORDER]:focused {
|
||||||
border: 1px solid #4f7cb1;
|
border: 1px solid #4f7cb1;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text:disabled,
|
Text:disabled,
|
||||||
Text:read-only,
|
Text:read-only,
|
||||||
Text[BORDER]:disabled,
|
|
||||||
Text[BORDER]:read-only,
|
|
||||||
Text[MULTI]:disabled,
|
Text[MULTI]:disabled,
|
||||||
Text[MULTI]:read-only,
|
Text[MULTI]:read-only {
|
||||||
Text[MULTI][BORDER]:disabled,
|
|
||||||
Text[MULTI][BORDER]:read-only {
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -460,6 +475,14 @@ Button[PUSH]:hover.header {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button[CHECK]:disabled {
|
||||||
|
color: #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button[RADIO]:disabled {
|
||||||
|
color: #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
FileUpload,
|
FileUpload,
|
||||||
FileUpload:default,
|
FileUpload:default,
|
||||||
FileUpload:hover,
|
FileUpload:hover,
|
||||||
|
|
|
@ -247,13 +247,18 @@ Text[BORDER]:focused, Text[MULTI][BORDER]:focused {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text[BORDER]:disabled,
|
||||||
|
Text[MULTI][BORDER]:disabled {
|
||||||
|
border: 1px solid #4a4a4a;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
Text:disabled,
|
Text:disabled,
|
||||||
Text:read-only,
|
Text:read-only,
|
||||||
Text[BORDER]:disabled,
|
|
||||||
Text[BORDER]:read-only,
|
|
||||||
Text[MULTI]:disabled,
|
Text[MULTI]:disabled,
|
||||||
|
Text[BORDER]:read-only,
|
||||||
Text[MULTI]:read-only,
|
Text[MULTI]:read-only,
|
||||||
Text[MULTI][BORDER]:disabled,
|
|
||||||
Text[MULTI][BORDER]:read-only {
|
Text[MULTI][BORDER]:read-only {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
|
|
Loading…
Reference in a new issue