SEBSERV-30 IndicatorList
This commit is contained in:
		
							parent
							
								
									100bf77bd4
								
							
						
					
					
						commit
						7ccacfcb73
					
				
					 40 changed files with 606 additions and 191 deletions
				
			
		| 
						 | 
				
			
			@ -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();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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());
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,10 +89,12 @@ public class FormHandle<T extends Entity> {
 | 
			
		|||
    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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,6 +52,7 @@ public abstract class RestCall<T> {
 | 
			
		|||
        GET_DEPENDENCIES,
 | 
			
		||||
        NEW,
 | 
			
		||||
        SAVE,
 | 
			
		||||
        DELETE,
 | 
			
		||||
        ACTIVATION_ACTIVATE,
 | 
			
		||||
        ACTIVATION_DEACTIVATE
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue