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_SAVE_TO_HISTORY_PATH_SEGMENT = "/save_to_history";
|
||||
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_VALUE_ENDPOINT = "/configuration_value";
|
||||
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 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_DESCRIPTION = "description";
|
||||
|
|
|
@ -176,7 +176,7 @@ public class ConfigTemplateAttributeForm implements TemplateComposer {
|
|||
configuration,
|
||||
new View(-1L, "template", 10, 0, templateId),
|
||||
attributeMapping,
|
||||
1);
|
||||
1, false);
|
||||
|
||||
final InputFieldBuilder inputFieldBuilder = this.examConfigurationService.getInputFieldBuilder(
|
||||
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.Configuration;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
|
@ -74,6 +75,8 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.examconfig.form.name");
|
||||
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
||||
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 =
|
||||
new LocTextKey("sebserver.examconfig.form.status");
|
||||
private static final LocTextKey FORM_IMPORT_TEXT_KEY =
|
||||
|
@ -113,6 +116,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
|
||||
final UserInfo user = this.currentUser.get();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final boolean isNew = entityKey == null;
|
||||
|
||||
// get data or create new. Handle error if happen
|
||||
|
@ -169,6 +173,14 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
FORM_DESCRIPTION_TEXT_KEY,
|
||||
examConfig.description)
|
||||
.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(
|
||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
FORM_STATUS_TEXT_KEY,
|
||||
|
@ -178,6 +190,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
? this.restService.getRestCall(NewExamConfig.class)
|
||||
: this.restService.getRestCall(SaveExamConfig.class));
|
||||
|
||||
final boolean settingsReadonly = examConfig.status == ConfigurationStatus.IN_USE;
|
||||
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
|
||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||
|
||||
|
@ -190,7 +203,11 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_MODIFY)
|
||||
.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)
|
||||
.withEntityKey(entityKey)
|
||||
|
@ -216,13 +233,13 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
.noEventPropagation()
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_SAVE)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_SAVE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> !isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_CANCEL_MODIFY)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.pageService.backToCurrentFunction())
|
||||
.publishIf(() -> !isReadonly);
|
||||
|
|
|
@ -113,7 +113,6 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
.getOrThrow();
|
||||
|
||||
final List<View> views = this.examConfigurationService.getViews(attributes);
|
||||
|
||||
final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content);
|
||||
tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
|
||||
|
||||
|
@ -124,7 +123,8 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
configuration,
|
||||
view,
|
||||
attributes,
|
||||
20);
|
||||
20,
|
||||
pageContext.isReadonly());
|
||||
viewContexts.add(viewContext);
|
||||
|
||||
final Composite viewGrid = this.examConfigurationService.createViewGrid(
|
||||
|
@ -141,6 +141,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
|
||||
final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE);
|
||||
this.pageService.pageActionBuilder(pageContext.clearEntityKeys())
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_SAVE_TO_HISTORY)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(action -> {
|
||||
|
@ -169,6 +170,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
||||
.withEntityKey(entityKey)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publish()
|
||||
|
||||
;
|
||||
|
|
|
@ -351,12 +351,12 @@ public enum ActionDefinition {
|
|||
SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST(
|
||||
new LocTextKey("sebserver.examconfig.action.list.view"),
|
||||
ImageIcon.SHOW,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW,
|
||||
ActionCategory.SEB_EXAM_CONFIG_LIST),
|
||||
SEB_EXAM_CONFIG_VIEW_PROP(
|
||||
new LocTextKey("sebserver.examconfig.action.view"),
|
||||
ImageIcon.SHOW,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW,
|
||||
ActionCategory.FORM),
|
||||
|
||||
SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST(
|
||||
|
@ -374,15 +374,20 @@ public enum ActionDefinition {
|
|||
ImageIcon.EDIT_SETTINGS,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_EDIT,
|
||||
ActionCategory.FORM),
|
||||
SEB_EXAM_CONFIG_CANCEL_MODIFY(
|
||||
new LocTextKey("sebserver.overall.action.modify.cancel"),
|
||||
ImageIcon.CANCEL,
|
||||
SEB_EXAM_CONFIG_VIEW(
|
||||
new LocTextKey("sebserver.examconfig.action.view"),
|
||||
ImageIcon.EDIT_SETTINGS,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
||||
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"),
|
||||
ImageIcon.SAVE,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_VIEW,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_VIEW,
|
||||
ActionCategory.FORM),
|
||||
SEB_EXAM_CONFIG_EXPORT_PLAIN_XML(
|
||||
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_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_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_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.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
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);
|
||||
if (this.isArea) {
|
||||
gridData.minimumHeight = 35;
|
||||
gridData.minimumHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
|
||||
}
|
||||
textInput.setLayoutData(gridData);
|
||||
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.institution.GetInstitutionNames;
|
||||
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.useraccount.GetUserAccountNames;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
|
@ -551,6 +552,19 @@ public class ResourceService {
|
|||
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() {
|
||||
return this.restService.getBuilder(GetExamConfigMappingNames.class)
|
||||
.withQueryParam(
|
||||
|
|
|
@ -54,7 +54,8 @@ public interface ExamConfigurationService {
|
|||
Configuration configuration,
|
||||
View view,
|
||||
AttributeMapping attributeMapping,
|
||||
int rows);
|
||||
int rows,
|
||||
boolean readonly);
|
||||
|
||||
Composite createViewGrid(
|
||||
Composite parent,
|
||||
|
|
|
@ -104,6 +104,7 @@ public abstract class AbstractTableFieldBuilder implements InputFieldBuilder {
|
|||
final TableContext tableContext) {
|
||||
|
||||
try {
|
||||
final boolean readonly = tableContext.getViewContext().readonly;
|
||||
final int currentTableWidth = table.getClientArea().width - TABLE_WIDTH_SPACE;
|
||||
final TableColumn[] columns = table.getColumns();
|
||||
final List<Orientation> orientations = tableContext
|
||||
|
@ -116,7 +117,7 @@ public abstract class AbstractTableFieldBuilder implements InputFieldBuilder {
|
|||
.map(o -> o.width)
|
||||
.reduce(0, (acc, val) -> acc + val);
|
||||
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);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
|
|
|
@ -154,7 +154,6 @@ interface CellFieldBuilderAdapter {
|
|||
label.setAlignment(SWT.LEFT);
|
||||
gridData.verticalIndent = 20;
|
||||
label.setLayoutData(gridData);
|
||||
//label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -85,13 +85,17 @@ public class CheckBoxBuilder implements InputFieldBuilder {
|
|||
viewContext.getOrientation(attribute.id),
|
||||
checkbox);
|
||||
|
||||
checkbox.addListener(
|
||||
SWT.Selection,
|
||||
event -> viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
checkboxField.getValue(),
|
||||
checkboxField.listIndex));
|
||||
if (viewContext.readonly) {
|
||||
checkbox.setEnabled(false);
|
||||
} else {
|
||||
checkbox.addListener(
|
||||
SWT.Selection,
|
||||
event -> viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
checkboxField.getValue(),
|
||||
checkboxField.listIndex));
|
||||
}
|
||||
|
||||
return checkboxField;
|
||||
}
|
||||
|
|
|
@ -201,16 +201,27 @@ public class CompositeTableFieldBuilder extends AbstractTableFieldBuilder {
|
|||
rowValues,
|
||||
row);
|
||||
|
||||
new ModalInputDialog<Map<Long, TableValue>>(
|
||||
final ModalInputDialog<Map<Long, TableValue>> dialog = new ModalInputDialog<Map<Long, TableValue>>(
|
||||
this.control.getShell(),
|
||||
this.tableContext.getWidgetFactory())
|
||||
.setDialogWidth(500)
|
||||
.open(
|
||||
new LocTextKey(ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + row),
|
||||
rowVals -> applyFormValues(this.values, rowVals, selectionIndex),
|
||||
() -> this.tableContext.getValueChangeListener()
|
||||
.tableChanged(extractTableValue(this.values)),
|
||||
builder);
|
||||
.setDialogWidth(500);
|
||||
|
||||
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),
|
||||
rowVals -> applyFormValues(this.values, rowVals, selectionIndex),
|
||||
() -> this.tableContext.getValueChangeListener()
|
||||
.tableChanged(extractTableValue(this.values)),
|
||||
builder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -177,7 +177,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
|
|||
final Configuration configuration,
|
||||
final View view,
|
||||
final AttributeMapping attributeMapping,
|
||||
final int rows) {
|
||||
final int rows,
|
||||
final boolean readonly) {
|
||||
|
||||
return new ViewContext(
|
||||
configuration,
|
||||
|
@ -189,7 +190,8 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService {
|
|||
this.restService,
|
||||
this.jsonMapper,
|
||||
this.valueChangeRules),
|
||||
this.widgetFactory.getI18nSupport());
|
||||
this.widgetFactory.getI18nSupport(),
|
||||
readonly);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -92,11 +92,17 @@ public class InlineTableFieldBuilder implements InputFieldBuilder {
|
|||
viewContext.getOrientation(attribute.id),
|
||||
gridTable);
|
||||
|
||||
gridTable.setListener(event -> viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
inlineTableInputField.getValue(),
|
||||
inlineTableInputField.listIndex));
|
||||
if (viewContext.readonly) {
|
||||
gridTable.setEnabled(false);
|
||||
gridTable.setListener(event -> {
|
||||
});
|
||||
} else {
|
||||
gridTable.setListener(event -> viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
inlineTableInputField.getValue(),
|
||||
inlineTableInputField.listIndex));
|
||||
}
|
||||
|
||||
return inlineTableInputField;
|
||||
|
||||
|
|
|
@ -68,14 +68,18 @@ public class MultiCheckboxSelection extends SelectionFieldBuilder implements Inp
|
|||
orientation,
|
||||
selection);
|
||||
|
||||
selection.setSelectionListener(event -> {
|
||||
multiSelectionCheckboxInputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
multiSelectionCheckboxInputField.getValue(),
|
||||
multiSelectionCheckboxInputField.listIndex);
|
||||
});
|
||||
if (viewContext.readonly) {
|
||||
selection.setEnabled(false);
|
||||
} else {
|
||||
selection.setSelectionListener(event -> {
|
||||
multiSelectionCheckboxInputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
multiSelectionCheckboxInputField.getValue(),
|
||||
multiSelectionCheckboxInputField.listIndex);
|
||||
});
|
||||
}
|
||||
|
||||
return multiSelectionCheckboxInputField;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.security.MessageDigest;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
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.InputFieldBuilder;
|
||||
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
|
||||
@Component
|
||||
|
@ -67,7 +70,8 @@ public class PassworFieldBuilder implements InputFieldBuilder {
|
|||
.createInnerGrid(parent, attribute, orientation);
|
||||
|
||||
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 GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||
gridData.verticalIndent = 14;
|
||||
|
@ -80,37 +84,46 @@ public class PassworFieldBuilder implements InputFieldBuilder {
|
|||
confirmInput,
|
||||
Form.createErrorLabel(innerGrid));
|
||||
|
||||
final Listener valueChangeEventListener = event -> {
|
||||
passwordInputField.clearError();
|
||||
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 -> {
|
||||
passwordInputField.clearError();
|
||||
|
||||
final String pwd = passwordInput.getText();
|
||||
final String confirm = confirmInput.getText();
|
||||
final String pwd = passwordInput.getText();
|
||||
final String confirm = confirmInput.getText();
|
||||
|
||||
if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) {
|
||||
return;
|
||||
}
|
||||
if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pwd.equals(confirm)) {
|
||||
passwordInputField.showError(viewContext
|
||||
.getI18nSupport()
|
||||
.getText(VAL_CONFIRM_PWD_TEXT_KEY));
|
||||
return;
|
||||
}
|
||||
if (!pwd.equals(confirm)) {
|
||||
passwordInputField.showError(viewContext
|
||||
.getI18nSupport()
|
||||
.getText(VAL_CONFIRM_PWD_TEXT_KEY));
|
||||
return;
|
||||
}
|
||||
|
||||
final String hashedPWD = passwordInputField.getValue();
|
||||
if (hashedPWD != null) {
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
hashedPWD,
|
||||
passwordInputField.listIndex);
|
||||
}
|
||||
};
|
||||
final String hashedPWD = passwordInputField.getValue();
|
||||
if (hashedPWD != null) {
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
hashedPWD,
|
||||
passwordInputField.listIndex);
|
||||
}
|
||||
};
|
||||
|
||||
passwordInput.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||
passwordInput.addListener(SWT.Traverse, valueChangeEventListener);
|
||||
confirmInput.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||
confirmInput.addListener(SWT.Traverse, valueChangeEventListener);
|
||||
passwordInput.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||
passwordInput.addListener(SWT.Traverse, valueChangeEventListener);
|
||||
confirmInput.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||
confirmInput.addListener(SWT.Traverse, valueChangeEventListener);
|
||||
}
|
||||
return passwordInputField;
|
||||
}
|
||||
|
||||
|
|
|
@ -71,14 +71,18 @@ public class RadioSelectionFieldBuilder extends SelectionFieldBuilder implements
|
|||
selection,
|
||||
null);
|
||||
|
||||
selection.setSelectionListener(event -> {
|
||||
radioSelectionInputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
radioSelectionInputField.getValue(),
|
||||
radioSelectionInputField.listIndex);
|
||||
});
|
||||
if (viewContext.readonly) {
|
||||
selection.setEnabled(false);
|
||||
} else {
|
||||
selection.setSelectionListener(event -> {
|
||||
radioSelectionInputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
radioSelectionInputField.getValue(),
|
||||
radioSelectionInputField.listIndex);
|
||||
});
|
||||
}
|
||||
|
||||
return radioSelectionInputField;
|
||||
|
||||
|
|
|
@ -76,14 +76,18 @@ public class SingleSelectionFieldBuilder extends SelectionFieldBuilder implement
|
|||
selection,
|
||||
Form.createErrorLabel(innerGrid));
|
||||
|
||||
selection.setSelectionListener(event -> {
|
||||
singleSelectionInputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
singleSelectionInputField.getValue(),
|
||||
singleSelectionInputField.listIndex);
|
||||
});
|
||||
if (viewContext.readonly) {
|
||||
selection.setEnabled(false);
|
||||
} else {
|
||||
selection.setSelectionListener(event -> {
|
||||
singleSelectionInputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
singleSelectionInputField.getValue(),
|
||||
singleSelectionInputField.listIndex);
|
||||
});
|
||||
}
|
||||
|
||||
return singleSelectionInputField;
|
||||
}
|
||||
|
|
|
@ -73,17 +73,21 @@ public class SliderFieldBuilder implements InputFieldBuilder {
|
|||
orientation,
|
||||
slider);
|
||||
|
||||
final Listener valueChangeEventListener = event -> {
|
||||
inputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
inputField.getValue(),
|
||||
inputField.listIndex);
|
||||
};
|
||||
if (viewContext.readonly) {
|
||||
slider.setEnabled(false);
|
||||
} else {
|
||||
final Listener valueChangeEventListener = event -> {
|
||||
inputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
inputField.getValue(),
|
||||
inputField.listIndex);
|
||||
};
|
||||
|
||||
slider.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||
slider.addListener(SWT.Traverse, valueChangeEventListener);
|
||||
slider.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||
slider.addListener(SWT.Traverse, valueChangeEventListener);
|
||||
}
|
||||
return inputField;
|
||||
}
|
||||
|
||||
|
|
|
@ -85,30 +85,32 @@ public class TableFieldBuilder extends AbstractTableFieldBuilder {
|
|||
tableContext,
|
||||
table);
|
||||
|
||||
TableColumn column = new TableColumn(table, SWT.NONE);
|
||||
column.setImage(ImageIcon.ADD_BOX.getImage(parent.getDisplay()));
|
||||
if (!viewContext.readonly) {
|
||||
TableColumn column = new TableColumn(table, SWT.NONE);
|
||||
column.setImage(ImageIcon.ADD_BOX.getImage(parent.getDisplay()));
|
||||
|
||||
column.setWidth(20);
|
||||
column.setResizable(false);
|
||||
column.setMoveable(false);
|
||||
column.setWidth(20);
|
||||
column.setResizable(false);
|
||||
column.setMoveable(false);
|
||||
|
||||
column.addListener(SWT.Selection, event -> {
|
||||
tableField.addRow();
|
||||
});
|
||||
column.addListener(SWT.Selection, event -> {
|
||||
tableField.addRow();
|
||||
});
|
||||
|
||||
column = new TableColumn(table, SWT.NONE);
|
||||
column.setImage(ImageIcon.REMOVE_BOX.getImage(parent.getDisplay()));
|
||||
column = new TableColumn(table, SWT.NONE);
|
||||
column.setImage(ImageIcon.REMOVE_BOX.getImage(parent.getDisplay()));
|
||||
|
||||
column.setWidth(20);
|
||||
column.setResizable(false);
|
||||
column.setMoveable(false);
|
||||
column.setWidth(20);
|
||||
column.setResizable(false);
|
||||
column.setMoveable(false);
|
||||
|
||||
column.addListener(SWT.Selection, event -> {
|
||||
final int selectionIndex = table.getSelectionIndex();
|
||||
if (selectionIndex >= 0) {
|
||||
tableField.deleteRow(selectionIndex);
|
||||
}
|
||||
});
|
||||
column.addListener(SWT.Selection, event -> {
|
||||
final int selectionIndex = table.getSelectionIndex();
|
||||
if (selectionIndex >= 0) {
|
||||
tableField.deleteRow(selectionIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setSelectionListener(table, tableField);
|
||||
return tableField;
|
||||
|
@ -163,8 +165,6 @@ public class TableFieldBuilder extends AbstractTableFieldBuilder {
|
|||
protected void addTableRow(final int index, final Map<Long, TableValue> rowValues) {
|
||||
new TableItem(this.control, SWT.NONE);
|
||||
applyTableRowValues(index);
|
||||
|
||||
// TODO try to add delete button within table row?
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.examconfig.impl;
|
|||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
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.LocTextKey;
|
||||
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
|
||||
@Component
|
||||
|
@ -74,7 +77,7 @@ public class TextFieldBuilder implements InputFieldBuilder {
|
|||
}
|
||||
case TEXT_AREA: {
|
||||
text = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.MULTI);
|
||||
gridData.heightHint = 50;
|
||||
gridData.minimumHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
@ -101,17 +104,25 @@ public class TextFieldBuilder implements InputFieldBuilder {
|
|||
text,
|
||||
Form.createErrorLabel(innerGrid));
|
||||
|
||||
final Listener valueChangeEventListener = event -> {
|
||||
textInputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
textInputField.getValue(),
|
||||
textInputField.listIndex);
|
||||
};
|
||||
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 -> {
|
||||
textInputField.clearError();
|
||||
viewContext.getValueChangeListener().valueChanged(
|
||||
viewContext,
|
||||
attribute,
|
||||
textInputField.getValue(),
|
||||
textInputField.listIndex);
|
||||
};
|
||||
|
||||
text.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||
text.addListener(SWT.Traverse, valueChangeEventListener);
|
||||
text.addListener(SWT.FocusOut, valueChangeEventListener);
|
||||
text.addListener(SWT.Traverse, valueChangeEventListener);
|
||||
}
|
||||
return textInputField;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ public final class ViewContext {
|
|||
final Map<Long, InputField> inputFieldMapping;
|
||||
final ValueChangeListener valueChangeListener;
|
||||
final I18nSupport i18nSupport;
|
||||
final boolean readonly;
|
||||
|
||||
ViewContext(
|
||||
final Configuration configuration,
|
||||
|
@ -44,7 +45,8 @@ public final class ViewContext {
|
|||
final int rows,
|
||||
final AttributeMapping attributeContext,
|
||||
final ValueChangeListener valueChangeListener,
|
||||
final I18nSupport i18nSupport) {
|
||||
final I18nSupport i18nSupport,
|
||||
final boolean readonly) {
|
||||
|
||||
Objects.requireNonNull(configuration);
|
||||
Objects.requireNonNull(view);
|
||||
|
@ -59,6 +61,7 @@ public final class ViewContext {
|
|||
this.inputFieldMapping = new HashMap<>();
|
||||
this.valueChangeListener = valueChangeListener;
|
||||
this.i18nSupport = i18nSupport;
|
||||
this.readonly = readonly;
|
||||
}
|
||||
|
||||
public I18nSupport getI18nSupport() {
|
||||
|
|
|
@ -49,7 +49,6 @@ public class ModalInputDialog<T> extends Dialog {
|
|||
|
||||
super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL | SWT.CLOSE);
|
||||
this.widgetFactory = widgetFactory;
|
||||
|
||||
}
|
||||
|
||||
public ModalInputDialog<T> setDialogWidth(final int dialogWidth) {
|
||||
|
@ -161,7 +160,6 @@ public class ModalInputDialog<T> extends Dialog {
|
|||
shell.pack();
|
||||
final Rectangle bounds = shell.getBounds();
|
||||
final Rectangle bounds2 = super.getParent().getDisplay().getBounds();
|
||||
//bounds.width = bounds.width;
|
||||
bounds.x = (bounds2.width - bounds.width) / 2;
|
||||
bounds.y = (bounds2.height - bounds.height) / 2;
|
||||
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.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
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.gui.service.i18n.LocTextKey;
|
||||
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;
|
||||
|
||||
public class GridTable extends Composite {
|
||||
|
@ -394,6 +396,7 @@ public class GridTable extends Composite {
|
|||
|
||||
TextField(final Composite parent, final ColumnDef columnDef, final Listener listener) {
|
||||
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.columnDef = columnDef;
|
||||
this._textField.addListener(SWT.FocusOut, listener);
|
||||
|
|
|
@ -58,6 +58,9 @@ public class WidgetFactory {
|
|||
|
||||
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 {
|
||||
MAXIMIZE("maximize.png"),
|
||||
MINIMIZE("minimize.png"),
|
||||
|
@ -127,7 +130,8 @@ public class WidgetFactory {
|
|||
TITLE_LABEL("head"),
|
||||
|
||||
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) {
|
||||
return readonly
|
||||
? new Text(content, SWT.LEFT | SWT.BORDER)
|
||||
? new Text(content, SWT.LEFT)
|
||||
: new Text(content, (password)
|
||||
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
|
||||
: SWT.LEFT | SWT.BORDER);
|
||||
|
|
|
@ -43,7 +43,7 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
|
|||
Result<Configuration> saveToHistory(Long configurationNodeId);
|
||||
|
||||
/** Can be used to reset the current follow-up configuration back to the last saved version in the history
|
||||
*
|
||||
*
|
||||
* @param configurationNodeId ConfigurationNode identifier to apply the undo on
|
||||
* @return the current and reseted follow-up version */
|
||||
Result<Configuration> undo(Long configurationNodeId);
|
||||
|
@ -62,4 +62,10 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
|
|||
* @return the current follow-up configuration */
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
public interface ConfigurationNodeDAO extends
|
||||
EntityDAO<ConfigurationNode, 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 */
|
||||
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.
|
||||
*
|
||||
* @param configurationId
|
||||
* @return */
|
||||
* @return Result referencing the List of exam identifiers (PK) for a given configuration identifier */
|
||||
Result<Collection<Long>> getExamIdsForConfigId(Long configurationId);
|
||||
|
||||
}
|
||||
|
|
|
@ -66,6 +66,8 @@ class ConfigurationDAOBatchService {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(ConfigurationDAOBatchService.class);
|
||||
|
||||
public static final String INITIAL_VERSION_NAME = "v0";
|
||||
|
||||
private final ConfigurationNodeRecordMapper batchConfigurationNodeRecordMapper;
|
||||
private final ConfigurationValueRecordMapper batchConfigurationValueRecordMapper;
|
||||
private final ConfigurationAttributeRecordMapper batchConfigurationAttributeRecordMapper;
|
||||
|
@ -187,23 +189,13 @@ class ConfigurationDAOBatchService {
|
|||
.execute();
|
||||
|
||||
// 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
|
||||
final ConfigurationRecord configUpdate = new ConfigurationRecord(
|
||||
followupConfig.getId(),
|
||||
null,
|
||||
null,
|
||||
"v" + versions,
|
||||
generateVersionName(configurationNodeId),
|
||||
DateTime.now(DateTimeZone.UTC),
|
||||
BooleanUtils.toInteger(false));
|
||||
this.batchConfigurationRecordMapper.updateByPrimaryKeySelective(configUpdate);
|
||||
|
@ -240,6 +232,20 @@ class ConfigurationDAOBatchService {
|
|||
.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) {
|
||||
return Result.tryCatch(() -> {
|
||||
// get all configurations of the node
|
||||
|
@ -311,6 +317,69 @@ class ConfigurationDAOBatchService {
|
|||
.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) {
|
||||
return this.batchConfigurationRecordMapper
|
||||
.selectByExample()
|
||||
|
@ -454,7 +523,7 @@ class ConfigurationDAOBatchService {
|
|||
null,
|
||||
config.institutionId,
|
||||
config.id,
|
||||
"v0", // TODO?
|
||||
INITIAL_VERSION_NAME,
|
||||
DateTime.now(DateTimeZone.UTC),
|
||||
BooleanUtils.toInteger(false));
|
||||
|
||||
|
@ -547,7 +616,7 @@ class ConfigurationDAOBatchService {
|
|||
}
|
||||
|
||||
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)))
|
||||
.build()
|
||||
.execute()
|
||||
|
@ -555,14 +624,15 @@ class ConfigurationDAOBatchService {
|
|||
.collect(Utils.toSingleton())
|
||||
.getId();
|
||||
|
||||
return this.batchConfigurationValueRecordMapper.selectByExample()
|
||||
final List<ConfigurationValueRecord> values = this.batchConfigurationValueRecordMapper.selectByExample()
|
||||
.where(ConfigurationValueRecordDynamicSqlSupport.configurationId, isEqualTo(configurationId))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.execute();
|
||||
|
||||
return values.stream()
|
||||
.collect(Collectors.toMap(
|
||||
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.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
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
|
||||
@Transactional
|
||||
public Result<Configuration> save(final Configuration data) {
|
||||
|
|
|
@ -19,7 +19,12 @@ import java.util.function.Function;
|
|||
import java.util.function.Predicate;
|
||||
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.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
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.ConfigurationValueRecordMapper;
|
||||
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.dao.ConfigurationNodeDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
|
||||
|
@ -56,18 +62,24 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
|||
private final ConfigurationNodeRecordMapper configurationNodeRecordMapper;
|
||||
private final ConfigurationValueRecordMapper configurationValueRecordMapper;
|
||||
private final ConfigurationDAOBatchService configurationDAOBatchService;
|
||||
private final String copyNamePrefix;
|
||||
private final String copyNameSuffix;
|
||||
|
||||
protected ConfigurationNodeDAOImpl(
|
||||
final ConfigurationRecordMapper configurationRecordMapper,
|
||||
final ConfigurationNodeRecordMapper configurationNodeRecordMapper,
|
||||
final ConfigurationValueRecordMapper configurationValueRecordMapper,
|
||||
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.configurationNodeRecordMapper = configurationNodeRecordMapper;
|
||||
this.configurationValueRecordMapper = configurationValueRecordMapper;
|
||||
this.configurationDAOBatchService = ConfigurationDAOBatchService;
|
||||
this.copyNamePrefix = copyNamePrefix;
|
||||
this.copyNameSuffix = copyNameSuffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -191,6 +203,84 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
|||
.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
|
||||
@Transactional
|
||||
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
|
||||
|
|
|
@ -288,11 +288,27 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
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
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<Long>> getExamIdsForConfigId(final Long configurationId) {
|
||||
return Result.tryCatch(() -> {
|
||||
final Long configNodeId = this.configurationNodeRecordMapper.selectIdsByExample()
|
||||
return this.configurationNodeRecordMapper.selectIdsByExample()
|
||||
.leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord)
|
||||
.on(
|
||||
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
|
||||
|
@ -304,17 +320,8 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.execute()
|
||||
.stream()
|
||||
.collect(Utils.toSingleton());
|
||||
|
||||
return this.examConfigurationMapRecordMapper.selectByExample()
|
||||
.where(
|
||||
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
|
||||
isEqualTo(configNodeId))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(record -> record.getExamId())
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
})
|
||||
.flatMap(this::getExamIdsForConfigNodeId);
|
||||
}
|
||||
|
||||
private Result<ExamConfigurationMapRecord> recordById(final Long id) {
|
||||
|
|
|
@ -111,7 +111,7 @@ public class ExamConfigIO {
|
|||
|
||||
// get follow-up configurationId for given configurationNodeId
|
||||
final Long configurationId = this.configurationDAO
|
||||
.getFollowupConfiguration(configurationNodeId)
|
||||
.getConfigurationLastStableVersion(configurationNodeId)
|
||||
.getOrThrow().id;
|
||||
|
||||
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.RestController;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
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.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.servicelayer.PaginationService;
|
||||
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.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.sebconfig.ConfigurationChangedEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||
|
||||
@WebServiceProfile
|
||||
|
@ -40,7 +46,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
|||
public class ConfigurationController extends ReadonlyEntityController<Configuration, Configuration> {
|
||||
|
||||
private final ConfigurationDAO configurationDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
private final ExamSessionService examSessionService;
|
||||
|
||||
protected ConfigurationController(
|
||||
final AuthorizationService authorization,
|
||||
|
@ -49,7 +57,9 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ApplicationEventPublisher applicationEventPublisher) {
|
||||
final ApplicationEventPublisher applicationEventPublisher,
|
||||
final ExamSessionService examSessionService,
|
||||
final ExamConfigurationMapDAO examConfigurationMapDAO) {
|
||||
|
||||
super(authorization,
|
||||
bulkActionService,
|
||||
|
@ -60,6 +70,8 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
|
||||
this.configurationDAO = entityDAO;
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
this.examSessionService = examSessionService;
|
||||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
|
@ -71,6 +83,7 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
|
||||
return this.entityDAO.byModelId(modelId)
|
||||
.flatMap(this.authorization::checkModify)
|
||||
.flatMap(this::checkRunningExamIntegrity)
|
||||
.flatMap(config -> this.configurationDAO.saveToHistory(config.configurationNodeId))
|
||||
.flatMap(this.userActivityLogDAO::logSaveToHistory)
|
||||
.flatMap(this::publishConfigChanged)
|
||||
|
@ -118,9 +131,53 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
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) {
|
||||
this.applicationEventPublisher.publishEvent(new ConfigurationChangedEvent(config.id));
|
||||
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 org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -71,11 +72,11 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(ConfigurationNodeController.class);
|
||||
|
||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
private final ConfigurationDAO configurationDAO;
|
||||
private final ViewDAO viewDAO;
|
||||
private final OrientationDAO orientationDAO;
|
||||
private final SebExamConfigService sebExamConfigService;
|
||||
|
||||
private final SebExamConfigTemplateService sebExamConfigTemplateService;
|
||||
|
||||
protected ConfigurationNodeController(
|
||||
|
@ -99,6 +100,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
beanValidationService);
|
||||
|
||||
this.configurationDAO = configurationDAO;
|
||||
this.configurationNodeDAO = entityDAO;
|
||||
this.viewDAO = viewDAO;
|
||||
this.orientationDAO = orientationDAO;
|
||||
this.sebExamConfigService = sebExamConfigService;
|
||||
|
@ -135,6 +137,35 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
.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(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_CONFIG_KEY_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
|
|
|
@ -13,7 +13,6 @@ import java.util.Objects;
|
|||
import javax.validation.Valid;
|
||||
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
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.ConfigurationValueDAO;
|
||||
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.validation.BeanValidationService;
|
||||
|
||||
|
@ -48,7 +46,6 @@ public class ConfigurationValueController extends EntityController<Configuration
|
|||
private final ConfigurationDAO configurationDAO;
|
||||
private final ConfigurationValueDAO configurationValueDAO;
|
||||
private final SebExamConfigService sebExamConfigService;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
protected ConfigurationValueController(
|
||||
final AuthorizationService authorization,
|
||||
|
@ -58,8 +55,7 @@ public class ConfigurationValueController extends EntityController<Configuration
|
|||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ConfigurationDAO configurationDAO,
|
||||
final SebExamConfigService sebExamConfigService,
|
||||
final ApplicationEventPublisher applicationEventPublisher) {
|
||||
final SebExamConfigService sebExamConfigService) {
|
||||
|
||||
super(authorization,
|
||||
bulkActionService,
|
||||
|
@ -71,19 +67,6 @@ public class ConfigurationValueController extends EntityController<Configuration
|
|||
this.configurationDAO = configurationDAO;
|
||||
this.configurationValueDAO = entityDAO;
|
||||
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
|
||||
|
@ -141,11 +124,6 @@ public class ConfigurationValueController extends EntityController<Configuration
|
|||
return this.configurationDAO.byPK(tableValue.configurationId)
|
||||
.flatMap(this.authorization::checkModify)
|
||||
.flatMap(config -> this.configurationValueDAO.saveTableValues(tableValue))
|
||||
.map(config -> {
|
||||
this.applicationEventPublisher.publishEvent(
|
||||
new ConfigurationChangedEvent(config.configurationId));
|
||||
return config;
|
||||
})
|
||||
.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.enable-indicator-cache=true
|
||||
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
|
||||
sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
|
||||
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.view=View Configuration
|
||||
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.view=View Configuration
|
||||
sebserver.examconfig.action.modify=Edit Settings
|
||||
sebserver.examconfig.action.modify.properties=Edit Configuration
|
||||
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.undo=Undo
|
||||
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.name=Name
|
||||
sebserver.examconfig.form.description=Description
|
||||
sebserver.examconfig.form.template=From Template
|
||||
sebserver.examconfig.form.status=Status
|
||||
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.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.view=View Template
|
||||
sebserver.configtemplate.action.list.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.name=Name
|
||||
sebserver.configtemplate.form.description=Description
|
||||
|
|
|
@ -244,17 +244,32 @@ Text[BORDER].error, Text[MULTI][BORDER].error {
|
|||
|
||||
Text[BORDER]:focused, Text[MULTI][BORDER]:focused {
|
||||
border: 1px solid #4f7cb1;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
Text:disabled,
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Text:disabled,
|
||||
Text:read-only,
|
||||
Text[BORDER]:disabled,
|
||||
Text[BORDER]:read-only,
|
||||
Text[MULTI]:disabled,
|
||||
Text[MULTI]:read-only,
|
||||
Text[MULTI][BORDER]:disabled,
|
||||
Text[MULTI][BORDER]:read-only {
|
||||
Text[MULTI]:disabled,
|
||||
Text[MULTI]:read-only {
|
||||
box-shadow: none;
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
|
@ -460,6 +475,14 @@ Button[PUSH]:hover.header {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
Button[CHECK]:disabled {
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
Button[RADIO]:disabled {
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
FileUpload,
|
||||
FileUpload:default,
|
||||
FileUpload:hover,
|
||||
|
|
|
@ -247,13 +247,18 @@ Text[BORDER]:focused, Text[MULTI][BORDER]:focused {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
Text:disabled,
|
||||
Text[BORDER]:disabled,
|
||||
Text[MULTI][BORDER]:disabled {
|
||||
border: 1px solid #4a4a4a;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
Text:disabled,
|
||||
Text:read-only,
|
||||
Text[BORDER]:disabled,
|
||||
Text[MULTI]:disabled,
|
||||
Text[BORDER]:read-only,
|
||||
Text[MULTI]:disabled,
|
||||
Text[MULTI]:read-only,
|
||||
Text[MULTI][BORDER]:disabled,
|
||||
Text[MULTI]:read-only,
|
||||
Text[MULTI][BORDER]:read-only {
|
||||
box-shadow: none;
|
||||
background-color: #ffffff;
|
||||
|
|
Loading…
Reference in a new issue