SEBSERV-30 IndicatorList

This commit is contained in:
anhefti 2019-03-27 11:44:28 +01:00
parent 100bf77bd4
commit 7ccacfcb73
40 changed files with 606 additions and 191 deletions

View file

@ -36,17 +36,17 @@ public final class Constants {
.forPattern(DEFAULT_DATE_TIME_FORMAT)
.withZoneUTC();
/** Date-Time formatter without milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss */
// TODO check if this works with DEFAULT_DATE_TIME_FORMAT
@Deprecated
public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_NO_MILLIS = DateTimeFormat
.forPattern("yyyy-MM-dd HH:mm:ss")
.withZoneUTC();
/** Date-Time formatter with milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss.S */
@Deprecated
public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_MILLIS = DateTimeFormat
.forPattern("yyyy-MM-dd HH:mm:ss.S")
.withZoneUTC();
// /** Date-Time formatter without milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss */
// // TODO check if this works with DEFAULT_DATE_TIME_FORMAT
// @Deprecated
// public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_NO_MILLIS = DateTimeFormat
// .forPattern("yyyy-MM-dd HH:mm:ss")
// .withZoneUTC();
//
// /** Date-Time formatter with milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss.S */
// @Deprecated
// public static final DateTimeFormatter DATE_TIME_PATTERN_UTC_MILLIS = DateTimeFormat
// .forPattern("yyyy-MM-dd HH:mm:ss.S")
// .withZoneUTC();
}

View file

@ -113,7 +113,7 @@ public final class Utils {
public static Result<Long> dateTimeStringToTimestamp(final String startTime) {
return Result.tryCatch(() -> {
return DateTime.parse(startTime, Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS).getMillis();
return DateTime.parse(startTime, Constants.STANDARD_DATE_TIME_FORMATTER).getMillis();
});
}
@ -143,11 +143,7 @@ public final class Utils {
return null;
}
if (dateString.contains(".")) {
return DateTime.parse(dateString, Constants.DATE_TIME_PATTERN_UTC_MILLIS);
} else {
return DateTime.parse(dateString, Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
}
return DateTime.parse(dateString, Constants.STANDARD_DATE_TIME_FORMATTER);
}
public static Long toMilliSeconds(final String dateString) {

View file

@ -42,6 +42,7 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
@ -113,6 +114,7 @@ public class ExamForm implements TemplateComposer {
// new PageContext with actual EntityKey
final PageContext formContext = pageContext.withEntityKey(exam.getEntityKey());
// the default page layout with title
final LocTextKey titleKey = new LocTextKey(
importFromQuizData
@ -270,11 +272,13 @@ public class ExamForm implements TemplateComposer {
.publishIf(() -> modifyGrant)
.createAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.withSelect(indicatorTable::getSelection, Action::applySingleSelection, emptySelectionTextKey)
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent())
.createAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
.withSelect(indicatorTable::getSelection, Action::applySingleSelection, emptySelectionTextKey)
.withEntityKey(entityKey)
.withSelect(indicatorTable::getSelection, this::deleteSelectedIndicator, emptySelectionTextKey)
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent());
// TODO List of attached SEB Configurations
@ -283,6 +287,15 @@ public class ExamForm implements TemplateComposer {
}
private Action deleteSelectedIndicator(final Action action) {
final EntityKey indicatorKey = action.getSingleSelection();
this.resourceService.getRestService()
.getBuilder(DeleteIndicator.class)
.withURIVariable(API.PARAM_MODEL_ID, indicatorKey.modelId)
.call();
return action;
}
private Result<Exam> getExistingExam(final EntityKey entityKey, final RestService restService) {
return restService.getBuilder(GetExam.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
@ -330,7 +343,7 @@ public class ExamForm implements TemplateComposer {
if (importFromQuizData) {
final PageContext pageContext = action.pageContext();
final Action activityHomeAction = pageContext.createAction(ActionDefinition.QUIZ_DISCOVERY_VIEW_LIST);
action.pageContext().publishPageEvent(new ActionEvent(activityHomeAction, false));
action.pageContext().firePageEvent(new ActionEvent(activityHomeAction, false));
return activityHomeAction;
}

View file

@ -19,14 +19,12 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.form.PageFormService;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
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.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
@ -36,7 +34,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicator;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewIndicator;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicator;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@ -46,9 +43,6 @@ public class IndicatorForm implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(IndicatorForm.class);
private final static LocTextKey listTitleKey =
new LocTextKey("sebserver.exam.indicator.thresholds.list.title");
private final PageFormService pageFormService;
private final ResourceService resourceService;
@ -62,12 +56,8 @@ public class IndicatorForm implements TemplateComposer {
@Override
public void compose(final PageContext pageContext) {
final CurrentUser currentUser = this.resourceService.getCurrentUser();
final RestService restService = this.resourceService.getRestService();
final WidgetFactory widgetFactory = this.pageFormService.getWidgetFactory();
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
final UserInfo user = currentUser.get();
final EntityKey entityKey = pageContext.getEntityKey();
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
final boolean isNew = entityKey == null;
@ -134,9 +124,13 @@ public class IndicatorForm implements TemplateComposer {
(indicator.type != null) ? indicator.type.name() : null,
this.resourceService::indicatorTypeResources))
.addField(FormBuilder.colorSelection(
Domain.INDICATOR.ATTR_TYPE,
Domain.INDICATOR.ATTR_COLOR,
"sebserver.exam.indicator.form.color",
indicator.defaultColor))
.addField(FormBuilder.thresholdList(
Domain.THRESHOLD.REFERENCE_NAME,
"sebserver.exam.indicator.form.thresholds",
indicator.getThresholds()))
.buildFor((isNew)
? restService.getRestCall(NewIndicator.class)
: restService.getRestCall(SaveIndicator.class));
@ -144,7 +138,7 @@ public class IndicatorForm implements TemplateComposer {
// propagate content actions to action-pane
formContext.clearEntityKeys()
.createAction(ActionDefinition.EXAM_INDICATOR_SAVE)
.withParentEntityKey(parentEntityKey)
.withEntityKey(parentEntityKey)
.withExec(formHandle::processFormSave)
.publishIf(() -> !isReadonly)

View file

@ -174,6 +174,7 @@ public class InstitutionForm implements TemplateComposer {
.publishIf(() -> writeGrant && isReadonly && !institution.isActive())
.createAction(ActionDefinition.INSTITUTION_SAVE)
.withEntityKey(entityKey)
.withExec(formHandle::processFormSave)
.publishIf(() -> !isReadonly)

View file

@ -200,6 +200,7 @@ public class LmsSetupForm implements TemplateComposer {
.publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive())
.createAction(ActionDefinition.LMS_SETUP_SAVE)
.withEntityKey(entityKey)
.withExec(formHandle::processFormSave)
.publishIf(() -> !readonly)

View file

@ -66,13 +66,13 @@ public class LoginPage implements TemplateComposer {
final Label name = this.widgetFactory.labelLocalized(loginGroup, "sebserver.login.username");
name.setLayoutData(new GridData(300, -1));
name.setAlignment(SWT.BOTTOM);
final Text loginName = new Text(loginGroup, SWT.LEFT | SWT.BORDER);
final Text loginName = this.widgetFactory.textInput(loginGroup);
loginName.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
GridData gridData = new GridData(SWT.FILL, SWT.TOP, false, false);
gridData.verticalIndent = 10;
final Label pwd = this.widgetFactory.labelLocalized(loginGroup, "sebserver.login.pwd");
pwd.setLayoutData(gridData);
final Text loginPassword = new Text(loginGroup, SWT.LEFT | SWT.PASSWORD | SWT.BORDER);
final Text loginPassword = this.widgetFactory.passwordInput(loginGroup);
loginPassword.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
final Button button = this.widgetFactory.buttonLocalized(loginGroup, "sebserver.login.login");

View file

@ -212,6 +212,7 @@ public class UserAccountForm implements TemplateComposer {
.publishIf(() -> writeGrant && readonly && institutionActive && !userAccount.isActive())
.createAction(ActionDefinition.USER_ACCOUNT_SAVE)
.withEntityKey(entityKey)
.withExec(action -> {
final Action postChanges = formHandle.processFormSave(action);
if (ownAccount) {

View file

@ -175,7 +175,15 @@ public class ActionPane implements TemplateComposer {
new ArrayList<>(this.actionTrees.entrySet())
.stream()
.forEach(entry -> {
if (entry.getValue().isDisposed()) {
final Control c = entry.getValue();
// of tree is already disposed.. remove it
if (c.isDisposed()) {
this.actionTrees.remove(entry.getKey());
}
// check access from current thread
try {
c.getBounds();
} catch (final Exception e) {
this.actionTrees.remove(entry.getKey());
}
});

View file

@ -162,7 +162,7 @@ public class ActivitiesPane implements TemplateComposer {
if (mainPageState.action == null) {
mainPageState.action = getActivitySelection(navigation.getItem(0));
}
pageContext.publishPageEvent(
pageContext.firePageEvent(
new ActionEvent(mainPageState.action, false));
navigation.select(navigation.getItem(0));
@ -177,7 +177,7 @@ public class ActivitiesPane implements TemplateComposer {
final Action action = getActivitySelection(treeItem);
if (mainPageState.action.definition != action.definition) {
mainPageState.action = action;
composerCtx.publishPageEvent(
composerCtx.firePageEvent(
new ActionEvent(action, true));
}
}

View file

@ -10,7 +10,7 @@ package ch.ethz.seb.sebserver.gui.form;
import java.util.function.BooleanSupplier;
public abstract class FieldBuilder {
public abstract class FieldBuilder<T> {
int spanLabel = -1;
int spanInput = -1;
int spanEmptyCell = -1;
@ -22,55 +22,55 @@ public abstract class FieldBuilder {
final String name;
final String label;
final String value;
final T value;
protected FieldBuilder(final String name, final String label, final String value) {
protected FieldBuilder(final String name, final String label, final T value) {
this.name = name;
this.label = label;
this.value = value;
}
public FieldBuilder withLabelSpan(final int span) {
public FieldBuilder<T> withLabelSpan(final int span) {
this.spanLabel = span;
return this;
}
public FieldBuilder withInputSpan(final int span) {
public FieldBuilder<T> withInputSpan(final int span) {
this.spanInput = span;
return this;
}
public FieldBuilder withEmptyCellSpan(final int span) {
public FieldBuilder<T> withEmptyCellSpan(final int span) {
this.spanEmptyCell = span;
return this;
}
public FieldBuilder withEmptyCellSeparation(final boolean separation) {
public FieldBuilder<T> withEmptyCellSeparation(final boolean separation) {
this.autoEmptyCellSeparation = separation;
return this;
}
public FieldBuilder withGroup(final String group) {
public FieldBuilder<T> withGroup(final String group) {
this.group = group;
return this;
}
public FieldBuilder withCondition(final BooleanSupplier condition) {
public FieldBuilder<T> withCondition(final BooleanSupplier condition) {
this.condition = condition;
return this;
}
public FieldBuilder readonly(final boolean readonly) {
public FieldBuilder<T> readonly(final boolean readonly) {
this.readonly = readonly;
return this;
}
public FieldBuilder visibleIf(final boolean visible) {
public FieldBuilder<T> visibleIf(final boolean visible) {
this.visible = visible;
return this;
}
public FieldBuilder readonlyIf(final BooleanSupplier readonly) {
public FieldBuilder<T> readonlyIf(final BooleanSupplier readonly) {
this.readonly = readonly != null && readonly.getAsBoolean();
return this;
}

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.gui.form;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@ -30,10 +31,12 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding;
import ch.ethz.seb.sebserver.gui.widget.ImageUpload;
import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.ThresholdList;
public final class Form implements FormBinding {
@ -69,7 +72,7 @@ public final class Form implements FormBinding {
for (final Map.Entry<String, List<FormFieldAccessor>> entry : this.formFields.entrySet()) {
entry.getValue()
.stream()
.forEach(ffa -> appendFormUrlEncodedValue(buffer, entry.getKey(), ffa.getValue()));
.forEach(ffa -> appendFormUrlEncodedValue(buffer, entry.getKey(), ffa.getStringValue()));
}
return buffer.toString();
@ -106,6 +109,10 @@ public final class Form implements FormBinding {
this.formFields.add(name, createAccessor(label, field));
}
public void putField(final String name, final Label label, final ThresholdList field) {
this.formFields.add(name, createAccessor(label, field));
}
public void putField(
final String name,
final Label label,
@ -174,7 +181,7 @@ public final class Form implements FormBinding {
for (final Map.Entry<String, List<FormFieldAccessor>> entry : this.formFields.entrySet()) {
entry.getValue()
.stream()
.filter(ffa -> StringUtils.isNoneBlank(ffa.getValue()))
.filter(ffa -> StringUtils.isNoneBlank(ffa.getStringValue()))
.forEach(ffa -> ffa.putJsonValue(entry.getKey(), this.objectRoot));
}
}
@ -183,14 +190,12 @@ public final class Form implements FormBinding {
//@formatter:off
private FormFieldAccessor createAccessor(final Label label, final Label field) {
return new FormFieldAccessor(label, field) {
@Override public String getValue() { return null; }
@Override public void setValue(final String value) { field.setText(value); }
@Override public String getStringValue() { return null; }
};
}
private FormFieldAccessor createAccessor(final Label label, final Text text) {
return new FormFieldAccessor(label, text) {
@Override public String getValue() { return text.getText(); }
@Override public void setValue(final String value) { text.setText(value); }
@Override public String getStringValue() { return text.getText(); }
};
}
private FormFieldAccessor createAccessor(final Label label, final Selection selection) {
@ -207,14 +212,30 @@ public final class Form implements FormBinding {
final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter) {
return new FormFieldAccessor(label, selection.adaptToControl(), jsonValueAdapter) {
@Override public String getValue() { return selection.getSelectionValue(); }
@Override public void setValue(final String value) { selection.select(value); }
@Override public String getStringValue() { return selection.getSelectionValue(); }
};
}
private FormFieldAccessor createAccessor(final Label label, final ThresholdList thresholdList) {
return new FormFieldAccessor(label, thresholdList) {
@Override public String getStringValue() {
return ThresholdListBuilder
.thresholdsToFormURLEncodedStringValue(thresholdList.getThresholds());
}
@Override
public void putJsonValue(final String key, final ObjectNode objectRoot) {
final Collection<Threshold> thresholds = thresholdList.getThresholds();
if (thresholds == null || thresholds.isEmpty()) {
return;
}
final ArrayNode array = Form.this.jsonMapper.valueToTree(thresholds);
objectRoot.putArray(key).addAll(array);
}
};
}
private FormFieldAccessor createAccessor(final Label label, final ImageUpload imageUpload) {
return new FormFieldAccessor(label, imageUpload) {
@Override public String getValue() { return imageUpload.getImageBase64(); }
@Override public void setValue(final String value) { imageUpload.setImageBase64(value); }
@Override public String getStringValue() { return imageUpload.getImageBase64(); }
};
}
//@formatter:on
@ -224,23 +245,38 @@ public final class Form implements FormBinding {
* Checks first if the value String is a comma separated list. If true, splits values
* and adds every value within the same name mapping to the string buffer
*/
private void appendFormUrlEncodedValue(final StringBuffer buffer, final String name, final String value) {
private static void appendFormUrlEncodedValue(final StringBuffer buffer, final String name, final String value) {
if (StringUtils.isBlank(value)) {
return;
}
final String[] split = StringUtils.split(value, Constants.LIST_SEPARATOR_CHAR);
if (split != null) {
for (int i = 0; i < split.length; i++) {
if (StringUtils.isNoneBlank(split[i])) {
if (buffer.length() > 0) {
buffer.append(Constants.FORM_URL_ENCODED_SEPARATOR);
}
buffer.append(name)
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(split[i]);
}
for (int i = 0; i < split.length; i++) {
if (StringUtils.isBlank(split[i])) {
continue;
}
if (buffer.length() > 0) {
buffer.append(Constants.FORM_URL_ENCODED_SEPARATOR);
}
// check of the string value is a name-value pair. If true, use the specified name an value
// otherwise use the general name given within this method call and
if (split[i].contains(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)) {
final String[] nameValue = StringUtils.split(split[i], Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR);
buffer.append(nameValue[0])
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(nameValue[1]);
} else {
buffer.append(name)
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(split[i]);
}
}
}
private static final void adaptCommaSeparatedStringToJsonArray(final Tuple<String> tuple,
private static final void adaptCommaSeparatedStringToJsonArray(
final Tuple<String> tuple,
final ObjectNode jsonNode) {
if (StringUtils.isNoneBlank(tuple._2)) {
final ArrayNode arrayNode = jsonNode.putArray(tuple._1);
@ -280,17 +316,15 @@ public final class Form implements FormBinding {
}
}
public abstract String getValue();
public abstract void setValue(String value);
public abstract String getStringValue();
public void setVisible(final boolean visible) {
this.label.setVisible(visible);
this.control.setVisible(visible);
}
public final void putJsonValue(final String key, final ObjectNode objectRoot) {
this.jsonValueAdapter.accept(new Tuple<>(key, getValue()), objectRoot);
public void putJsonValue(final String key, final ObjectNode objectRoot) {
this.jsonValueAdapter.accept(new Tuple<>(key, getStringValue()), objectRoot);
}
public void setError(final String errorTooltip) {

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.gui.form;
import java.util.Collection;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
@ -26,6 +27,7 @@ import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
@ -39,6 +41,7 @@ public class FormBuilder {
private static final Logger log = LoggerFactory.getLogger(FormBuilder.class);
final WidgetFactory widgetFactory;
final JSONMapper jsonMapper;
private final PolyglotPageService polyglotPageService;
public final PageContext pageContext;
public final Composite formParent;
@ -58,6 +61,7 @@ public class FormBuilder {
final int rows) {
this.widgetFactory = widgetFactory;
this.jsonMapper = jsonMapper;
this.polyglotPageService = polyglotPageService;
this.pageContext = pageContext;
this.form = new Form(jsonMapper);
@ -145,7 +149,7 @@ public class FormBuilder {
return this;
}
public FormBuilder addField(final FieldBuilder template) {
public FormBuilder addField(final FieldBuilder<?> template) {
if (template.condition == null || template.condition.getAsBoolean()) {
template.spanLabel = (template.spanLabel < 0) ? this.defaultSpanLabel : template.spanLabel;
template.spanInput = (template.spanInput < 0) ? this.defaultSpanInput : template.spanInput;
@ -224,6 +228,14 @@ public class FormBuilder {
return new SelectionFieldBuilder(Selection.Type.COLOR, name, label, value, null);
}
public static ThresholdListBuilder thresholdList(
final String name,
final String label,
final Collection<Threshold> value) {
return new ThresholdListBuilder(name, label, value);
}
public static ImageUploadFieldBuilder imageUpload(final String name, final String label, final String value) {
return new ImageUploadFieldBuilder(name, label, value);
}

View file

@ -82,17 +82,19 @@ public class FormHandle<T extends Entity> {
* go to the read-only-view of the specified form to indicate a successful form post
* or stay within the edit-mode of the form and indicate errors or field validation messages
* to the user on error case.
*
*
* @param postResult The form post result
* @param action the action that was applied with the form post
* @return the new Action that was used to stay on page or go the read-only-view of the form */
public Action handleFormPost(final Result<T> postResult, final Action action) {
return postResult
.map(result -> {
final Action resultAction = action.createNew()
.withAttribute(AttributeKeys.READ_ONLY, "true")
.withEntityKey(result.getEntityKey());
action.pageContext().publishPageEvent(new ActionEvent(resultAction, false));
Action resultAction = action.createNew()
.withAttribute(AttributeKeys.READ_ONLY, "true");
if (resultAction.getEntityKey() == null) {
resultAction = resultAction.withEntityKey(result.getEntityKey());
}
action.pageContext().firePageEvent(new ActionEvent(resultAction, false));
return resultAction;
})
.onErrorDo(this::handleError)

View file

@ -15,7 +15,7 @@ import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.ImageUpload;
public final class ImageUploadFieldBuilder extends FieldBuilder {
public final class ImageUploadFieldBuilder extends FieldBuilder<String> {
ImageUploadFieldBuilder(final String name, final String label, final String value) {
super(name, label, value);

View file

@ -30,7 +30,7 @@ import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
public final class SelectionFieldBuilder extends FieldBuilder {
public final class SelectionFieldBuilder extends FieldBuilder<String> {
final Supplier<List<Tuple<String>>> itemsSupplier;
Consumer<Form> selectionListener = null;

View file

@ -13,9 +13,10 @@ import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
public final class TextFieldBuilder extends FieldBuilder {
public final class TextFieldBuilder extends FieldBuilder<String> {
boolean isPassword = false;
boolean isNumber = false;
TextFieldBuilder(final String name, final String label, final String value) {
super(name, label, value);
@ -26,6 +27,11 @@ public final class TextFieldBuilder extends FieldBuilder {
return this;
}
public TextFieldBuilder asNumber() {
this.isNumber = true;
return this;
}
@Override
void build(final FormBuilder builder) {
if (this.isPassword && builder.readonly) {
@ -38,11 +44,11 @@ public final class TextFieldBuilder extends FieldBuilder {
builder.valueLabel(builder.formParent, this.value, this.spanInput));
builder.setFieldVisible(this.visible, this.name);
} else {
final Text textInput = new Text(builder.formParent, (this.isPassword)
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
: SWT.LEFT | SWT.BORDER);
final Text textInput = (this.isNumber)
? builder.widgetFactory.numberInput(builder.formParent, null)
: builder.widgetFactory.textInput(builder.formParent, this.isPassword);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1);
gridData.heightHint = 15;
textInput.setLayoutData(gridData);
if (this.value != null) {
textInput.setText(this.value);

View file

@ -0,0 +1,62 @@
/*
* 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.form;
import java.util.Collection;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gui.widget.ThresholdList;
public class ThresholdListBuilder extends FieldBuilder<Collection<Threshold>> {
protected ThresholdListBuilder(
final String name,
final String label,
final Collection<Threshold> value) {
super(name, label, value);
}
@Override
void build(final FormBuilder builder) {
final Label lab = builder.labelLocalized(builder.formParent, this.label, this.spanLabel);
if (builder.readonly || this.readonly) {
} else {
final ThresholdList thresholdList = builder.widgetFactory.thresholdList(
builder.formParent,
this.value);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1);
thresholdList.setLayoutData(gridData);
builder.form.putField(this.name, lab, thresholdList);
builder.setFieldVisible(this.visible, this.name);
}
}
public static final String thresholdsToFormURLEncodedStringValue(final Collection<Threshold> thresholds) {
if (thresholds == null || thresholds.isEmpty()) {
return null;
}
return StringUtils.join(thresholds.stream()
.map(t -> String.valueOf(t.getValue()) + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + t.getColor())
.collect(Collectors.toList()),
Constants.LIST_SEPARATOR);
}
}

View file

@ -155,7 +155,7 @@ public interface PageContext {
* the specified page event type.
*
* @param event the concrete PageEvent instance */
<T extends PageEvent> void publishPageEvent(T event);
<T extends PageEvent> void firePageEvent(T event);
Action createAction(ActionDefinition actionDefinition);

View file

@ -72,7 +72,7 @@ public final class Action implements Runnable {
try {
final Action executedAction = this.exec.apply(this);
this.pageContext.publishPageEvent(new ActionEvent(executedAction, false));
this.pageContext.firePageEvent(new ActionEvent(executedAction, false));
} catch (final PageMessageException pme) {
Action.this.pageContext.publishPageMessage(pme);
@ -183,7 +183,7 @@ public final class Action implements Runnable {
}
public PageContext publish() {
this.pageContext.publishPageEvent(new ActionPublishEvent(this));
this.pageContext.firePageEvent(new ActionPublishEvent(this));
return this.originalPageContext;
}
@ -233,7 +233,7 @@ public final class Action implements Runnable {
if (action.getEntityKey() == null) {
final PageContext pageContext = action.pageContext();
final Action activityHomeAction = pageContext.createAction(action.definition.activityAlias);
action.pageContext.publishPageEvent(new ActionEvent(activityHomeAction, false));
action.pageContext.firePageEvent(new ActionEvent(activityHomeAction, false));
return activityHomeAction;
}

View file

@ -219,7 +219,7 @@ public class PageContextImpl implements PageContext {
@Override
@SuppressWarnings("unchecked")
public <T extends PageEvent> void publishPageEvent(final T event) {
public <T extends PageEvent> void firePageEvent(final T event) {
final Class<? extends PageEvent> typeClass = event.getClass();
final List<PageEventListener<T>> listeners = new ArrayList<>();
ComposerService.traversePageTree(

View file

@ -52,6 +52,7 @@ public abstract class RestCall<T> {
GET_DEPENDENCIES,
NEW,
SAVE,
DELETE,
ACTIVATION_ACTIVATE,
ACTIVATION_DEACTIVATE
}

View file

@ -0,0 +1,40 @@
/*
* 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.exam;
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.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class DeleteIndicator extends RestCall<EntityProcessingReport> {
protected DeleteIndicator() {
super(new TypeKey<>(
CallType.DELETE,
EntityType.INDICATOR,
new TypeReference<EntityProcessingReport>() {
}),
HttpMethod.DELETE,
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_INDICATOR_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT);
}
}

View file

@ -229,7 +229,7 @@ public class TableFilter<ROW extends Entity> {
@Override
FilterComponent build(final Composite parent) {
this.textInput = new Text(parent, SWT.LEFT | SWT.BORDER);
this.textInput = TableFilter.this.entityTable.widgetFactory.textInput(parent, false);
this.textInput.setLayoutData(this.rowData);
return this;
}
@ -321,7 +321,7 @@ public class TableFilter<ROW extends Entity> {
.withMonthOfYear(this.selector.getMonth())
.withDayOfMonth(this.selector.getDay());
return date.toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
return date.toString(Constants.STANDARD_DATE_TIME_FORMATTER);
} else {
return null;
}

View file

@ -44,6 +44,7 @@ public class ColorSelection extends Composite implements Selection {
gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
gridLayout.horizontalSpacing = 0;
setLayout(gridLayout);
this.colorDialog = new ColorDialog(this.getShell(), SWT.NONE);

View file

@ -47,7 +47,9 @@ public class MultiSelectionCombo extends Composite implements Selection {
private final List<Tuple<Control>> selectionControls = new ArrayList<>();
private final List<Tuple<String>> selectedValues = new ArrayList<>();
private final Map<String, String> mapping = new HashMap<>();
//private final List<Tuple<String>> mapping = new ArrayList<>();
private final GridData comboCell;
private final GridData actionCell;
MultiSelectionCombo(final Composite parent, final WidgetFactory widgetFactory) {
super(parent, SWT.NONE);
@ -57,22 +59,23 @@ public class MultiSelectionCombo extends Composite implements Selection {
gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
gridLayout.horizontalSpacing = 0;
setLayout(gridLayout);
this.addListener(SWT.Resize, this::adaptColumnWidth);
this.combo = new Combo(this, SWT.NONE);
final GridData comboCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
this.combo.setLayoutData(comboCell);
this.comboCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
this.combo.setLayoutData(this.comboCell);
final Label imageButton = widgetFactory.imageButton(
ImageIcon.ADD_BOX,
this,
new LocTextKey("Add"),
this::addComboSelection);
final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
actionCell.widthHint = ACTION_COLUMN_WIDTH;
imageButton.setLayoutData(actionCell);
this.actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
this.actionCell.widthHint = ACTION_COLUMN_WIDTH;
imageButton.setLayoutData(this.actionCell);
}
@Override
@ -205,8 +208,7 @@ public class MultiSelectionCombo extends Composite implements Selection {
private void adaptColumnWidth(final Event event) {
try {
final int currentTableWidth = this.getClientArea().width;
final GridData comboCell = (GridData) this.combo.getLayoutData();
comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH;
this.comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH;
this.layout();
} catch (final Exception e) {
log.warn("Failed to adaptColumnWidth: ", e);

View file

@ -1,6 +1,6 @@
/*
* 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/.
@ -8,6 +8,202 @@
package ch.ethz.seb.sebserver.gui.widget;
public class ThresholdList {
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
public class ThresholdList extends Composite {
private static final Logger log = LoggerFactory.getLogger(ThresholdList.class);
private static final long serialVersionUID = -2305091471607040280L;
private static final int ACTION_COLUMN_WIDTH = 20;
private static final LocTextKey valueTextKey = new LocTextKey("sebserver.exam.indicator.thresholds.list.value");
private static final LocTextKey colorTextKey = new LocTextKey("sebserver.exam.indicator.thresholds.list.color");
private static final LocTextKey addTextKey = new LocTextKey("sebserver.exam.indicator.thresholds.list.add");
private static final LocTextKey removeTextKey = new LocTextKey("sebserver.exam.indicator.thresholds.list.remove");
private final WidgetFactory widgetFactory;
private final List<Entry> thresholds = new ArrayList<>();
private final GridData valueCell;
private final GridData colorCell;
private final GridData actionCell;
ThresholdList(final Composite parent, final WidgetFactory widgetFactory) {
super(parent, SWT.NONE);
this.widgetFactory = widgetFactory;
final GridLayout gridLayout = new GridLayout(3, false);
gridLayout.verticalSpacing = 1;
gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
gridLayout.horizontalSpacing = 0;
setLayout(gridLayout);
this.addListener(SWT.Resize, this::adaptColumnWidth);
final Label valueTitle = widgetFactory.labelLocalized(this, CustomVariant.TITLE_LABEL, valueTextKey);
this.valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
valueTitle.setLayoutData(this.valueCell);
final Label colorTitle = widgetFactory.labelLocalized(this, CustomVariant.TITLE_LABEL, colorTextKey);
this.colorCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
colorTitle.setLayoutData(this.colorCell);
final Label imageButton = widgetFactory.imageButton(
ImageIcon.ADD_BOX,
this,
addTextKey,
this::addThreshold);
this.actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
imageButton.setLayoutData(this.actionCell);
}
public void setThresholds(final Collection<Threshold> thresholds) {
clearList();
if (thresholds != null) {
thresholds
.stream()
.forEach(this::addThreshold);
}
}
public Collection<Threshold> getThresholds() {
removeInvalidListEntries();
return this.thresholds
.stream()
.map(entry -> new Threshold(entry.getValue(), entry.getColor()))
.collect(Collectors.toList());
}
private void removeInvalidListEntries() {
this.thresholds
.stream()
.filter(entry -> entry.getValue() == null || StringUtils.isBlank(entry.getColor()))
.collect(Collectors.toList())
.stream()
.forEach(entry -> removeThreshold(entry));
}
private void clearList() {
this.thresholds.stream()
.forEach(e -> e.dispose());
this.thresholds.clear();
}
private void addThreshold(final Event event) {
addThreshold((Threshold) null);
}
private void addThreshold(final Threshold threshold) {
final Text valueInput = this.widgetFactory.numberInput(this, s -> Double.parseDouble(s));
final GridData valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
valueInput.setLayoutData(valueCell);
final Selection selector = this.widgetFactory.selectionLocalized(Type.COLOR, this, null);
final GridData selectorCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
selectorCell.horizontalIndent = 2;
selector.adaptToControl().setLayoutData(selectorCell);
final Label imageButton = this.widgetFactory.imageButton(
ImageIcon.REMOVE_BOX,
this,
removeTextKey,
null);
final GridData actionCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
imageButton.setLayoutData(actionCell);
if (threshold != null) {
if (threshold.value != null) {
valueInput.setText(threshold.value.toString());
}
if (threshold.color != null) {
selector.select(threshold.color);
}
}
final Entry entry = new Entry(valueInput, selector, imageButton);
this.thresholds.add(entry);
this.getParent().layout();
}
private void removeThreshold(final Entry entry) {
if (this.thresholds.remove(entry)) {
entry.dispose();
}
this.getParent().layout();
}
private void adaptColumnWidth(final Event event) {
try {
// TODO
final int currentTableWidth = this.getClientArea().width;
final int dynWidth = currentTableWidth - ACTION_COLUMN_WIDTH;
final int colWidth = dynWidth / 2;
this.valueCell.widthHint = colWidth;
this.colorCell.widthHint = colWidth;
this.layout();
} catch (final Exception e) {
log.warn("Failed to adaptColumnWidth: ", e);
}
}
private final class Entry {
final Text valueInput;
final Selection colorSelector;
final Label removeButton;
Entry(final Text valueInput, final Selection colorSelector, final Label removeButton) {
super();
this.valueInput = valueInput;
this.colorSelector = colorSelector;
this.removeButton = removeButton;
removeButton.addListener(SWT.MouseDown, event -> removeThreshold(this));
}
void dispose() {
this.valueInput.dispose();
this.colorSelector.adaptToControl().dispose();
this.removeButton.dispose();
}
Double getValue() {
if (this.valueInput == null || StringUtils.isBlank(this.valueInput.getText())) {
return null;
}
return Double.parseDouble(this.valueInput.getText());
}
String getColor() {
if (this.colorSelector == null) {
return null;
}
return this.colorSelector.getSelectionValue();
}
}
}

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.widget;
import static ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService.*;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
@ -30,6 +31,7 @@ import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.slf4j.Logger;
@ -39,6 +41,7 @@ import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
@ -242,6 +245,35 @@ public class WidgetFactory {
return labelLocalized;
}
public Text textInput(final Composite content) {
return textInput(content, false);
}
public Text passwordInput(final Composite content) {
return textInput(content, true);
}
public Text textInput(final Composite content, final boolean password) {
return new Text(content, (password)
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
: SWT.LEFT | SWT.BORDER);
}
public Text numberInput(final Composite content, final Consumer<String> numberCheck) {
final Text numberInput = new Text(content, SWT.RIGHT | SWT.BORDER);
if (numberCheck != null) {
numberInput.addListener(SWT.Verify, event -> {
final String value = event.text;
try {
numberCheck.accept(value);
} catch (final Exception e) {
event.doit = false;
}
});
}
return numberInput;
}
public Tree treeLocalized(final Composite parent, final int style) {
final Tree tree = new Tree(parent, style);
this.injectI18n(tree);
@ -343,6 +375,14 @@ public class WidgetFactory {
return selection;
}
public ThresholdList thresholdList(final Composite parent, final Collection<Threshold> values) {
final ThresholdList thresholdList = new ThresholdList(parent, this);
if (values != null) {
thresholdList.setThresholds(values);
}
return thresholdList;
}
public ImageUpload imageUploadLocalized(
final Composite parent,
final LocTextKey locTextKey,

View file

@ -21,17 +21,26 @@ import org.apache.ibatis.type.JdbcType;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.Constants;
/** Joda DateTime resolver for MyBatis TIMESTAMP to DateTime conversion and vis versa. This is used to convert MyBatis
* TIMESTAMP type to Joda-Time's DateTime
*
* NOTE: The TIMESTAMP is always stored and read in UTC time-zone. */
public class JodaTimeTypeResolver extends BaseTypeHandler<DateTime> {
static final DateTimeFormatter DATE_TIME_PATTERN_UTC_NO_MILLIS = DateTimeFormat
.forPattern("yyyy-MM-dd HH:mm:ss")
.withZoneUTC();
/** Date-Time formatter with milliseconds using UTC time-zone. Pattern is yyyy-MM-dd HH:mm:ss.S */
static final DateTimeFormatter DATE_TIME_PATTERN_UTC_MILLIS = DateTimeFormat
.forPattern("yyyy-MM-dd HH:mm:ss.S")
.withZoneUTC();
private static final Logger log = LoggerFactory.getLogger(JodaTimeTypeResolver.class);
@Override
@ -68,7 +77,7 @@ public class JodaTimeTypeResolver extends BaseTypeHandler<DateTime> {
return getDateTime(supplier.get());
} catch (final Exception e) {
log.error("while trying to parse LocalDateTime; value: " + dateFormattedString + " format: "
+ Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS, e);
+ DATE_TIME_PATTERN_UTC_NO_MILLIS, e);
throw new RuntimeException("Failed to parse date-time from SQL string: " + dateFormattedString, e);
}
}
@ -87,7 +96,7 @@ public class JodaTimeTypeResolver extends BaseTypeHandler<DateTime> {
// NOTE: This create a DateTime in UTC time.zone with no time-zone-offset.
final LocalDateTime localDateTime = LocalDateTime.parse(
dateFormattedString,
Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
DATE_TIME_PATTERN_UTC_NO_MILLIS);
final DateTime dateTime = localDateTime.toDateTime(DateTimeZone.UTC);
return dateTime;

View file

@ -8,7 +8,6 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@ -21,7 +20,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.JodaTimeTypeResolver;
import ch.ethz.seb.sebserver.gbl.util.Utils;
/** A Map containing various filter criteria from a certain API request.
* This is used as a data object that can be used to collect API request parameter
@ -76,27 +75,15 @@ public class FilterMap extends POSTMapper {
}
public DateTime getQuizFromTime() {
final String value = getString(QuizData.FILTER_ATTR_START_TIME);
if (StringUtils.isBlank(value)) {
return null;
}
return JodaTimeTypeResolver.getDateTime(value);
return Utils.toDateTime(getString(QuizData.FILTER_ATTR_START_TIME));
}
public DateTime getExamFromTime() {
final String value = getString(Exam.FILTER_ATTR_FROM);
if (StringUtils.isBlank(value)) {
return null;
}
return JodaTimeTypeResolver.getDateTime(value);
return Utils.toDateTime(getString(Exam.FILTER_ATTR_FROM));
}
public DateTime getSebClientConfigFromTime() {
final String value = getString(SebClientConfig.FILTER_ATTR_FROM);
if (StringUtils.isBlank(value)) {
return null;
}
return JodaTimeTypeResolver.getDateTime(value);
return Utils.toDateTime(getString(SebClientConfig.FILTER_ATTR_FROM));
}
public Long getLmsSetupId() {

View file

@ -187,7 +187,7 @@ public class ExamDAOImpl implements ExamDAO {
BooleanUtils.toIntegerObject(exam.active));
this.examRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.examRecordMapper.selectByPrimaryKey(exam.id);
return this.examRecordMapper.selectByPrimaryKey(examRecord.getId());
}
final ExamRecord examRecord = new ExamRecord(

View file

@ -12,6 +12,7 @@ import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
@ -23,6 +24,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
@ -217,6 +219,10 @@ public class IndicatorDAOImpl implements IndicatorDAO {
@Override
@Transactional(readOnly = true)
public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) {
return Collections.emptySet();
}
final Set<EntityKey> examEntities = (bulkAction.sourceType == EntityType.EXAM)
? bulkAction.sources
: bulkAction.extractKeys(EntityType.EXAM);

View file

@ -57,25 +57,25 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
this.mockups = new ArrayList<>();
this.mockups.add(new QuizData(
"quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1", "Demo Quit Mockup",
"2020-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
"2020-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2", "Demo Quit Mockup",
"2020-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
"2020-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz3", institutionId, lmsSetupId, lmsType, "Demo Quiz 3", "Demo Quit Mockup",
"2018-07-30T09:00:00", "2018-08-01T00:00:00", "http://lms.mockup.com/api/"));
"2018-07-30T09:00:00Z", "2018-08-01T00:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz4", institutionId, lmsSetupId, lmsType, "Demo Quiz 4", "Demo Quit Mockup",
"2018-01-01T00:00:00", "2019-01-01T00:00:00", "http://lms.mockup.com/api/"));
"2018-01-01T00:00:00Z", "2019-01-01T00:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz5", institutionId, lmsSetupId, lmsType, "Demo Quiz 5", "Demo Quit Mockup",
"2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
"2018-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz6", institutionId, lmsSetupId, lmsType, "Demo Quiz 6", "Demo Quit Mockup",
"2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
"2018-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz7", institutionId, lmsSetupId, lmsType, "Demo Quiz 7", "Demo Quit Mockup",
"2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
"2018-01-01T09:00:00Z", "2021-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
}
@Override

View file

@ -109,34 +109,6 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
.collect(Collectors.toList()));
}
private UserInfo checkPasswordChange(final UserInfo info, final PasswordChange passwordChange) {
final SEBServerUser currentUser = this.userDAO.sebServerUserByUsername(this.authorization
.getUserService()
.getCurrentUser().getUsername())
.getOrThrow();
if (!this.userPasswordEncoder.matches(passwordChange.getPassword(), currentUser.getPassword())) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
"passwordChange",
PasswordChange.ATTR_NAME_PASSWORD,
"user:oldPassword:password.wrong")));
}
if (!passwordChange.newPasswordMatch()) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
"passwordChange",
PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD,
"user:retypedNewPassword:password.mismatch")));
}
return info;
}
@Override
protected Result<UserMod> validForCreate(final UserMod userInfo) {
return super.validForCreate(userInfo)
@ -219,4 +191,31 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
});
}
private UserInfo checkPasswordChange(final UserInfo info, final PasswordChange passwordChange) {
final SEBServerUser currentUser = this.userDAO.sebServerUserByUsername(this.authorization
.getUserService()
.getCurrentUser().getUsername())
.getOrThrow();
if (!this.userPasswordEncoder.matches(passwordChange.getPassword(), currentUser.getPassword())) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
"passwordChange",
PasswordChange.ATTR_NAME_PASSWORD,
"user:oldPassword:password.wrong")));
}
if (!passwordChange.newPasswordMatch()) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
"passwordChange",
PasswordChange.ATTR_NAME_CONFIRM_NEW_PASSWORD,
"user:retypedNewPassword:password.mismatch")));
}
return info;
}
}

View file

@ -169,7 +169,7 @@ sebserver.lmssetup.action.list.view=View LMS Setup
sebserver.lmssetup.action.list.modify=Edit LMS Setup
sebserver.lmssetup.action.modify=Edit
sebserver.lmssetup.action.test=Test Setup
sebserver.lmssetup.action.test.ok=Successfully connect to the LMSs course API
sebserver.lmssetup.action.test.ok=Successfully connected to the course API
sebserver.lmssetup.action.test.tokenRequestError=The API access was denied: {0}
sebserver.lmssetup.action.test.quizRequestError=Unable to request courses or quizzes from the course API of the LMS. {0}
sebserver.lmssetup.action.test.missingParameter=There is one or more missing connection parameter.<br/>Please check the connection parameter for this LMS Setup
@ -227,15 +227,15 @@ sebserver.exam.list.empty=No Exams has been found. Please adapt the filter or im
sebserver.exam.action.list=Exam
sebserver.exam.action.list.view=View Exam
sebserver.exam.action.list.modify=Edit Exam
sebserver.exam.action.modify=Edit
sebserver.exam.action.modify=Edit Exam
sebserver.exam.action.import=Import From Quizzes
sebserver.exam.action.save=Save
sebserver.exam.action.activate=Activate
sebserver.exam.action.deactivate=Deactivate
sebserver.exam.action.save=Save Exam
sebserver.exam.action.activate=Activate Exam
sebserver.exam.action.deactivate=Deactivate Exam
sebserver.exam.info.pleaseSelect=Please Select an Exam first
sebserver.exam.form.title.import=Create From Quiz
sebserver.exam.form.title.import=New Exam
sebserver.exam.form.title=Exam
sebserver.exam.form.lmssetup=LMS Setup
sebserver.exam.form.quizid=Quiz Identifier
@ -266,7 +266,8 @@ sebserver.exam.indicator.type.ERROR_COUNT=Error Count
sebserver.exam.indicator.info.pleaseSelect=Please Select an Indicator first
sebserver.exam.indicator.action.list.new=New Indicator
sebserver.exam.indicator.action.list.modify=Modify Indicator
sebserver.exam.indicator.action.list.modify=Edit
sebserver.exam.indicator.action.list.delete=Delete
sebserver.exam.indicator.action.save=Save
sebserver.exam.indicator.form.title=Indicator
@ -274,6 +275,11 @@ sebserver.exam.indicator.form.title.new=New Indicator
sebserver.exam.indicator.form.exam=Exam
sebserver.exam.indicator.form.name=Name
sebserver.exam.indicator.form.type=Type
sebserver.exam.indicator.form.color=Color
sebserver.exam.indicator.form.color=Default Color
sebserver.exam.indicator.form.thresholds=Thresholds
sebserver.exam.indicator.thresholds.list.title=Thresholds
sebserver.exam.indicator.thresholds.list.value=Value
sebserver.exam.indicator.thresholds.list.color=Color
sebserver.exam.indicator.thresholds.list.add=New Threshold
sebserver.exam.indicator.thresholds.list.remove=Delete Threshold

View file

@ -127,7 +127,7 @@ CREATE TABLE IF NOT EXISTS `indicator` (
`exam_id` BIGINT UNSIGNED NOT NULL,
`type` VARCHAR(45) NOT NULL,
`name` VARCHAR(45) NOT NULL,
`color` VARCHAR(45) NOT NULL,
`color` VARCHAR(45) NULL,
INDEX `indicator_exam_idx` (`exam_id` ASC),
PRIMARY KEY (`id`),
CONSTRAINT `exam_ref`
@ -357,7 +357,7 @@ CREATE TABLE IF NOT EXISTS `threshold` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`indicator_id` BIGINT UNSIGNED NOT NULL,
`value` DECIMAL(10,4) NOT NULL,
`color` VARCHAR(45) NOT NULL,
`color` VARCHAR(45) NULL,
PRIMARY KEY (`id`),
INDEX `indicator_threshold_id_idx` (`indicator_id` ASC),
CONSTRAINT `indicator_threshold_id`

View file

@ -140,7 +140,7 @@ CREATE TABLE IF NOT EXISTS `indicator` (
`exam_id` BIGINT UNSIGNED NOT NULL,
`type` VARCHAR(45) NOT NULL,
`name` VARCHAR(45) NOT NULL,
`color` VARCHAR(45) NOT NULL,
`color` VARCHAR(45) NULL,
INDEX `indicator_exam_idx` (`exam_id` ASC),
PRIMARY KEY (`id`),
CONSTRAINT `exam_ref`
@ -381,7 +381,7 @@ CREATE TABLE IF NOT EXISTS `threshold` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`indicator_id` BIGINT UNSIGNED NOT NULL,
`value` DECIMAL(10,4) NOT NULL,
`color` VARCHAR(45) NOT NULL,
`color` VARCHAR(45) NULL,
PRIMARY KEY (`id`),
INDEX `indicator_threshold_id_idx` (`indicator_id` ASC),
CONSTRAINT `indicator_threshold_id`

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.batis;
package ch.ethz.seb.sebserver.webservice.datalayer.batis;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
@ -23,9 +23,6 @@ import org.joda.time.DateTimeZone;
import org.junit.Test;
import org.mockito.Mockito;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.JodaTimeTypeResolver;
public class JodaTimeTypeResolverTest {
@Test
@ -53,7 +50,7 @@ public class JodaTimeTypeResolverTest {
final int columnIndex = 0;
final DateTime pointInTime = new DateTime(0, DateTimeZone.UTC);
final String pointInTimeString = pointInTime.toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
final String pointInTimeString = pointInTime.toString(JodaTimeTypeResolver.DATE_TIME_PATTERN_UTC_NO_MILLIS);
assertEquals("1970-01-01 00:00:00", pointInTimeString);
final JodaTimeTypeResolver jodaTimeTypeResolver = new JodaTimeTypeResolver();
@ -64,12 +61,12 @@ public class JodaTimeTypeResolverTest {
DateTime nullableResult = jodaTimeTypeResolver.getNullableResult(resultSetMock, columnName);
assertNotNull(nullableResult);
assertEquals(pointInTimeString, nullableResult.toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS));
assertEquals(pointInTimeString, nullableResult.toString(JodaTimeTypeResolver.DATE_TIME_PATTERN_UTC_NO_MILLIS));
assertEquals(pointInTime, nullableResult);
nullableResult = jodaTimeTypeResolver.getNullableResult(resultSetMock, columnIndex);
assertNotNull(nullableResult);
assertEquals(pointInTimeString, nullableResult.toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS));
assertEquals(pointInTimeString, nullableResult.toString(JodaTimeTypeResolver.DATE_TIME_PATTERN_UTC_NO_MILLIS));
assertEquals(pointInTime, nullableResult);
}
@ -78,7 +75,7 @@ public class JodaTimeTypeResolverTest {
final String columnName = "timestamp";
final DateTime pointInTime = new DateTime(0, DateTimeZone.UTC);
final String pointInTimeString = pointInTime.toString(Constants.DATE_TIME_PATTERN_UTC_MILLIS);
final String pointInTimeString = pointInTime.toString(JodaTimeTypeResolver.DATE_TIME_PATTERN_UTC_MILLIS);
assertEquals("1970-01-01 00:00:00.0", pointInTimeString);
final JodaTimeTypeResolver jodaTimeTypeResolver = new JodaTimeTypeResolver();
@ -88,7 +85,8 @@ public class JodaTimeTypeResolverTest {
final DateTime nullableResult = jodaTimeTypeResolver.getNullableResult(resultSetMock, columnName);
assertNotNull(nullableResult);
assertEquals("1970-01-01 00:00:00", nullableResult.toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS));
assertEquals("1970-01-01 00:00:00",
nullableResult.toString(JodaTimeTypeResolver.DATE_TIME_PATTERN_UTC_NO_MILLIS));
assertEquals(pointInTime, nullableResult);
}

View file

@ -894,7 +894,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
@Test
public void deactivateUserAccount() throws Exception {
final String timeNow = DateTime.now(DateTimeZone.UTC).toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
final String timeNow = DateTime.now(DateTimeZone.UTC).toString(Constants.STANDARD_DATE_TIME_FORMATTER);
// only a SEB Administrator or an Institutional administrator should be able to deactivate a user-account
final String examAdminToken = getExamAdmin1();
this.mockMvc.perform(post(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/user4/inactive")
@ -957,7 +957,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
@Test
public void activateUserAccount() throws Exception {
final String timeNow = DateTime.now(DateTimeZone.UTC).toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
final String timeNow = DateTime.now(DateTimeZone.UTC).toString(Constants.STANDARD_DATE_TIME_FORMATTER);
// only a SEB Administrator or an Institutional administrator should be able to deactivate a user-account
final String examAdminToken = getExamAdmin1();
this.mockMvc.perform(post(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/user6/active")

View file

@ -72,12 +72,12 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
@Test
public void getAllAsSEBAdminInTimeRange() throws Exception {
final DateTime zeroDate = DateTime.parse("1970-01-01 00:00:00", Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
final DateTime zeroDate = DateTime.parse("1970-01-01T00:00:00Z", Constants.STANDARD_DATE_TIME_FORMATTER);
assertEquals("0", String.valueOf(zeroDate.getMillis()));
final String sec2 = zeroDate.plus(1000).toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
final String sec4 = zeroDate.plus(4000).toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
final String sec5 = zeroDate.plus(5000).toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
final String sec6 = zeroDate.plus(6000).toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS);
final String sec2 = zeroDate.plus(1000).toString(Constants.STANDARD_DATE_TIME_FORMATTER);
final String sec4 = zeroDate.plus(4000).toString(Constants.STANDARD_DATE_TIME_FORMATTER);
final String sec5 = zeroDate.plus(5000).toString(Constants.STANDARD_DATE_TIME_FORMATTER);
final String sec6 = zeroDate.plus(6000).toString(Constants.STANDARD_DATE_TIME_FORMATTER);
final String token = getSebAdminAccess();
Page<UserActivityLog> logs = this.jsonMapper.readValue(