SEBSERV-376 migration task and GUI implementation

This commit is contained in:
anhefti 2023-02-02 16:50:20 +01:00
parent 2062c3cddb
commit a6f7c501ca
9 changed files with 504 additions and 13 deletions

View file

@ -171,7 +171,7 @@ public class SEBSettingsForm implements TemplateComposer {
view,
viewContextSupplier,
attributes,
20,
30,
readonly,
publishedMessagePanelViewCallback);
viewContexts.add(viewContext);

View file

@ -216,7 +216,7 @@ public abstract class FieldBuilder<T> {
public static Label createErrorLabel(final Composite innerGrid) {
final Label errorLabel = new Label(innerGrid, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
errorLabel.setLayoutData(gridData);
errorLabel.setVisible(false);
errorLabel.setData(RWT.CUSTOM_VARIANT, CustomVariant.ERROR.key);

View file

@ -0,0 +1,155 @@
/*
* Copyright (c) 2023 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.examconfig.impl;
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;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.form.FieldBuilder;
import ch.ethz.seb.sebserver.gui.service.examconfig.ExamConfigurationService;
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.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.widget.TextListInput;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@Lazy
@Component
@GuiProfile
public class TextFieldListBuilder extends AbstractTableFieldBuilder {
protected TextFieldListBuilder(
final RestService restService,
final WidgetFactory widgetFactory) {
super(restService, widgetFactory);
}
@Override
public boolean builderFor(final ConfigurationAttribute attribute, final Orientation orientation) {
return AttributeType.TEXT_FIELD_LIST == attribute.type;
}
@Override
public InputField createInputField(
final Composite parent,
final ConfigurationAttribute attribute,
final ViewContext viewContext) {
final Orientation orientation = viewContext
.getOrientation(attribute.id);
final Composite innerGrid = InputFieldBuilder
.createInnerGrid(parent, attribute, orientation);
// final Composite scroll = PageService.createManagedVScrolledComposite(
// innerGrid,
// scrolledComposite -> {
// final Composite result = new Composite(scrolledComposite, SWT.NONE);
// final GridLayout gridLayout1 = new GridLayout();
// result.setLayout(gridLayout1);
// final GridData gridData1 = new GridData(SWT.FILL, SWT.FILL, true, true);
// result.setLayoutData(gridData1);
// return result;
// },
// false,
// false, true);
final String attributeNameKey = ExamConfigurationService.attributeNameKey(attribute);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
gridData.minimumHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
final TextListInput textListInput = new TextListInput(
innerGrid,
new LocTextKey(attributeNameKey),
3,
this.widgetFactory);
WidgetFactory.setTestId(textListInput, attributeNameKey);
textListInput.setLayoutData(gridData);
final TextListInputField textListInputField = new TextListInputField(
attribute,
orientation,
textListInput,
FieldBuilder.createErrorLabel(innerGrid));
if (viewContext.readonly) {
textListInput.setEditable(false);
} else {
final Listener valueChangeEventListener = event -> {
textListInputField.clearError();
viewContext.getValueChangeListener().valueChanged(
viewContext,
attribute,
textListInputField.getValue(),
textListInputField.listIndex);
};
textListInput.addListener(valueChangeEventListener);
}
return textListInputField;
}
static final class TextListInputField extends AbstractInputField<TextListInput> {
TextListInputField(
final ConfigurationAttribute attribute,
final Orientation orientation,
final TextListInput control,
final Label errorLabel) {
super(attribute, orientation, control, errorLabel);
}
@Override
protected void setValueToControl(final String value) {
if (value == null) {
this.control.setValue(StringUtils.EMPTY);
return;
}
this.control.setValue(value);
}
@Override
public void enable(final boolean group) {
this.control.setData(RWT.CUSTOM_VARIANT, null);
this.control.setEditable(true);
}
@Override
public void disable(final boolean group) {
this.control.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key);
this.control.setEditable(false);
final GridData gridData = (GridData) this.control.getLayoutData();
gridData.heightHint = (this.attribute.type == AttributeType.TEXT_AREA)
? WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT
: WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
}
@Override
public String getValue() {
return this.control.getValue();
}
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 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.widget;
import ch.ethz.seb.sebserver.gui.widget.GridTable.ColumnDef;
interface ControlAdapter {
String getValue();
void setValue(String value);
void dispose();
ColumnDef columnDef();
}

View file

@ -305,16 +305,6 @@ public class GridTable extends Composite {
}
}
interface ControlAdapter {
String getValue();
void setValue(String value);
void dispose();
ColumnDef columnDef();
}
private static class Dummy implements ControlAdapter {
private final Label label;

View file

@ -0,0 +1,204 @@
/*
* Copyright (c) 2023 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.widget;
import java.util.ArrayList;
import java.util.List;
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;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
public class TextListInput extends Composite {
private static final long serialVersionUID = 1361754245260627626L;
private static final int ACTION_COLUMN_WIDTH = 20;
private final LocTextKey nameKey;
private final int initialSize;
private final WidgetFactory widgetFactory;
private final Button addAction;
private final Composite content;
private final List<Row> list = new ArrayList<>();
private Listener valueChangeEventListener = null;
public TextListInput(
final Composite parent,
final LocTextKey nameKey,
final int initialSize,
final WidgetFactory widgetFactory) {
super(parent, SWT.NONE);
this.nameKey = nameKey;
this.initialSize = initialSize;
this.widgetFactory = widgetFactory;
// main grid layout
GridLayout gridLayout = new GridLayout(1, false);
gridLayout.verticalSpacing = 1;
gridLayout.marginLeft = 0;
gridLayout.marginHeight = 5;
gridLayout.marginWidth = 0;
gridLayout.horizontalSpacing = 0;
this.setLayout(gridLayout);
final GridData gridData2 = new GridData(SWT.FILL, SWT.FILL, true, true);
this.setLayoutData(gridData2);
// build header
final Composite header = new Composite(this, SWT.NONE);
gridLayout = new GridLayout(2, false);
header.setLayout(gridLayout);
GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
header.setLayoutData(gridData);
final Label label = widgetFactory.labelLocalized(header, this.nameKey);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
label.setLayoutData(gridData);
final LocTextKey toolTipKey = new LocTextKey(nameKey.name + ".tooltip");
if (widgetFactory.getI18nSupport().hasText(toolTipKey)) {
label.setToolTipText(Utils.formatLineBreaks(
widgetFactory.getI18nSupport().getText(toolTipKey)));
}
this.addAction = widgetFactory.imageButton(
ImageIcon.ADD_BOX,
header,
new LocTextKey(this.nameKey.name + ".addAction"),
this::addRow);
gridData = new GridData(SWT.RIGHT, SWT.CENTER, false, true);
gridData.widthHint = ACTION_COLUMN_WIDTH;
this.addAction.setLayoutData(gridData);
this.content = header;
for (int i = 0; i < initialSize; i++) {
addRow(null);
}
}
public void addListener(final Listener valueChangeEventListener) {
this.valueChangeEventListener = valueChangeEventListener;
this.list.stream().forEach(row -> row.addListener());
}
void addRow(final Event event) {
this.list.add(new Row(this.list.size()));
this.content.getParent().getParent().layout(true, true);
}
public String getValue() {
return this.list.stream()
.map(row -> row.textInput.getText())
.reduce("", (acc, val) -> {
if (StringUtils.isNotBlank(val)) {
if (StringUtils.isNotBlank(acc)) {
acc += Constants.LIST_SEPARATOR;
}
acc += val;
}
return acc;
});
}
public void setValue(final String value) {
System.out.println("************ value: " + value);
if (StringUtils.isBlank(value)) {
// clear rows
new ArrayList<>(this.list).stream().forEach(row -> row.deleteRow());
this.list.clear();
// and fill with default empty
for (int i = 0; i < this.initialSize; i++) {
addRow(null);
}
return;
}
final String[] split = StringUtils.split(value, Constants.LIST_SEPARATOR);
int gap = this.list.size() - split.length;
while (gap < 0) {
addRow(null);
gap++;
}
for (int i = 0; i < split.length; i++) {
this.list.get(i).textInput.setText(split[i]);
}
}
public void setEditable(final boolean b) {
this.addAction.setEnabled(b);
this.list.stream().forEach(row -> row.setEditable(b));
}
private final class Row {
public final Text textInput;
public final Button deleteButton;
public Row(final int index) {
this.textInput = TextListInput.this.widgetFactory.textInput(
TextListInput.this.content,
TextListInput.this.nameKey);
GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
this.textInput.setLayoutData(gridData);
this.addListener();
this.deleteButton = TextListInput.this.widgetFactory.imageButton(
ImageIcon.REMOVE_BOX,
TextListInput.this.content,
new LocTextKey(TextListInput.this.nameKey.name + ".removeAction"),
deleteEvent -> deleteRow());
gridData = new GridData(SWT.RIGHT, SWT.CENTER, false, true);
gridData.widthHint = ACTION_COLUMN_WIDTH;
this.deleteButton.setLayoutData(gridData);
}
private void addListener() {
if (TextListInput.this.valueChangeEventListener != null) {
this.textInput.addListener(SWT.FocusOut, TextListInput.this.valueChangeEventListener);
this.textInput.addListener(SWT.Traverse, TextListInput.this.valueChangeEventListener);
}
}
public void deleteRow() {
TextListInput.this.list.remove(this);
this.textInput.dispose();
this.deleteButton.dispose();
TextListInput.this.content.getParent().getParent().layout(true, true);
if (TextListInput.this.valueChangeEventListener != null) {
TextListInput.this.valueChangeEventListener.handleEvent(null);
}
}
public void setEditable(final boolean e) {
this.textInput.setEditable(e);
this.textInput.setData(
RWT.CUSTOM_VARIANT,
e ? null : CustomVariant.CONFIG_INPUT_READONLY.key);
this.deleteButton.setEnabled(e);
}
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 2023 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.webservice.servicelayer.sebconfig.impl.validation;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator;
@Lazy
@Component
@WebServiceProfile
public class SEBVersionValidator implements ConfigurationValueValidator {
public static final String NAME = "SEBVersionValidator";
private final ConfigurationValueDAO configurationValueDAO;
public SEBVersionValidator(final ConfigurationValueDAO configurationValueDAO) {
this.configurationValueDAO = configurationValueDAO;
}
@Override
public String name() {
return NAME;
}
@Override
public boolean validate(final ConfigurationValue value, final ConfigurationAttribute attribute) {
// if validator is not specified --> skip
if (!name().equals(attribute.validator)) {
return true;
}
if (StringUtils.isBlank(value.value)) {
return true;
}
final String[] split = StringUtils.split(value.value, Constants.LIST_SEPARATOR);
for (int i = 0; i < split.length; i++) {
if (!isValidSEBVersionMarker(split[i])) {
return false;
}
}
return true;
}
private boolean isValidSEBVersionMarker(final String versionMarker) {
// TODO Auto-generated method stub
return false;
}
@Override
public String createErrorMessage(final ConfigurationValue value, final ConfigurationAttribute attribute) {
if (StringUtils.isBlank(value.value)) {
return ConfigurationValueValidator.super.createErrorMessage(value, attribute);
}
final String[] split = StringUtils.split(value.value, Constants.LIST_SEPARATOR);
final List<String> newValues = new ArrayList<>();
for (int i = 0; i < split.length; i++) {
if (isValidSEBVersionMarker(split[i])) {
newValues.add(split[i]);
}
}
// save with the removed invalid values
if (!newValues.isEmpty()) {
this.configurationValueDAO.save(new ConfigurationValue(
value.id,
value.institutionId,
value.configurationId,
value.attributeId,
value.listIndex,
StringUtils.join(newValues, Constants.LIST_SEPARATOR)));
}
return ConfigurationValueValidator.super.createErrorMessage(value, attribute);
}
}

View file

@ -0,0 +1,23 @@
-- -----------------------------------------------------------------
-- SEBSERV-376 Add SEBVersionValidator to setting
-- -----------------------------------------------------------------
UPDATE configuration_attribute SET validator='SEBVersionValidator' WHERE id=1578;
-- -----------------------------------------------------------------
-- SEBSERV-376 Reorder security / logging section for default template
-- -----------------------------------------------------------------
UPDATE orientation SET width=6 WHERE config_attribute_id=305 AND template_id=0;
UPDATE orientation SET width=4 WHERE config_attribute_id=306 AND template_id=0;
UPDATE orientation SET width=4 WHERE config_attribute_id=307 AND template_id=0;
UPDATE orientation SET width=4 WHERE config_attribute_id=317 AND template_id=0;
UPDATE orientation SET width=4 WHERE config_attribute_id=319 AND template_id=0;
UPDATE orientation SET width=4 WHERE config_attribute_id=320 AND template_id=0;
-- -----------------------------------------------------------------
-- SEBSERV-376 add new orientation for default template
-- -----------------------------------------------------------------
INSERT IGNORE INTO orientation (config_attribute_id, template_id, view_id, group_id, x_position, y_position, width, height, title) VALUES
(1578, 0, 9, null, 7, 14, 4, 12, 'NONE');

View file

@ -1841,7 +1841,9 @@ sebserver.examconfig.props.label.prohibitedProcesses.ignoreInAAC=Ignore in AAC
sebserver.examconfig.props.label.prohibitedProcesses.ignoreInAAC.tooltip=When using the AAC kiosk mode (which prevents network and screen access for other processes), ignore this prohibited process
sebserver.examconfig.props.label.sebAllowedVersions=Allowed SEB Versions
sebserver.examconfig.props.label.sebAllowedVersions.tooltip=List of text inputs which represent either a specific or a minimal SEB version which is allowed to access the (exam) session using current settings.<br/>The version string has the following format: [OS.mayor.minor.patch(.minimal)], OS and mayor version are mandatory,<br/>"minimal" indicates if version shall be interpreted as minimal version this and all above are valid.
sebserver.examconfig.props.label.sebAllowedVersions.addAction=Add new allowed SEB version
sebserver.examconfig.props.label.sebAllowedVersions.deleteAction=Delete allowed SEB version
sebserver.examconfig.props.validation.SEBVersionValidator=At least one SEB Version has wrong format
################################
# SEB Exam Configuration Template