diff --git a/pom.xml b/pom.xml
index 1424dbda..ccd1c323 100644
--- a/pom.xml
+++ b/pom.xml
@@ -289,6 +289,11 @@
org.apache.commons
commons-lang3
+
+ org.cryptonode.jncryptor
+ jncryptor
+ 1.2.0
+
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java
index 81f56ef8..1430ae4c 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java
@@ -28,15 +28,23 @@ public final class Constants {
public static final String FORM_URL_ENCODED_SEPARATOR = "&";
public static final String FORM_URL_ENCODED_NAME_VALUE_SEPARATOR = "=";
+ public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
public static final String DEFAULT_DISPLAY_DATE_FORMAT = "MM-dd-yyy HH:mm";
public static final String TIME_ZONE_OFFSET_TAIL_FORMAT = "|ZZ";
+ public static final DateTimeFormatter STANDARD_DATE_TIME_FORMATTER = DateTimeFormat
+ .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();
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
index e00cc9cd..132762ef 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
@@ -20,10 +20,10 @@ public final class API {
public static final String PARAM_INSTITUTION_ID = "institutionId";
public static final String PARAM_MODEL_ID = "modelId";
public static final String PARAM_MODEL_ID_LIST = "modelIds";
+ public static final String PARAM_PARENT_MODEL_ID = "parentModelId";
public static final String PARAM_ENTITY_TYPE = "entityType";
public static final String PARAM_BULK_ACTION_TYPE = "bulkActionType";
public static final String PARAM_LMS_SETUP_ID = "lmsSetupId";
- public static final String PARAM_EXAM_ID = "examId";
public static final String INSTITUTION_VAR_PATH_SEGMENT = "/{" + PARAM_INSTITUTION_ID + "}";
public static final String MODEL_ID_VAR_PATH_SEGMENT = "/{" + PARAM_MODEL_ID + "}";
@@ -54,7 +54,7 @@ public final class API {
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
- public static final String EXAM_INDICATOR_ENDPOINT = "/exam/indicator";
+ public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
public static final String USER_ACTIVITY_LOG_ENDPOINT = "/useractivity";
@@ -75,4 +75,20 @@ public final class API {
public static final String PATH_VAR_ACTIVE = MODEL_ID_VAR_PATH_SEGMENT + ACTIVE_PATH_SEGMENT;
public static final String PATH_VAR_INACTIVE = MODEL_ID_VAR_PATH_SEGMENT + INACTIVE_PATH_SEGMENT;
+ // *************************
+ // ** Exam API
+ // *************************
+
+ public static final String EXAM_API_PARAM_EXAM_ID = "examId";
+
+ public static final String EXAM_API_SEB_CONNECTION_TOKEN = "seb-connection-token";
+
+ public static final String EXAM_API_HANDSHAKE_ENDPOINT = "/handshake";
+
+ public static final String EXAM_API_CONFIGURATION_REQUEST_ENDPOINT = "/examconfig";
+
+ public static final String EXAM_API_PING_ENDPOINT = "/sebping";
+
+ public static final String EXAM_API_EVENT_ENDPOINT = "/sebevent";
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java
index 006ff3cb..3e401443 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java
@@ -48,12 +48,12 @@ public final class Indicator implements GrantEntity {
public final Long examId;
@JsonProperty(INDICATOR.ATTR_NAME)
- @NotNull
+ @NotNull(message = "indicator:name:notNull")
@Size(min = 3, max = 255, message = "indicator:name:size:{min}:{max}:${validatedValue}")
public final String name;
@JsonProperty(INDICATOR.ATTR_TYPE)
- @NotNull
+ @NotNull(message = "indicator:type:notNull")
public final IndicatorType type;
@JsonProperty(INDICATOR.ATTR_COLOR)
@@ -62,12 +62,14 @@ public final class Indicator implements GrantEntity {
@JsonProperty(THRESHOLD.REFERENCE_NAME)
public final List thresholds;
+ public final String examOwner;
+
@JsonCreator
public Indicator(
@JsonProperty(INDICATOR.ATTR_ID) final Long id,
@JsonProperty(EXAM.ATTR_INSTITUTION_ID) final Long institutionId,
- @JsonProperty(EXAM.ATTR_OWNER) final String owner,
@JsonProperty(INDICATOR.ATTR_EXAM_ID) final Long examId,
+ @JsonProperty(EXAM.ATTR_OWNER) final String examOwner,
@JsonProperty(INDICATOR.ATTR_NAME) final String name,
@JsonProperty(INDICATOR.ATTR_TYPE) final IndicatorType type,
@JsonProperty(INDICATOR.ATTR_COLOR) final String defaultColor,
@@ -76,6 +78,7 @@ public final class Indicator implements GrantEntity {
this.id = id;
this.institutionId = institutionId;
this.examId = examId;
+ this.examOwner = examOwner;
this.name = name;
this.type = type;
this.defaultColor = defaultColor;
@@ -86,6 +89,7 @@ public final class Indicator implements GrantEntity {
this.id = null;
this.institutionId = exam.institutionId;
this.examId = exam.id;
+ this.examOwner = exam.owner;
this.name = postParams.getString(Domain.INDICATOR.ATTR_NAME);
this.type = postParams.getEnum(Domain.INDICATOR.ATTR_TYPE, IndicatorType.class);
this.defaultColor = postParams.getString(Domain.INDICATOR.ATTR_COLOR);
@@ -119,7 +123,7 @@ public final class Indicator implements GrantEntity {
@Override
public String getOwnerId() {
- return null;
+ return this.examOwner;
}
public Long getExamId() {
@@ -145,14 +149,18 @@ public final class Indicator implements GrantEntity {
+ this.defaultColor + ", thresholds=" + this.thresholds + "]";
}
+ public static Indicator createNew(final Long institutionId, final Exam exam) {
+ return new Indicator(null, institutionId, exam.id, exam.owner, null, null, null, null);
+ }
+
public static final class Threshold {
- @JsonProperty(THRESHOLD.ATTR_ID)
- public final Long id;
-
- @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID)
- @NotNull
- public final Long indicatorId;
+// @JsonProperty(THRESHOLD.ATTR_ID)
+// public final Long id;
+//
+// @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID)
+// @NotNull
+// public final Long indicatorId;
@JsonProperty(THRESHOLD.ATTR_VALUE)
@NotNull
@@ -163,24 +171,24 @@ public final class Indicator implements GrantEntity {
@JsonCreator
public Threshold(
- @JsonProperty(THRESHOLD.ATTR_ID) final Long id,
- @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID) final Long indicatorId,
+// @JsonProperty(THRESHOLD.ATTR_ID) final Long id,
+// @JsonProperty(THRESHOLD.ATTR_INDICATOR_ID) final Long indicatorId,
@JsonProperty(THRESHOLD.ATTR_VALUE) final Double value,
@JsonProperty(THRESHOLD.ATTR_COLOR) final String color) {
- this.id = id;
- this.indicatorId = indicatorId;
+// this.id = id;
+// this.indicatorId = indicatorId;
this.value = value;
this.color = color;
}
- public Long getId() {
- return this.id;
- }
-
- public Long getIndicatorId() {
- return this.indicatorId;
- }
+// public Long getId() {
+// return this.id;
+// }
+//
+// public Long getIndicatorId() {
+// return this.indicatorId;
+// }
public Double getValue() {
return this.value;
@@ -190,12 +198,12 @@ public final class Indicator implements GrantEntity {
return this.color;
}
- @Override
- public String toString() {
- return "Threshold [id=" + this.id + ", indicatorId=" + this.indicatorId + ", value=" + this.value
- + ", color=" + this.color
- + "]";
- }
+// @Override
+// public String toString() {
+// return "Threshold [id=" + this.id + ", indicatorId=" + this.indicatorId + ", value=" + this.value
+// + ", color=" + this.color
+// + "]";
+// }
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java
index 2148b904..06e75a3d 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java
@@ -105,12 +105,16 @@ public final class QuizData implements GrantEntity {
this.lmsType = lmsType;
this.name = name;
this.description = description;
- this.startTime = LocalDateTime
- .parse(startTime, Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS)
- .toDateTime(DateTimeZone.UTC);
- this.endTime = LocalDateTime
- .parse(endTime, Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS)
- .toDateTime(DateTimeZone.UTC);
+ this.startTime = (startTime != null)
+ ? LocalDateTime
+ .parse(startTime, Constants.STANDARD_DATE_TIME_FORMATTER)
+ .toDateTime(DateTimeZone.UTC)
+ : null;
+ this.endTime = (endTime != null)
+ ? LocalDateTime
+ .parse(endTime, Constants.STANDARD_DATE_TIME_FORMATTER)
+ .toDateTime(DateTimeZone.UTC)
+ : null;
this.startURL = startURL;
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/PingResponse.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/PingResponse.java
new file mode 100644
index 00000000..0d2611a9
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/PingResponse.java
@@ -0,0 +1,13 @@
+/*
+ * 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.gbl.model.seb;
+
+public class PingResponse {
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/RunningExams.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/RunningExams.java
new file mode 100644
index 00000000..d19e6f1b
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/seb/RunningExams.java
@@ -0,0 +1,13 @@
+/*
+ * 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.gbl.model.seb;
+
+public class RunningExams {
+ // TODO
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java
index e5cf3f4f..db1e9f43 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java
@@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.content;
import java.util.function.BooleanSupplier;
import org.apache.commons.lang3.BooleanUtils;
+import org.apache.tomcat.util.buf.StringUtils;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,6 +37,7 @@ 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.PageContext.AttributeKeys;
+import ch.ethz.seb.sebserver.gui.service.page.PageUtils;
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;
@@ -43,7 +45,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
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;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
@@ -71,6 +72,8 @@ public class ExamForm implements TemplateComposer {
new LocTextKey("sebserver.exam.indicator.list.column.name");
private final static LocTextKey thresholdColumnKey =
new LocTextKey("sebserver.exam.indicator.list.column.thresholds");
+ private final static LocTextKey emptySelectionTextKey =
+ new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
protected ExamForm(
final PageFormService pageFormService,
@@ -124,11 +127,6 @@ public class ExamForm implements TemplateComposer {
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
final boolean writeGrant = userGrantCheck.w();
final boolean modifyGrant = userGrantCheck.m();
- final boolean institutionActive = restService.getBuilder(GetInstitution.class)
- .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.getInstitutionId()))
- .call()
- .map(inst -> inst.active)
- .getOr(false);
// The Exam form
final FormHandle formHandle = this.pageFormService.getBuilder(
@@ -140,7 +138,7 @@ public class ExamForm implements TemplateComposer {
.putStaticValue(
Domain.EXAM.ATTR_INSTITUTION_ID,
String.valueOf(exam.getInstitutionId()))
- .putStaticValueIf(isNew,
+ .putStaticValue(
Domain.EXAM.ATTR_OWNER,
user.uuid)
.putStaticValueIf(isNotNew,
@@ -192,11 +190,48 @@ public class ExamForm implements TemplateComposer {
"sebserver.exam.form.type",
String.valueOf(exam.type),
this.resourceService::examTypeResources))
+ .addField(FormBuilder.multiComboSelection(
+ Domain.EXAM.ATTR_SUPPORTER,
+ "sebserver.exam.form.supporter",
+ StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR),
+ this.resourceService::examSupporterResources)
+ .withCondition(isNotNew))
.buildFor(importFromQuizData
? restService.getRestCall(ImportAsExam.class)
: restService.getRestCall(SaveExam.class));
+ // propagate content actions to action-pane
+ formContext.clearEntityKeys()
+ .removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA)
+
+ .createAction(ActionDefinition.EXAM_MODIFY)
+ .withEntityKey(entityKey)
+ .publishIf(() -> modifyGrant && readonly)
+
+ .createAction(ActionDefinition.EXAM_SAVE)
+ .withExec(formHandle::processFormSave)
+ .publishIf(() -> !readonly && modifyGrant)
+
+ .createAction(ActionDefinition.EXAM_CANCEL_MODIFY)
+ .withEntityKey(entityKey)
+ .withAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA, String.valueOf(importFromQuizData))
+ .withExec(ExamForm::cancelModify)
+ .withConfirm("sebserver.overall.action.modify.cancel.confirm")
+ .publishIf(() -> !readonly)
+
+ .createAction(ActionDefinition.EXAM_DEACTIVATE)
+ .withEntityKey(entityKey)
+ .withExec(restService::activation)
+ .withConfirm(PageUtils.confirmDeactivation(exam, restService))
+ .publishIf(() -> writeGrant && readonly && exam.isActive())
+
+ .createAction(ActionDefinition.EXAM_ACTIVATE)
+ .withEntityKey(entityKey)
+ .withExec(restService::activation)
+ .publishIf(() -> writeGrant && readonly && !exam.isActive());
+
+ // additional data in read-only view
if (readonly) {
// List of Indicators
@@ -227,24 +262,25 @@ public class ExamForm implements TemplateComposer {
.compose(content);
+ formContext.clearEntityKeys()
+ .removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA)
+
+ .createAction(ActionDefinition.EXAM_INDICATOR_NEW)
+ .withParentEntityKey(entityKey)
+ .publishIf(() -> modifyGrant)
+
+ .createAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
+ .withSelect(indicatorTable::getSelection, Action::applySingleSelection, emptySelectionTextKey)
+ .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent())
+
+ .createAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
+ .withSelect(indicatorTable::getSelection, Action::applySingleSelection, emptySelectionTextKey)
+ .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent());
+
// TODO List of attached SEB Configurations
}
- // propagate content actions to action-pane
- formContext.clearEntityKeys()
- .removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA)
-
- .createAction(ActionDefinition.EXAM_SAVE)
- .withExec(formHandle::processFormSave)
- .publishIf(() -> !readonly && modifyGrant)
-
- .createAction(ActionDefinition.EXAM_CANCEL_MODIFY)
- .withEntityKey(entityKey)
- .withAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA, String.valueOf(importFromQuizData))
- .withExec(ExamForm::cancelModify)
- .withConfirm("sebserver.overall.action.modify.cancel.confirm")
- .publishIf(() -> !readonly);
}
private Result getExistingExam(final EntityKey entityKey, final RestService restService) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java
index 250ec71d..48236a6c 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java
@@ -10,11 +10,7 @@ package ch.ethz.seb.sebserver.gui.content;
import java.util.function.Function;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@@ -32,7 +28,6 @@ import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
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.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
@@ -136,9 +131,9 @@ public class ExamList implements TemplateComposer {
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
pageContext.clearEntityKeys()
- .createAction(ActionDefinition.TEST_ACTION)
- .withExec(this::testModalInput)
- .publish()
+// .createAction(ActionDefinition.TEST_ACTION)
+// .withExec(this::testModalInput)
+// .publish()
.createAction(ActionDefinition.EXAM_IMPORT)
.publishIf(userGrant::im)
@@ -172,29 +167,29 @@ public class ExamList implements TemplateComposer {
.getText("sebserver.exam.type." + exam.type.name());
}
- private Action testModalInput(final Action action) {
- final ModalInputDialog dialog = new ModalInputDialog<>(
- action.pageContext().getParent().getShell(),
- this.widgetFactory);
-
- dialog.open(
- "Test Input Dialog",
- action.pageContext(),
- value -> {
- System.out.println("********************** value: " + value);
- },
- pc -> {
- final Composite parent = pc.getParent();
- final Label label = new Label(parent, SWT.NONE);
- label.setText("Please Enter:");
- label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
- final Text text = new Text(parent, SWT.LEFT | SWT.BORDER);
- text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- return () -> text.getText();
- });
-
- return action;
- }
+// private Action testModalInput(final Action action) {
+// final ModalInputDialog dialog = new ModalInputDialog<>(
+// action.pageContext().getParent().getShell(),
+// this.widgetFactory);
+//
+// dialog.open(
+// "Test Input Dialog",
+// action.pageContext(),
+// value -> {
+// System.out.println("********************** value: " + value);
+// },
+// pc -> {
+// final Composite parent = pc.getParent();
+// final Label label = new Label(parent, SWT.NONE);
+// label.setText("Please Enter:");
+// label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+//
+// final Text text = new Text(parent, SWT.LEFT | SWT.BORDER);
+// text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+// return () -> text.getText();
+// });
+//
+// return action;
+// }
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/IndicatorForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/IndicatorForm.java
index 568920db..60b341c3 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/IndicatorForm.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/IndicatorForm.java
@@ -8,20 +8,47 @@
package ch.ethz.seb.sebserver.gui.content;
+import org.eclipse.swt.widgets.Composite;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
+import ch.ethz.seb.sebserver.gbl.api.API;
+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;
+import ch.ethz.seb.sebserver.gui.service.page.action.Action;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+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
@Component
@GuiProfile
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;
@@ -35,7 +62,97 @@ public class IndicatorForm implements TemplateComposer {
@Override
public void compose(final PageContext pageContext) {
- // TODO Auto-generated method stub
+ 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;
+ final boolean isReadonly = pageContext.isReadonly();
+
+ final Exam exam = restService
+ .getBuilder(GetExam.class)
+ .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
+ .call()
+ .get(pageContext::notifyError);
+
+ // get data or create new. Handle error if happen
+ final Indicator indicator = (isNew)
+ ? Indicator.createNew(exam.getInstitutionId(), exam)
+ : restService
+ .getBuilder(GetIndicator.class)
+ .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
+ .call()
+ .get(pageContext::notifyError);
+
+ if (indicator == null) {
+ log.error("Failed to get Indicator. "
+ + "Error was notified to the User. "
+ + "See previous logs for more infomation");
+ return;
+ }
+
+ // new PageContext with actual EntityKey
+ final PageContext formContext = pageContext.withEntityKey(indicator.getEntityKey());
+
+ // the default page layout
+ final LocTextKey titleKey = new LocTextKey(
+ (isNew)
+ ? "sebserver.exam.indicator.form.title.new"
+ : "sebserver.exam.indicator.form.title");
+ final Composite content = widgetFactory.defaultPageLayout(
+ formContext.getParent(),
+ titleKey);
+
+ final FormHandle formHandle = this.pageFormService.getBuilder(
+ formContext.copyOf(content), 4)
+ .readonly(isReadonly)
+ .putStaticValueIf(() -> !isNew,
+ Domain.INDICATOR.ATTR_ID,
+ indicator.getModelId())
+ .putStaticValue(
+ Domain.EXAM.ATTR_INSTITUTION_ID,
+ exam.getModelId())
+ .putStaticValue(
+ Domain.INDICATOR.ATTR_EXAM_ID,
+ parentEntityKey.getModelId())
+ .addField(FormBuilder.text(
+ "examName",
+ "sebserver.exam.indicator.form.exam",
+ exam.name)
+ .readonly(true))
+ .addField(FormBuilder.text(
+ Domain.INDICATOR.ATTR_NAME,
+ "sebserver.exam.indicator.form.name",
+ indicator.name))
+ .addField(FormBuilder.singleSelection(
+ Domain.INDICATOR.ATTR_TYPE,
+ "sebserver.exam.indicator.form.type",
+ (indicator.type != null) ? indicator.type.name() : null,
+ this.resourceService::indicatorTypeResources))
+ .addField(FormBuilder.colorSelection(
+ Domain.INDICATOR.ATTR_TYPE,
+ "sebserver.exam.indicator.form.color",
+ indicator.defaultColor))
+ .buildFor((isNew)
+ ? restService.getRestCall(NewIndicator.class)
+ : restService.getRestCall(SaveIndicator.class));
+
+ // propagate content actions to action-pane
+ formContext.clearEntityKeys()
+ .createAction(ActionDefinition.EXAM_INDICATOR_SAVE)
+ .withParentEntityKey(parentEntityKey)
+ .withExec(formHandle::processFormSave)
+ .publishIf(() -> !isReadonly)
+
+ .createAction(ActionDefinition.EXAM_INDICATOR_CANCEL_MODIFY)
+ .withEntityKey(parentEntityKey)
+ .withExec(Action::onEmptyEntityKeyGoToActivityHome)
+ .withConfirm("sebserver.overall.action.modify.cancel.confirm")
+ .publishIf(() -> !isReadonly);
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java
index fa5768c7..cb3423ee 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionForm.java
@@ -72,7 +72,7 @@ public class InstitutionForm implements TemplateComposer {
final EntityKey entityKey = pageContext.getEntityKey();
final boolean isNew = entityKey == null;
// get data or create new. Handle error if happen
- final Institution institution = (entityKey == null)
+ final Institution institution = (isNew)
? Institution.createNew()
: this.restService
.getBuilder(GetInstitution.class)
@@ -116,7 +116,7 @@ public class InstitutionForm implements TemplateComposer {
// The Institution form
final FormHandle formHandle = this.pageFormService.getBuilder(
formContext.copyOf(content), 4)
- .readonly(formContext.isReadonly())
+ .readonly(isReadonly)
.putStaticValueIf(() -> !isNew,
Domain.INSTITUTION.ATTR_ID,
institution.getModelId())
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java
index dd1ce4b6..96c66b45 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java
@@ -196,7 +196,7 @@ public class LmsSetupForm implements TemplateComposer {
.createAction(ActionDefinition.LMS_SETUP_ACTIVATE)
.withEntityKey(entityKey)
- .withExec(restService::activation)
+ .withExec(action -> activate(action, formHandle))
.publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive())
.createAction(ActionDefinition.LMS_SETUP_SAVE)
@@ -210,6 +210,14 @@ public class LmsSetupForm implements TemplateComposer {
.publishIf(() -> !readonly);
}
+ /** Save and test connection before activation */
+ private Action activate(final Action action, final FormHandle formHandle) {
+ final RestService restService = this.resourceService.getRestService();
+ final Action testLmsSetup = this.testLmsSetup(action, formHandle);
+ final Action activation = restService.activation(testLmsSetup);
+ return activation;
+ }
+
/** LmsSetup test action implementation */
private Action testLmsSetup(final Action action, final FormHandle formHandle) {
// If we are in edit-mode we have to save the form before testing
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java
index 8a8efc27..0824f29d 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java
@@ -321,7 +321,6 @@ public enum ActionDefinition {
ImageIcon.NEW,
IndicatorForm.class,
EXAM_VIEW_FROM_LIST,
- ActionCategory.FORM,
false),
EXAM_INDICATOR_MODIFY_FROM_LIST(
new LocTextKey("sebserver.exam.indicator.action.list.modify"),
@@ -338,7 +337,7 @@ public enum ActionDefinition {
ActionCategory.INDICATOR_LIST,
true),
EXAM_INDICATOR_SAVE(
- new LocTextKey("sebserver.exam.indicator.action.list.save"),
+ new LocTextKey("sebserver.exam.indicator.action.save"),
ImageIcon.SAVE,
ExamForm.class,
EXAM_VIEW_FROM_LIST,
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java
index cd021a3a..f6b9f469 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java
@@ -195,7 +195,9 @@ public final class Form implements FormBinding {
}
private FormFieldAccessor createAccessor(final Label label, final Selection selection) {
switch (selection.type()) {
- case MULTI : return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray);
+ case MULTI:
+ case MULTI_COMBO:
+ return createAccessor(label, selection, Form::adaptCommaSeparatedStringToJsonArray);
default : return createAccessor(label, selection, null);
}
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java
index ce1bccbf..34bc940a 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormBuilder.java
@@ -216,6 +216,14 @@ public class FormBuilder {
return new SelectionFieldBuilder(Selection.Type.MULTI_COMBO, name, label, value, itemsSupplier);
}
+ public static SelectionFieldBuilder colorSelection(
+ final String name,
+ final String label,
+ final String value) {
+
+ return new SelectionFieldBuilder(Selection.Type.COLOR, name, label, value, null);
+ }
+
public static ImageUploadFieldBuilder imageUpload(final String name, final String label, final String value) {
return new ImageUploadFieldBuilder(name, label, value);
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java
index 1f09f780..41898fcb 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/SelectionFieldBuilder.java
@@ -89,6 +89,7 @@ public final class SelectionFieldBuilder extends FieldBuilder {
if (this.type == Type.MULTI || this.type == Type.MULTI_COMBO) {
final Composite composite = new Composite(builder.formParent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(1, true);
+ gridLayout.verticalSpacing = 0;
gridLayout.horizontalSpacing = 0;
gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0;
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java
index cd292f2d..019e876d 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/OAuth2AuthorizationContextHolder.java
@@ -206,7 +206,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
try {
final OAuth2AccessToken accessToken = this.restTemplate.getAccessToken();
- log.debug("Got token for user: {} : {}", username, accessToken);
+ log.debug("Got token for user: {} : {}", username, "--");
this.loggedInUser = getLoggedInUser();
return true;
} catch (final OAuth2AccessDeniedException | AccessDeniedException e) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/ColorSelection.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ColorSelection.java
new file mode 100644
index 00000000..25625469
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ColorSelection.java
@@ -0,0 +1,146 @@
+/*
+ * 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.widget;
+
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.ColorDialog;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.ethz.seb.sebserver.gbl.util.Tuple;
+import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
+
+public class ColorSelection extends Composite implements Selection {
+
+ private static final long serialVersionUID = 4775375044147842526L;
+ private static final Logger log = LoggerFactory.getLogger(ColorSelection.class);
+
+ private static final int ACTION_COLUMN_WIDTH = 20;
+ private final ColorDialog colorDialog;
+ private final Composite colorField;
+ private RGB selection;
+
+ ColorSelection(final Composite parent, final WidgetFactory widgetFactory) {
+ super(parent, SWT.NONE);
+ final GridLayout gridLayout = new GridLayout(2, false);
+ gridLayout.verticalSpacing = 1;
+ gridLayout.marginLeft = 0;
+ gridLayout.marginHeight = 0;
+ gridLayout.marginWidth = 0;
+ setLayout(gridLayout);
+
+ this.colorDialog = new ColorDialog(this.getShell(), SWT.NONE);
+
+ this.colorField = new Composite(this, SWT.NONE);
+ final GridData colorCell = new GridData(SWT.FILL, SWT.TOP, true, false);
+ colorCell.heightHint = 20;
+ this.colorField.setLayoutData(colorCell);
+
+ final Label imageButton = widgetFactory.imageButton(
+ ImageIcon.COLOR,
+ this,
+ new LocTextKey("Set Color"),
+ this::addColorSelection);
+
+ final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
+ actionCell.widthHint = ACTION_COLUMN_WIDTH;
+ imageButton.setLayoutData(actionCell);
+
+ this.addListener(SWT.Resize, this::adaptColumnWidth);
+ }
+
+ @Override
+ public Type type() {
+ return Type.COLOR;
+ }
+
+ @Override
+ public void applyNewMapping(final List> mapping) {
+ }
+
+ @Override
+ public void select(final String keys) {
+ this.selection = parseRGB(keys);
+ applySelection();
+ }
+
+ @Override
+ public String getSelectionValue() {
+ return parseColorString(this.selection);
+ }
+
+ @Override
+ public void clear() {
+ this.selection = null;
+ }
+
+ private void addColorSelection(final Event event) {
+ this.colorDialog.open(code -> {
+ if (code == SWT.CANCEL) {
+ return;
+ }
+
+ this.selection = this.colorDialog.getRGB();
+ applySelection();
+ });
+ }
+
+ private void applySelection() {
+ if (this.selection != null) {
+ this.colorField.setBackground(new Color(this.getDisplay(), this.selection));
+ } else {
+ this.colorField.setBackground(null);
+ }
+ }
+
+ private void adaptColumnWidth(final Event event) {
+ try {
+ final int currentTableWidth = this.getClientArea().width;
+ final GridData comboCell = (GridData) this.colorField.getLayoutData();
+ comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH;
+ this.layout();
+ } catch (final Exception e) {
+ log.warn("Failed to adaptColumnWidth: ", e);
+ }
+ }
+
+ static String parseColorString(final RGB color) {
+ if (color == null) {
+ return null;
+ }
+
+ return Integer.toHexString(color.red)
+ + Integer.toHexString(color.green)
+ + Integer.toHexString(color.blue);
+ }
+
+ static RGB parseRGB(final String colorString) {
+ if (StringUtils.isBlank(colorString)) {
+ return null;
+ }
+
+ final int r = Integer.parseInt(colorString.substring(0, 2), 16);
+ final int g = Integer.parseInt(colorString.substring(2, 4), 16);
+ final int b = Integer.parseInt(colorString.substring(4, 6), 16);
+
+ return new RGB(r, g, b);
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/ImageUpload.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ImageUpload.java
index de6f2fe1..f0cfe6f9 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/ImageUpload.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ImageUpload.java
@@ -26,6 +26,7 @@ import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
@@ -51,14 +52,14 @@ public class ImageUpload extends Composite {
ImageUpload(final Composite parent, final ServerPushService serverPushService, final boolean readonly) {
super(parent, SWT.NONE);
- super.setLayout(new GridLayout(2, false));
+ super.setLayout(new GridLayout(1, false));
this.serverPushService = serverPushService;
if (!readonly) {
this.fileUpload = new FileUpload(this, SWT.NONE);
- this.fileUpload.setText("Select File");
- this.fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
+ this.fileUpload.setImage(WidgetFactory.ImageIcon.IMPORT.getImage(parent.getDisplay()));
+ this.fileUpload.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
final FileUploadHandler uploadHandler = new FileUploadHandler(new FileUploadReceiver() {
@@ -108,7 +109,7 @@ public class ImageUpload extends Composite {
public void setSelectionText(final String text) {
if (this.fileUpload != null) {
- this.fileUpload.setText(text);
+ this.fileUpload.setToolTipText(text);
}
}
@@ -124,8 +125,8 @@ public class ImageUpload extends Composite {
this.imageBase64 = imageBase64;
final Base64InputStream input = new Base64InputStream(
new ByteArrayInputStream(imageBase64.getBytes(StandardCharsets.UTF_8)), false);
- this.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
- this.imageCanvas.setBackgroundImage(new Image(super.getDisplay(), input));
+
+ setImage(this, input);
}
private static final boolean uploadInProgress(final ServerPushContext context) {
@@ -153,12 +154,20 @@ public class ImageUpload extends Composite {
imageUpload.imageBase64.getBytes(StandardCharsets.UTF_8)),
false);
- imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
- imageUpload.imageCanvas.setBackgroundImage(new Image(context.getDisplay(), input));
+ setImage(imageUpload, input);
context.layout();
imageUpload.layout();
imageUpload.loadNewImage = false;
}
}
+ private static void setImage(final ImageUpload imageUpload, final Base64InputStream input) {
+ imageUpload.imageCanvas.setData(RWT.CUSTOM_VARIANT, "bgLogoNoImage");
+
+ final Image image = new Image(imageUpload.imageCanvas.getDisplay(), input);
+ final ImageData imageData = image.getImageData().scaledTo(200, 100);
+
+ imageUpload.imageCanvas.setBackgroundImage(new Image(imageUpload.imageCanvas.getDisplay(), imageData));
+ }
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java
index 5bf96414..6ca8de53 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java
@@ -9,18 +9,23 @@
package ch.ethz.seb.sebserver.gui.widget;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.TableEditor;
+import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Label;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,43 +39,40 @@ public class MultiSelectionCombo extends Composite implements Selection {
private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class);
private static final long serialVersionUID = -7787134114963647332L;
private static final int ACTION_COLUMN_WIDTH = 20;
+ private static final String SELECTION_KEY = "SELECTION_KEY";
private final WidgetFactory widgetFactory;
- private final Table table;
private final Combo combo;
- private final List selected = new ArrayList<>();
- private final List> mapping = new ArrayList<>();
+
+ private final List> selectionControls = new ArrayList<>();
+ private final List> selectedValues = new ArrayList<>();
+ private final Map mapping = new HashMap<>();
+ //private final List> mapping = new ArrayList<>();
MultiSelectionCombo(final Composite parent, final WidgetFactory widgetFactory) {
super(parent, SWT.NONE);
this.widgetFactory = widgetFactory;
- final GridLayout gridLayout = new GridLayout(1, true);
+ final GridLayout gridLayout = new GridLayout(2, false);
gridLayout.verticalSpacing = 1;
gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0;
setLayout(gridLayout);
- this.table = new Table(this, SWT.NONE);
+ this.addListener(SWT.Resize, this::adaptColumnWidth);
- TableColumn column = new TableColumn(this.table, SWT.NONE);
- column = new TableColumn(this.table, SWT.NONE);
- column.setWidth(ACTION_COLUMN_WIDTH);
- this.table.setHeaderVisible(false);
- this.table.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);
- final TableItem header = new TableItem(this.table, SWT.NONE);
- final TableEditor editor = new TableEditor(this.table);
- this.combo = new Combo(this.table, SWT.NONE);
- editor.grabHorizontal = true;
- editor.setEditor(this.combo, header, 0);
-
- widgetFactory.imageButton(
- ImageIcon.ADD,
- this.table,
+ 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);
}
@Override
@@ -80,49 +82,132 @@ public class MultiSelectionCombo extends Composite implements Selection {
@Override
public void applyNewMapping(final List> mapping) {
- this.selected.clear();
- this.mapping.clear();
- this.mapping.addAll(mapping);
-
+ this.mapping.putAll(mapping.stream()
+ .collect(Collectors.toMap(t -> t._1, t -> t._2)));
+ this.clear();
}
@Override
public void select(final String keys) {
clear();
+ if (StringUtils.isBlank(keys)) {
+ return;
+ }
+ Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR))
+ .stream()
+ .forEach(this::addSelection);
}
@Override
public String getSelectionValue() {
- if (this.selected.isEmpty()) {
+ if (this.selectedValues.isEmpty()) {
return null;
}
- return this.mapping
+ return this.selectedValues
.stream()
- .filter(t -> this.selected.contains(t._2))
.map(t -> t._1)
- .reduce("", (s1, s2) -> s1.concat(Constants.LIST_SEPARATOR).concat(s2));
+ .reduce("", (s1, s2) -> {
+ if (!StringUtils.isBlank(s1)) {
+ return s1.concat(Constants.LIST_SEPARATOR).concat(s2);
+ } else {
+ return s1.concat(s2);
+ }
+ });
}
@Override
public void clear() {
- this.selected.clear();
- this.table.remove(1, this.table.getItemCount());
- final List names = this.mapping
+ this.selectedValues.clear();
+ this.selectionControls
.stream()
- .map(t -> t._2)
- .collect(Collectors.toList());
- this.combo.setItems(names.toArray(new String[names.size()]));
+ .forEach(t -> {
+ t._1.dispose();
+ t._2.dispose();
+ });
+ this.selectionControls.clear();
+ this.combo.setItems(this.mapping.values().toArray(new String[this.mapping.size()]));
}
private void addComboSelection(final Event event) {
+ final int selectionIndex = this.combo.getSelectionIndex();
+ if (selectionIndex < 0) {
+ return;
+ }
+ final String itemName = this.combo.getItem(selectionIndex);
+ if (itemName == null) {
+ return;
+ }
+
+ final Optional> findFirst = this.mapping.entrySet()
+ .stream()
+ .filter(entity -> entity.getValue().equals(itemName))
+ .findFirst();
+
+ if (!findFirst.isPresent()) {
+ return;
+ }
+
+ addSelection(findFirst.get().getKey());
+ }
+
+ private void addSelection(final String itemKey) {
+ final String itemName = this.mapping.get(itemKey);
+ if (itemName == null) {
+ return;
+ }
+
+ this.selectedValues.add(new Tuple<>(itemKey, itemName));
+ final Label label = this.widgetFactory.label(this, itemName);
+ final Label imageButton = this.widgetFactory.imageButton(
+ ImageIcon.REMOVE_BOX,
+ this,
+ new LocTextKey("Remove"),
+ this::removeComboSelection);
+ imageButton.setData(SELECTION_KEY, itemName);
+ final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
+ actionCell.widthHint = ACTION_COLUMN_WIDTH;
+ imageButton.setLayoutData(actionCell);
+
+ this.selectionControls.add(new Tuple<>(label, imageButton));
+
+ this.combo.remove(itemName);
+ this.getParent().layout();
+ }
+
+ private void removeComboSelection(final Event event) {
+ if (event.widget == null) {
+ return;
+ }
+
+ final String selectionKey = (String) event.widget.getData(SELECTION_KEY);
+ final Optional> findFirst = this.selectionControls.stream()
+ .filter(t -> selectionKey.equals(t._2.getData(SELECTION_KEY)))
+ .findFirst();
+ if (!findFirst.isPresent()) {
+ return;
+ }
+
+ final Tuple tuple = findFirst.get();
+ final int indexOf = this.selectionControls.indexOf(tuple);
+ this.selectionControls.remove(tuple);
+
+ tuple._1.dispose();
+ tuple._2.dispose();
+
+ final Tuple value = this.selectedValues.remove(indexOf);
+ this.combo.add(value._2, this.combo.getItemCount());
+
+ this.getParent().layout();
}
private void adaptColumnWidth(final Event event) {
try {
- final int currentTableWidth = this.table.getParent().getClientArea().width;
- this.table.getColumn(0).setWidth(currentTableWidth - ACTION_COLUMN_WIDTH);
+ final int currentTableWidth = this.getClientArea().width;
+ final GridData comboCell = (GridData) this.combo.getLayoutData();
+ comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH;
+ this.layout();
} catch (final Exception e) {
log.warn("Failed to adaptColumnWidth: ", e);
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/Selection.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/Selection.java
index 074a7e60..e2bb3b80 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/Selection.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/Selection.java
@@ -19,7 +19,8 @@ public interface Selection {
enum Type {
SINGLE,
MULTI,
- MULTI_COMBO
+ MULTI_COMBO,
+ COLOR,
}
Type type();
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/ThresholdList.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ThresholdList.java
new file mode 100644
index 00000000..d8b73e9b
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ThresholdList.java
@@ -0,0 +1,13 @@
+/*
+ * 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.widget;
+
+public class ThresholdList {
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java
index 434a238a..d160a0de 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java
@@ -62,6 +62,8 @@ public class WidgetFactory {
MINIMIZE("minimize.png"),
ADD("add.png"),
REMOVE("remove.png"),
+ ADD_BOX("add_box.png"),
+ REMOVE_BOX("remove_box.png"),
EDIT("edit.png"),
TEST("test.png"),
IMPORT("import.png"),
@@ -73,7 +75,8 @@ public class WidgetFactory {
SAVE("save.png"),
NEW("new.png"),
DELETE("delete.png"),
- SEARCH("lens.png");
+ SEARCH("lens.png"),
+ COLOR("color.png");
private String fileName;
private ImageData image = null;
@@ -325,49 +328,21 @@ public class WidgetFactory {
case MULTI_COMBO:
selection = new MultiSelectionCombo(parent, this);
break;
+ case COLOR:
+ selection = new ColorSelection(parent, this);
+ break;
default:
throw new IllegalArgumentException("Unsupported Selection.Type: " + type);
}
- final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
- selection.adaptToControl().setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
- updateFunction.accept(selection);
+ if (itemsSupplier != null) {
+ final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
+ selection.adaptToControl().setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
+ updateFunction.accept(selection);
+ }
return selection;
}
-// public SingleSelection singleSelectionLocalized(
-// final Composite parent,
-// final Supplier>> itemsSupplier) {
-//
-// final SingleSelection singleSelection = new SingleSelection(parent);
-// final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
-// singleSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
-// updateFunction.accept(singleSelection);
-// return singleSelection;
-// }
-//
-// public MultiSelection multiSelectionLocalized(
-// final Composite parent,
-// final Supplier>> itemsSupplier) {
-//
-// final MultiSelection multiSelection = new MultiSelection(parent);
-// final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
-// multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
-// updateFunction.accept(multiSelection);
-// return multiSelection;
-// }
-//
-// public MultiSelectionCombo multiSelectionComboLocalized(
-// final Composite parent,
-// final Supplier>> itemsSupplier) {
-//
-// final MultiSelectionCombo multiSelection = new MultiSelectionCombo(parent, this);
-// final Consumer updateFunction = ss -> ss.applyNewMapping(itemsSupplier.get());
-// multiSelection.setData(POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
-// updateFunction.accept(multiSelection);
-// return multiSelection;
-// }
-
public ImageUpload imageUploadLocalized(
final Composite parent,
final LocTextKey locTextKey,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationServiceImpl.java
index ce7b6147..27462b25 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/authorization/AuthorizationServiceImpl.java
@@ -107,7 +107,7 @@ public class AuthorizationServiceImpl implements AuthorizationService {
.withInstitutionalPrivilege(PrivilegeType.MODIFY)
.withOwnerPrivilege(PrivilegeType.WRITE)
.andForRole(UserRole.EXAM_SUPPORTER)
- .withOwnerPrivilege(PrivilegeType.MODIFY)
+ .withInstitutionalPrivilege(PrivilegeType.READ_ONLY)
.create();
// grants for indicators
@@ -120,7 +120,7 @@ public class AuthorizationServiceImpl implements AuthorizationService {
.withInstitutionalPrivilege(PrivilegeType.MODIFY)
.withOwnerPrivilege(PrivilegeType.WRITE)
.andForRole(UserRole.EXAM_SUPPORTER)
- .withOwnerPrivilege(PrivilegeType.MODIFY)
+ .withInstitutionalPrivilege(PrivilegeType.READ_ONLY)
.create();
// TODO other entities
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/client/ClientCredentialServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/client/ClientCredentialServiceImpl.java
index 7f56026d..5f2d211f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/client/ClientCredentialServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/client/ClientCredentialServiceImpl.java
@@ -12,6 +12,8 @@ import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import org.apache.commons.lang3.RandomStringUtils;
+import org.cryptonode.jncryptor.AES256JNCryptor;
+import org.cryptonode.jncryptor.JNCryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
@@ -35,6 +37,9 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
private final Environment environment;
+ // TODO try to integrate with JNCryptor since this is also used by SEB
+ private final JNCryptor cryptor = new AES256JNCryptor();
+
protected ClientCredentialServiceImpl(final Environment environment) {
this.environment = environment;
}
@@ -142,6 +147,7 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
}
try {
+
return Encryptors
.delux(secret, getSalt(salt))
.encrypt(text.toString());
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/EntityDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/EntityDAO.java
index 50c885de..785aa1fb 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/EntityDAO.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/EntityDAO.java
@@ -95,7 +95,9 @@ public interface EntityDAO {
* reported exception on error case */
Result save(T data);
- /** Use this to delete a set Entity by a Collection of EntityKey
+ /** Use this to delete all entities defined by a set of EntityKey
+ * NOTE: the Set of EntityKey may contain EntityKey of other entity types like the concrete type of the DAO
+ * use extractPKsFromKeys to get a list of concrete primary keys for entities to delete
*
* @param all The Collection of EntityKey to delete
* @return Result referring a collection of all entities that has been deleted or refer to an error if
@@ -104,12 +106,12 @@ public interface EntityDAO {
/** Get a (unordered) collection of all Entities that matches the given filter criteria.
* The possible filter criteria for a specific Entity type is defined by the entity type.
- *
+ *
* This adds filtering in SQL level by creating the select where clause from related
* filter criteria of the specific Entity type. If the filterMap contains a value for
* a particular filter criteria the value is extracted from the map and added to the where
* clause of the SQL select statement.
- *
+ *
* @param filterMap FilterMap instance containing all the relevant filter criteria
* @return Result referring to collection of all matching entities or an error if happened */
@Transactional(readOnly = true)
@@ -119,16 +121,16 @@ public interface EntityDAO {
/** Get a (unordered) collection of all Entities that matches a given filter criteria
* and a given predicate.
- *
+ *
* The possible filter criteria for a specific Entity type is defined by the entity type.
* This adds filtering in SQL level by creating the select where clause from related
* filter criteria of the specific Entity type. If the filterMap contains a value for
* a particular filter criteria the value is extracted from the map and added to the where
* clause of the SQL select statement.
- *
+ *
* The predicate is applied after the SQL query by filtering the resulting list with the
* predicate after on the SQL query result, before returning.
- *
+ *
* @param filterMap FilterMap instance containing all the relevant filter criteria
* @return Result referring to collection of all matching entities or an error if happened */
Result> allMatching(FilterMap filterMap, Predicate predicate);
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java
index 0ba770bb..c3d68512 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java
@@ -261,8 +261,6 @@ public class IndicatorDAOImpl implements IndicatorDAO {
.execute()
.stream()
.map(tRec -> new Threshold(
- tRec.getId(),
- tRec.getIndicatorId(),
tRec.getValue().doubleValue(),
tRec.getColor()))
.collect(Collectors.toList());
@@ -270,8 +268,8 @@ public class IndicatorDAOImpl implements IndicatorDAO {
return new Indicator(
record.getId(),
examRecord.getInstitutionId(),
- examRecord.getOwner(),
record.getExamId(),
+ examRecord.getOwner(),
record.getName(),
IndicatorType.valueOf(record.getType()),
record.getColor(),
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java
index 528bdcc8..f22dfa8b 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java
@@ -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-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
+ "2020-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2", "Demo Quit Mockup",
- "2020-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
+ "2020-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz3", institutionId, lmsSetupId, lmsType, "Demo Quiz 3", "Demo Quit Mockup",
- "2018-07-30 09:00:00", "2018-08-01 00:00:00", "http://lms.mockup.com/api/"));
+ "2018-07-30T09:00:00", "2018-08-01T00:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz4", institutionId, lmsSetupId, lmsType, "Demo Quiz 4", "Demo Quit Mockup",
- "2018-01-01 00:00:00", "2019-01-01 00:00:00", "http://lms.mockup.com/api/"));
+ "2018-01-01T00:00:00", "2019-01-01T00:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz5", institutionId, lmsSetupId, lmsType, "Demo Quiz 5", "Demo Quit Mockup",
- "2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
+ "2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz6", institutionId, lmsSetupId, lmsType, "Demo Quiz 6", "Demo Quit Mockup",
- "2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
+ "2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData(
"quiz7", institutionId, lmsSetupId, lmsType, "Demo Quiz 7", "Demo Quit Mockup",
- "2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
+ "2018-01-01T09:00:00", "2021-01-01T09:00:00", "http://lms.mockup.com/api/"));
}
@Override
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java
index dee9f595..3580d3fc 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java
@@ -25,9 +25,13 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.oauth2.client.OAuth2ClientContext;
+import org.springframework.security.oauth2.client.OAuth2RequestAuthenticator;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
@@ -115,6 +119,19 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
this.knownTokenAccessPaths);
}
+ try {
+ this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT);
+ } catch (final Exception e) {
+ if (this.restTemplate != null) {
+ this.restTemplate.setAuthenticator(new EdxOAuth2RequestAuthenticator());
+ }
+ try {
+ this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT);
+ } catch (final Exception ee) {
+ return LmsSetupTestResult.ofQuizRequestError(ee.getMessage());
+ }
+ }
+
return LmsSetupTestResult.ofOkay();
}
@@ -192,28 +209,6 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
return template;
}
-// private Result> getAllQuizes(final LmsSetup lmsSetup) {
-// if (this.allQuizzesSupplier == null) {
-// this.allQuizzesSupplier = new CircuitBreaker<>(
-// () -> collectAllCourses(lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT)
-// .stream()
-// .reduce(
-// new ArrayList(),
-// (list, courseData) -> {
-// list.add(quizDataOf(lmsSetup, courseData));
-// return list;
-// },
-// (list1, list2) -> {
-// list1.addAll(list2);
-// return list1;
-// }),
-// 5, 1000L); // TODO specify better CircuitBreaker params
-// }
-//
-// return this.allQuizzesSupplier.get();
-//
-// }
-
private Supplier> allQuizzesSupplier(final LmsSetup lmsSetup) {
return () -> {
return initRestTemplateAndRequestAccessToken()
@@ -320,8 +315,27 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
params.add("client_id", resource.getClientId());
params.add("client_secret", resource.getClientSecret());
- return retrieveToken(request, resource, params, headers);
+ final OAuth2AccessToken retrieveToken = retrieveToken(request, resource, params, headers);
+ return retrieveToken;
}
}
+ private class EdxOAuth2RequestAuthenticator implements OAuth2RequestAuthenticator {
+
+ @Override
+ public void authenticate(
+ final OAuth2ProtectedResourceDetails resource,
+ final OAuth2ClientContext clientContext,
+ final ClientHttpRequest request) {
+
+ final OAuth2AccessToken accessToken = clientContext.getAccessToken();
+ if (accessToken == null) {
+ throw new AccessTokenRequiredException(resource);
+ }
+
+ request.getHeaders().set("Authorization", String.format("%s %s", "JWT:", accessToken.getValue()));
+ }
+
+ }
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIController.java
new file mode 100644
index 00000000..bf5d0cb0
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPIController.java
@@ -0,0 +1,86 @@
+/*
+ * 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.webservice.weblayer.api;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.model.seb.PingResponse;
+import ch.ethz.seb.sebserver.gbl.model.seb.RunningExams;
+import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
+
+@WebServiceProfile
+@RestController
+@RequestMapping("/${sebserver.webservice.api.exam.endpoint}")
+public class ExamAPIController {
+
+ @RequestMapping(
+ path = API.EXAM_API_HANDSHAKE_ENDPOINT,
+ method = RequestMethod.GET,
+ consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
+ produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+ public RunningExams handshake(
+ @RequestParam(name = API.PARAM_INSTITUTION_ID, required = true) final Long institutionId,
+ final HttpServletRequest request,
+ final HttpServletResponse response) {
+
+ // TODO
+ return null;
+ }
+
+ @RequestMapping(
+ path = API.EXAM_API_CONFIGURATION_REQUEST_ENDPOINT,
+ method = RequestMethod.GET,
+ consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
+ produces = MediaType.TEXT_XML_VALUE)
+ public RunningExams getConfig(
+ @RequestParam(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken,
+ @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = true) final String examId,
+ final HttpServletRequest request,
+ final HttpServletResponse response) {
+
+ // TODO
+ return null;
+ }
+
+ @RequestMapping(
+ path = API.EXAM_API_PING_ENDPOINT,
+ method = RequestMethod.PUT,
+ consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
+ produces = MediaType.TEXT_XML_VALUE)
+ public PingResponse ping(
+ @RequestParam(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken,
+ final HttpServletRequest request,
+ final HttpServletResponse response) {
+
+ // TODO
+ return null;
+ }
+
+ @RequestMapping(
+ path = API.EXAM_API_EVENT_ENDPOINT,
+ method = RequestMethod.POST,
+ consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+ public void event(
+ @RequestParam(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken,
+ final HttpServletRequest request,
+ final HttpServletResponse response) {
+
+ // TODO
+
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamIndicatorController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/IndicatorController.java
similarity index 88%
rename from src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamIndicatorController.java
rename to src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/IndicatorController.java
index e5151b2e..62535887 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamIndicatorController.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/IndicatorController.java
@@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.IndicatorRecordDynamicSqlSupport;
@@ -28,11 +29,11 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
@WebServiceProfile
@RestController
@RequestMapping("/${sebserver.webservice.api.admin.endpoint}" + API.EXAM_INDICATOR_ENDPOINT)
-public class ExamIndicatorController extends EntityController {
+public class IndicatorController extends EntityController {
private final ExamDAO examDao;
- protected ExamIndicatorController(
+ protected IndicatorController(
final AuthorizationService authorization,
final BulkActionService bulkActionService,
final IndicatorDAO entityDAO,
@@ -53,7 +54,7 @@ public class ExamIndicatorController extends EntityController checkPasswordChange(ui, passwordChange))
+ .flatMap(e -> this.userDAO.changePassword(modelId, passwordChange.getNewPassword()))
+ .flatMap(this::revokeAccessToken)
+ .flatMap(e -> this.userActivityLogDAO.log(ActivityType.PASSWORD_CHANGE, e))
+ .getOrThrow();
+ }
+
+ private Result revokeAccessToken(final UserInfo userInfo) {
+ return Result.tryCatch(() -> {
+ this.applicationEventPublisher.publishEvent(
+ new RevokeTokenEndpoint.RevokeTokenEvent(userInfo, userInfo.username));
+ return userInfo;
+ });
+ }
+
/** Additional consistency checks that has to be checked before create and save actions */
private Result additionalConsistencyChecks(final T userInfo) {
return Result.tryCatch(() -> {
@@ -194,29 +219,4 @@ public class UserAccountController extends ActivatableEntityController checkPasswordChange(ui, passwordChange))
- .flatMap(e -> this.userDAO.changePassword(modelId, passwordChange.getNewPassword()))
- .flatMap(this::revokeAccessToken)
- .flatMap(e -> this.userActivityLogDAO.log(ActivityType.PASSWORD_CHANGE, e))
- .getOrThrow();
- }
-
- private Result revokeAccessToken(final UserInfo userInfo) {
- return Result.tryCatch(() -> {
- this.applicationEventPublisher.publishEvent(
- new RevokeTokenEndpoint.RevokeTokenEvent(userInfo, userInfo.username));
- return userInfo;
- });
- }
-
}
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index c5006929..99ef0581 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -77,9 +77,9 @@ sebserver.institution.list.column.active=Active
sebserver.institution.action.list=Institution
sebserver.institution.action.form=Institution
sebserver.institution.action.new=New Institution
-sebserver.institution.action.list.view=View Selected
-sebserver.institution.action.modify=Edit Institution
-sebserver.institution.action.list.modify=Edit Selected
+sebserver.institution.action.list.view=View Institution
+sebserver.institution.action.list.modify=Edit Institution
+sebserver.institution.action.modify=Edit
sebserver.institution.action.save=Save Institution
sebserver.institution.action.activate=Activate
sebserver.institution.action.deactivate=Deactivate
@@ -117,8 +117,8 @@ sebserver.useraccount.list.column.active=Active
sebserver.useraccount.action.list=User Account
sebserver.useraccount.action.form=User Account of {0}
sebserver.useraccount.action.new=New User Account
-sebserver.useraccount.action.view=View Selected
-sebserver.useraccount.action.list.modify=Edit Selected
+sebserver.useraccount.action.view=View User Account
+sebserver.useraccount.action.list.modify=Edit User Account
sebserver.useraccount.action.modify=Edit
sebserver.useraccount.action.save=Save User Account
sebserver.useraccount.action.activate=Activate
@@ -165,8 +165,8 @@ sebserver.lmssetup.list.column.active=Active
sebserver.lmssetup.action.list=LMS Setup
sebserver.lmssetup.action.form=LMS Setup
sebserver.lmssetup.action.new=New LMS Setup
-sebserver.lmssetup.action.list.view=View Selected
-sebserver.lmssetup.action.list.modify=Edit Selected
+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
@@ -225,6 +225,9 @@ sebserver.exam.list.column.type=Type
sebserver.exam.list.empty=No Exams has been found. Please adapt the filter or import one from Quiz
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.import=Import From Quizzes
sebserver.exam.action.save=Save
sebserver.exam.action.activate=Activate
@@ -242,6 +245,7 @@ sebserver.exam.form.starttime=Start Time
sebserver.exam.form.endtime=End Time
sebserver.exam.form.status=Status
sebserver.exam.form.type=Exam Type
+sebserver.exam.form.supporter=Exam Supporter
sebserver.exam.type.UNDEFINED=Not Defined
sebserver.exam.type.MANAGED=Managed
@@ -252,8 +256,9 @@ sebserver.exam.indicator.list.actions=Selected Indicator
sebserver.exam.indicator.list.title=Indicators
sebserver.exam.indicator.list.column.type=Type
sebserver.exam.indicator.list.column.name=Name
-sebserver.exam.indicator.list.column.thresholds
+sebserver.exam.indicator.list.column.thresholds=Thresholds
sebserver.exam.indicator.list.empty=There is currently no Indicator defined for this Exam. Please create a new one
+sebserver.exam.indicator.list.pleaseSelect=Please Select an Indicator first
sebserver.exam.indicator.type.LAST_PING=Last Ping
sebserver.exam.indicator.type.ERROR_COUNT=Error Count
@@ -262,4 +267,13 @@ 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.save=Save
+sebserver.exam.indicator.form.title=Indicator
+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.thresholds.list.title=Thresholds
diff --git a/src/main/resources/static/css/sebserver.css b/src/main/resources/static/css/sebserver.css
index f02b34b8..3f0be26c 100644
--- a/src/main/resources/static/css/sebserver.css
+++ b/src/main/resources/static/css/sebserver.css
@@ -345,9 +345,7 @@ Button {
/* Push Buttons */
Button[PUSH],
-Button[PUSH]:default,
-FileUpload,
-FileUpload:default {
+Button[PUSH]:default {
font: bold 12px Arial, Helvetica, sans-serif;
background-color: #0069B4;
background-gradient-color: #0069B4;
@@ -359,16 +357,14 @@ FileUpload:default {
text-shadow: none;
}
-Button[PUSH]:pressed,
-FileUpload:pressed {
+Button[PUSH]:pressed {
background-color: #444;
color: #fff;
background-gradient-color: #444;
background-image: gradient( linear, left top, left bottom, from( #444 ), to( #444 ) );
}
-Button[PUSH]:hover,
-FileUpload:hover {
+Button[PUSH]:hover {
background-color: #82BE1E;
background-gradient-color: #82BE1E;
background-image: gradient( linear, left top, left bottom, from( #82BE1E ), to( #82BE1E ) );
@@ -376,8 +372,7 @@ FileUpload:hover {
cursor: pointer;
}
-Button[PUSH]:disabled,
-FileUpload:disabled {
+Button[PUSH]:disabled {
background-color: transparent;
border: 1px solid #EAECEE;
color: #c0c0c0;
@@ -418,6 +413,18 @@ Button[PUSH]:hover.header {
cursor: pointer;
}
+FileUpload,
+FileUpload:default,
+FileUpload:hover,
+FileUpload:pressed {
+ background-color: transparent;
+ background-gradient-color: transparent;
+ background-image: gradient( linear, left top, left bottom, from( transparent ), to( transparent ) );
+ border: none;
+ border-radius: 0px;
+ text-shadow: none;
+}
+
/* Sash default */
Sash {
diff --git a/src/main/resources/static/images/add_box.png b/src/main/resources/static/images/add_box.png
new file mode 100644
index 00000000..86527618
Binary files /dev/null and b/src/main/resources/static/images/add_box.png differ
diff --git a/src/main/resources/static/images/color.png b/src/main/resources/static/images/color.png
new file mode 100644
index 00000000..80e72e67
Binary files /dev/null and b/src/main/resources/static/images/color.png differ
diff --git a/src/main/resources/static/images/remove_box.png b/src/main/resources/static/images/remove_box.png
new file mode 100644
index 00000000..1c42a35b
Binary files /dev/null and b/src/main/resources/static/images/remove_box.png differ
diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/widget/ColorSelectionTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/widget/ColorSelectionTest.java
new file mode 100644
index 00000000..495eb9da
--- /dev/null
+++ b/src/test/java/ch/ethz/seb/sebserver/gui/widget/ColorSelectionTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package ch.ethz.seb.sebserver.gui.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.swt.graphics.RGB;
+import org.junit.Test;
+
+public class ColorSelectionTest {
+
+ @Test
+ public void testParseRGB() {
+ String colorString = "FFFFFF";
+ assertEquals(
+ "RGB {255, 255, 255}",
+ ColorSelection.parseRGB(colorString).toString());
+
+ colorString = "FFaa34";
+ assertEquals(
+ "RGB {255, 170, 52}",
+ ColorSelection.parseRGB(colorString).toString());
+ }
+
+ @Test
+ public void testParseColorString() {
+ final RGB color = new RGB(255, 255, 255);
+ assertEquals("ffffff", ColorSelection.parseColorString(color));
+
+ }
+
+}