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 e4180d08..e24aa2c6 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
@@ -1,287 +1,294 @@
-/*
- * Copyright (c) 2018 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.exam;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Map;
-
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.joda.time.LocalDateTime;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.model.Domain;
-import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
-import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
-import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-
-public final class QuizData implements GrantEntity {
-
- public static final String FILTER_ATTR_START_TIME = "start_timestamp";
-
- public static final String ATTR_ADDITIONAL_ATTRIBUTES = "ADDITIONAL_ATTRIBUTES";
-
- public static final String QUIZ_ATTR_ID = "quiz_id";
- public static final String QUIZ_ATTR_INSTITUION_ID = Domain.EXAM.ATTR_INSTITUTION_ID;
- public static final String QUIZ_ATTR_LMS_SETUP_ID = "lms_setup_id";
- public static final String QUIZ_ATTR_LMS_TYPE = "lms_setup_type";
- public static final String QUIZ_ATTR_NAME = "quiz_name";
- public static final String QUIZ_ATTR_DESCRIPTION = "quiz_description";
- public static final String QUIZ_ATTR_START_TIME = "quiz_start_time";
- public static final String QUIZ_ATTR_END_TIME = "quiz_end_time";
- public static final String QUIZ_ATTR_START_URL = "quiz_start_url";
-
- @JsonProperty(QUIZ_ATTR_ID)
- public final String id;
-
- @JsonProperty(QUIZ_ATTR_INSTITUION_ID)
- public final Long institutionId;
-
- @JsonProperty(QUIZ_ATTR_LMS_SETUP_ID)
- public final Long lmsSetupId;
-
- @JsonProperty(QUIZ_ATTR_LMS_TYPE)
- public final LmsType lmsType;
-
- @JsonProperty(QUIZ_ATTR_NAME)
- public final String name;
-
- @JsonProperty(QUIZ_ATTR_DESCRIPTION)
- public final String description;
-
- @JsonProperty(QUIZ_ATTR_START_TIME)
- public final DateTime startTime;
-
- @JsonProperty(QUIZ_ATTR_END_TIME)
- public final DateTime endTime;
-
- @JsonProperty(QUIZ_ATTR_START_URL)
- public final String startURL;
-
- @JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES)
- public final Map additionalAttributes;
-
- @JsonCreator
- public QuizData(
- @JsonProperty(QUIZ_ATTR_ID) final String id,
- @JsonProperty(QUIZ_ATTR_INSTITUION_ID) final Long institutionId,
- @JsonProperty(QUIZ_ATTR_LMS_SETUP_ID) final Long lmsSetupId,
- @JsonProperty(QUIZ_ATTR_LMS_TYPE) final LmsType lmsType,
- @JsonProperty(QUIZ_ATTR_NAME) final String name,
- @JsonProperty(QUIZ_ATTR_DESCRIPTION) final String description,
- @JsonProperty(QUIZ_ATTR_START_TIME) final DateTime startTime,
- @JsonProperty(QUIZ_ATTR_END_TIME) final DateTime endTime,
- @JsonProperty(QUIZ_ATTR_START_URL) final String startURL,
- @JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES) final Map additionalAttributes) {
-
- this.id = id;
- this.institutionId = institutionId;
- this.lmsSetupId = lmsSetupId;
- this.lmsType = lmsType;
- this.name = name;
- this.description = description;
- this.startTime = startTime;
- this.endTime = endTime;
- this.startURL = startURL;
- this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
- }
-
- public QuizData(
- final String id,
- final Long institutionId,
- final Long lmsSetupId,
- final LmsType lmsType,
- final String name,
- final String description,
- final String startTime,
- final String endTime,
- final String startURL) {
-
- this(id, institutionId, lmsSetupId, lmsType, name, description,
- startTime, endTime, startURL, Collections.emptyMap());
- }
-
- public QuizData(
- final String id,
- final Long institutionId,
- final Long lmsSetupId,
- final LmsType lmsType,
- final String name,
- final String description,
- final String startTime,
- final String endTime,
- final String startURL,
- final Map additionalAttributes) {
-
- this.id = id;
- this.institutionId = institutionId;
- this.lmsSetupId = lmsSetupId;
- this.lmsType = lmsType;
- this.name = name;
- this.description = description;
- 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;
- this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
- }
-
- @Override
- public String getModelId() {
- if (this.id == null) {
- return null;
- }
-
- return String.valueOf(this.id);
- }
-
- @Override
- public EntityType entityType() {
- return EntityType.EXAM;
- }
-
- public String geId() {
- return this.id;
- }
-
- @Override
- public Long getInstitutionId() {
- return this.institutionId;
- }
-
- public Long getLmsSetupId() {
- return this.lmsSetupId;
- }
-
- public LmsType getLmsType() {
- return this.lmsType;
- }
-
- @Override
- public String getName() {
- return this.name;
- }
-
- public String getDescription() {
- return this.description;
- }
-
- public DateTime getStartTime() {
- return this.startTime;
- }
-
- public DateTime getEndTime() {
- return this.endTime;
- }
-
- public String getStartURL() {
- return this.startURL;
- }
-
- public Map getAdditionalAttributes() {
- return this.additionalAttributes;
- }
-
- @Override
- public String toString() {
- final StringBuilder builder = new StringBuilder();
- builder.append("QuizData [id=");
- builder.append(this.id);
- builder.append(", institutionId=");
- builder.append(this.institutionId);
- builder.append(", lmsSetupId=");
- builder.append(this.lmsSetupId);
- builder.append(", lmsType=");
- builder.append(this.lmsType);
- builder.append(", name=");
- builder.append(this.name);
- builder.append(", description=");
- builder.append(this.description);
- builder.append(", startTime=");
- builder.append(this.startTime);
- builder.append(", endTime=");
- builder.append(this.endTime);
- builder.append(", startURL=");
- builder.append(this.startURL);
- builder.append("]");
- return builder.toString();
- }
-
- public static Comparator getIdComparator(final boolean descending) {
- return (qd1, qd2) -> ((qd1 == qd2)
- ? 0
- : (qd1 == null || qd1.id == null)
- ? 1
- : (qd2 == null || qd2.id == null)
- ? -1
- : qd1.id.compareTo(qd2.id))
- * ((descending) ? -1 : 1);
- }
-
- public static Comparator getNameComparator(final boolean descending) {
- return (qd1, qd2) -> ((qd1 == qd2)
- ? 0
- : (qd1 == null || qd1.name == null)
- ? 1
- : (qd2 == null || qd2.name == null)
- ? -1
- : qd1.name.compareTo(qd2.name))
- * ((descending) ? -1 : 1);
- }
-
- public static Comparator getStartTimeComparator(final boolean descending) {
- return (qd1, qd2) -> ((qd1 == qd2)
- ? 0
- : (qd1 == null || qd1.startTime == null)
- ? 1
- : (qd2 == null || qd2.startTime == null)
- ? -1
- : qd1.startTime.compareTo(qd2.startTime))
- * ((descending) ? -1 : 1);
- }
-
- public static Comparator getEndTimeComparator(final boolean descending) {
- return (qd1, qd2) -> ((qd1 == qd2)
- ? 0
- : (qd1 == null || qd1.endTime == null)
- ? 1
- : (qd2 == null || qd2.endTime == null)
- ? -1
- : qd1.endTime.compareTo(qd2.endTime))
- * ((descending) ? -1 : 1);
- }
-
- public static Comparator getComparator(final String sort) {
- final boolean descending = PageSortOrder.getSortOrder(sort) == PageSortOrder.DESCENDING;
- final String sortParam = PageSortOrder.decode(sort);
- if (QUIZ_ATTR_NAME.equals(sortParam)) {
- return getNameComparator(descending);
- } else if (QUIZ_ATTR_START_TIME.equals(sortParam)) {
- return getStartTimeComparator(descending);
- } else if (QUIZ_ATTR_END_TIME.equals(sortParam)) {
- return getEndTimeComparator(descending);
- }
-
- return getIdComparator(descending);
- }
-
-}
+/*
+ * Copyright (c) 2018 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.exam;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDateTime;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
+import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+
+public final class QuizData implements GrantEntity {
+
+ public static final String FILTER_ATTR_START_TIME = "start_timestamp";
+
+ public static final String ATTR_ADDITIONAL_ATTRIBUTES = "ADDITIONAL_ATTRIBUTES";
+
+ public static final String QUIZ_ATTR_ID = "quiz_id";
+ public static final String QUIZ_ATTR_INSTITUTION_ID = Domain.EXAM.ATTR_INSTITUTION_ID;
+ public static final String QUIZ_ATTR_LMS_SETUP_ID = "lms_setup_id";
+ public static final String QUIZ_ATTR_LMS_TYPE = "lms_setup_type";
+ public static final String QUIZ_ATTR_NAME = "quiz_name";
+ public static final String QUIZ_ATTR_DESCRIPTION = "quiz_description";
+ public static final String QUIZ_ATTR_START_TIME = "quiz_start_time";
+ public static final String QUIZ_ATTR_END_TIME = "quiz_end_time";
+ public static final String QUIZ_ATTR_START_URL = "quiz_start_url";
+
+ public static final String ATTR_ADDITIONAL_CREATION_TIME = "time_created";
+ public static final String ATTR_ADDITIONAL_SHORT_NAME = "course_short_name";
+ public static final String ATTR_ADDITIONAL_FULL_NAME = "course_full_name";
+ public static final String ATTR_ADDITIONAL_DISPLAY_NAME = "course_display_name";
+ public static final String ATTR_ADDITIONAL_SUMMARY = "course_summary";
+ public static final String ATTR_ADDITIONAL_TIME_LIMIT = "time_limit";
+
+ @JsonProperty(QUIZ_ATTR_ID)
+ public final String id;
+
+ @JsonProperty(QUIZ_ATTR_INSTITUTION_ID)
+ public final Long institutionId;
+
+ @JsonProperty(QUIZ_ATTR_LMS_SETUP_ID)
+ public final Long lmsSetupId;
+
+ @JsonProperty(QUIZ_ATTR_LMS_TYPE)
+ public final LmsType lmsType;
+
+ @JsonProperty(QUIZ_ATTR_NAME)
+ public final String name;
+
+ @JsonProperty(QUIZ_ATTR_DESCRIPTION)
+ public final String description;
+
+ @JsonProperty(QUIZ_ATTR_START_TIME)
+ public final DateTime startTime;
+
+ @JsonProperty(QUIZ_ATTR_END_TIME)
+ public final DateTime endTime;
+
+ @JsonProperty(QUIZ_ATTR_START_URL)
+ public final String startURL;
+
+ @JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES)
+ public final Map additionalAttributes;
+
+ @JsonCreator
+ public QuizData(
+ @JsonProperty(QUIZ_ATTR_ID) final String id,
+ @JsonProperty(QUIZ_ATTR_INSTITUTION_ID) final Long institutionId,
+ @JsonProperty(QUIZ_ATTR_LMS_SETUP_ID) final Long lmsSetupId,
+ @JsonProperty(QUIZ_ATTR_LMS_TYPE) final LmsType lmsType,
+ @JsonProperty(QUIZ_ATTR_NAME) final String name,
+ @JsonProperty(QUIZ_ATTR_DESCRIPTION) final String description,
+ @JsonProperty(QUIZ_ATTR_START_TIME) final DateTime startTime,
+ @JsonProperty(QUIZ_ATTR_END_TIME) final DateTime endTime,
+ @JsonProperty(QUIZ_ATTR_START_URL) final String startURL,
+ @JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES) final Map additionalAttributes) {
+
+ this.id = id;
+ this.institutionId = institutionId;
+ this.lmsSetupId = lmsSetupId;
+ this.lmsType = lmsType;
+ this.name = name;
+ this.description = description;
+ this.startTime = startTime;
+ this.endTime = endTime;
+ this.startURL = startURL;
+ this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
+ }
+
+ public QuizData(
+ final String id,
+ final Long institutionId,
+ final Long lmsSetupId,
+ final LmsType lmsType,
+ final String name,
+ final String description,
+ final String startTime,
+ final String endTime,
+ final String startURL) {
+
+ this(id, institutionId, lmsSetupId, lmsType, name, description,
+ startTime, endTime, startURL, Collections.emptyMap());
+ }
+
+ public QuizData(
+ final String id,
+ final Long institutionId,
+ final Long lmsSetupId,
+ final LmsType lmsType,
+ final String name,
+ final String description,
+ final String startTime,
+ final String endTime,
+ final String startURL,
+ final Map additionalAttributes) {
+
+ this.id = id;
+ this.institutionId = institutionId;
+ this.lmsSetupId = lmsSetupId;
+ this.lmsType = lmsType;
+ this.name = name;
+ this.description = description;
+ 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;
+ this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
+ }
+
+ @Override
+ public String getModelId() {
+ if (this.id == null) {
+ return null;
+ }
+
+ return String.valueOf(this.id);
+ }
+
+ @Override
+ public EntityType entityType() {
+ return EntityType.EXAM;
+ }
+
+ public String geId() {
+ return this.id;
+ }
+
+ @Override
+ public Long getInstitutionId() {
+ return this.institutionId;
+ }
+
+ public Long getLmsSetupId() {
+ return this.lmsSetupId;
+ }
+
+ public LmsType getLmsType() {
+ return this.lmsType;
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public DateTime getStartTime() {
+ return this.startTime;
+ }
+
+ public DateTime getEndTime() {
+ return this.endTime;
+ }
+
+ public String getStartURL() {
+ return this.startURL;
+ }
+
+ public Map getAdditionalAttributes() {
+ return this.additionalAttributes;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("QuizData [id=");
+ builder.append(this.id);
+ builder.append(", institutionId=");
+ builder.append(this.institutionId);
+ builder.append(", lmsSetupId=");
+ builder.append(this.lmsSetupId);
+ builder.append(", lmsType=");
+ builder.append(this.lmsType);
+ builder.append(", name=");
+ builder.append(this.name);
+ builder.append(", description=");
+ builder.append(this.description);
+ builder.append(", startTime=");
+ builder.append(this.startTime);
+ builder.append(", endTime=");
+ builder.append(this.endTime);
+ builder.append(", startURL=");
+ builder.append(this.startURL);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ public static Comparator getIdComparator(final boolean descending) {
+ return (qd1, qd2) -> ((qd1 == qd2)
+ ? 0
+ : (qd1 == null || qd1.id == null)
+ ? 1
+ : (qd2 == null || qd2.id == null)
+ ? -1
+ : qd1.id.compareTo(qd2.id))
+ * ((descending) ? -1 : 1);
+ }
+
+ public static Comparator getNameComparator(final boolean descending) {
+ return (qd1, qd2) -> ((qd1 == qd2)
+ ? 0
+ : (qd1 == null || qd1.name == null)
+ ? 1
+ : (qd2 == null || qd2.name == null)
+ ? -1
+ : qd1.name.compareTo(qd2.name))
+ * ((descending) ? -1 : 1);
+ }
+
+ public static Comparator getStartTimeComparator(final boolean descending) {
+ return (qd1, qd2) -> ((qd1 == qd2)
+ ? 0
+ : (qd1 == null || qd1.startTime == null)
+ ? 1
+ : (qd2 == null || qd2.startTime == null)
+ ? -1
+ : qd1.startTime.compareTo(qd2.startTime))
+ * ((descending) ? -1 : 1);
+ }
+
+ public static Comparator getEndTimeComparator(final boolean descending) {
+ return (qd1, qd2) -> ((qd1 == qd2)
+ ? 0
+ : (qd1 == null || qd1.endTime == null)
+ ? 1
+ : (qd2 == null || qd2.endTime == null)
+ ? -1
+ : qd1.endTime.compareTo(qd2.endTime))
+ * ((descending) ? -1 : 1);
+ }
+
+ public static Comparator getComparator(final String sort) {
+ final boolean descending = PageSortOrder.getSortOrder(sort) == PageSortOrder.DESCENDING;
+ final String sortParam = PageSortOrder.decode(sort);
+ if (QUIZ_ATTR_NAME.equals(sortParam)) {
+ return getNameComparator(descending);
+ } else if (QUIZ_ATTR_START_TIME.equals(sortParam)) {
+ return getStartTimeComparator(descending);
+ } else if (QUIZ_ATTR_END_TIME.equals(sortParam)) {
+ return getEndTimeComparator(descending);
+ }
+
+ return getIdComparator(descending);
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java
index f8863fff..7cb29e6a 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java
@@ -332,7 +332,9 @@ public final class Utils {
return null;
}
- return text.replace("", "\n");
+ return text
+ .replace("
", "\n")
+ .replace("
", "\n");
}
public static String encodeFormURL_UTF_8(final String value) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java b/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java
index b157382d..7783478b 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/InstitutionalAuthenticationEntryPoint.java
@@ -1,231 +1,231 @@
-/*
- * 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;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.codec.Charsets;
-import org.apache.commons.codec.binary.Base64InputStream;
-import org.apache.commons.lang3.StringUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.ResourceLoader;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.web.AuthenticationEntryPoint;
-import org.springframework.stereotype.Component;
-import org.springframework.util.FileCopyUtils;
-import org.springframework.web.client.RestTemplate;
-
-import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
-import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
-
-@Lazy
-@Component
-@GuiProfile
-public final class InstitutionalAuthenticationEntryPoint implements AuthenticationEntryPoint {
-
- private static final String INST_SUFFIX_ATTRIBUTE = "instSuffix";
-
- private static final Logger log = LoggerFactory.getLogger(InstitutionalAuthenticationEntryPoint.class);
-
- private final String guiEntryPoint;
- private final String defaultLogo;
- private final WebserviceURIService webserviceURIService;
- private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
-
- protected InstitutionalAuthenticationEntryPoint(
- @Value("${sebserver.gui.entrypoint}") final String guiEntryPoint,
- @Value("${sebserver.gui.defaultLogo:" + Constants.NO_NAME + "}") final String defaultLogoFileName,
- final WebserviceURIService webserviceURIService,
- final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
- final ResourceLoader resourceLoader) {
-
- this.guiEntryPoint = guiEntryPoint;
- this.webserviceURIService = webserviceURIService;
- this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
-
- String _defaultLogo = null;
- if (!Constants.NO_NAME.equals(defaultLogoFileName)) {
- try {
-
- final String extension = ImageUploadSelection.SUPPORTED_IMAGE_FILES.stream()
- .filter(ext -> defaultLogoFileName.endsWith(ext))
- .findFirst()
- .orElse(null);
-
- if (extension == null) {
- throw new IllegalArgumentException("Image of type: " + defaultLogoFileName + " not supported");
- }
-
- final Resource resource = resourceLoader.getResource(defaultLogoFileName);
- final Reader reader = new InputStreamReader(
- new Base64InputStream(resource.getInputStream(), true),
- Charsets.UTF_8);
-
- _defaultLogo = FileCopyUtils.copyToString(reader);
-
- } catch (final Exception e) {
- log.warn("Failed to load default logo image from filesystem: {}", defaultLogoFileName);
- _defaultLogo = null;
- }
-
- this.defaultLogo = _defaultLogo;
- } else {
- this.defaultLogo = null;
- }
- }
-
- @Override
- public void commence(
- final HttpServletRequest request,
- final HttpServletResponse response,
- final AuthenticationException authException) throws IOException, ServletException {
-
- final String institutionalEndpoint = extractInstitutionalEndpoint(request);
-
- if (log.isDebugEnabled()) {
- log.debug("No default gui entrypoint requested: {}", institutionalEndpoint);
- }
-
- final String logoImageBase64 = requestLogoImage(institutionalEndpoint);
- if (StringUtils.isNotBlank(logoImageBase64)) {
- request.getSession().setAttribute(API.PARAM_LOGO_IMAGE, logoImageBase64);
- request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, institutionalEndpoint);
- forwardToEntryPoint(request, response, this.guiEntryPoint);
- } else {
- request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
- response.setStatus(HttpStatus.UNAUTHORIZED.value());
- forwardToEntryPoint(request, response, this.guiEntryPoint);
- }
- }
-
- private void forwardToEntryPoint(
- final HttpServletRequest request,
- final HttpServletResponse response,
- final String entryPoint) throws ServletException, IOException {
-
- final RequestDispatcher dispatcher = request
- .getServletContext()
- .getRequestDispatcher(entryPoint);
-
- dispatcher.forward(request, response);
- }
-
- public static String extractInstitutionalEndpoint(final HttpServletRequest request) {
- final String requestURI = request.getRequestURI();
-
- if (log.isDebugEnabled()) {
- log.debug("Trying to verify insitution from requested entrypoint url: {}", requestURI);
- }
-
- try {
- return requestURI.substring(
- requestURI.lastIndexOf(Constants.SLASH) + 1,
- requestURI.length());
- } catch (final Exception e) {
- log.error("Fauled to extract institutional URL suffix: {}", e.getMessage());
- return null;
- }
- }
-
- public static String extractInstitutionalEndpoint() {
- try {
- final Object attribute = RWT.getUISession().getHttpSession().getAttribute(INST_SUFFIX_ATTRIBUTE);
- return (attribute != null) ? String.valueOf(attribute) : null;
- } catch (final Exception e) {
- log.warn("Failed to extract institutional endpoint form user session: {}", e.getMessage());
- return null;
- }
- }
-
- private String requestLogoImage(final String institutionalEndpoint) {
- if (StringUtils.isBlank(institutionalEndpoint)) {
- return this.defaultLogo;
- }
-
- try {
-
- final RestTemplate restTemplate = new RestTemplate();
-
- final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
- .getClientHttpRequestFactory()
- .getOrThrow();
-
- restTemplate.setRequestFactory(clientHttpRequestFactory);
-
- final ResponseEntity exchange = restTemplate
- .exchange(
- this.webserviceURIService.getURIBuilder()
- .path(API.INFO_ENDPOINT + API.INSTITUTIONAL_LOGO_PATH)
- .toUriString(),
- HttpMethod.GET,
- HttpEntity.EMPTY,
- String.class,
- institutionalEndpoint);
-
- if (exchange.getStatusCodeValue() == HttpStatus.OK.value()) {
- return exchange.getBody();
- } else {
- log.warn("Failed to verify insitution from requested entrypoint url: {}, response: {}",
- institutionalEndpoint,
- exchange);
- }
- } catch (final Exception e) {
- log.warn("Failed to verify insitution from requested entrypoint url: {}",
- institutionalEndpoint,
- e);
- }
-
- return null;
- }
-
- /** TODO this seems not to work as expected. Different Theme is only possible in RAP on different
- * entry-points and since entry-points are statically defined within the RAPConficuration
- * there is no possibility to apply them dynamically within an institution so far.
- *
- * @param institutionalEndpoint
- * @return */
-// private boolean initInstitutionalBasedThemeEntryPoint(final String institutionalEndpoint) {
-// try {
-// final ApplicationContextImpl appContext = (ApplicationContextImpl) RWT.getApplicationContext();
-// final Map properties = new HashMap<>();
-// properties.put(WebClient.THEME_ID, "sms");
-// appContext.getEntryPointManager().register(
-// institutionalEndpoint,
-// new RAPSpringEntryPointFactory(),
-// properties);
-//
-// return true;
-// } catch (final Exception e) {
-// log.warn("Failed to dynamically set entry point for institution: {}", institutionalEndpoint, e);
-// return false;
-// }
-// }
-
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.Charsets;
+import org.apache.commons.codec.binary.Base64InputStream;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.client.RestTemplate;
+
+import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
+import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
+
+@Lazy
+@Component
+@GuiProfile
+public final class InstitutionalAuthenticationEntryPoint implements AuthenticationEntryPoint {
+
+ private static final String INST_SUFFIX_ATTRIBUTE = "instSuffix";
+
+ private static final Logger log = LoggerFactory.getLogger(InstitutionalAuthenticationEntryPoint.class);
+
+ private final String guiEntryPoint;
+ private final String defaultLogo;
+ private final WebserviceURIService webserviceURIService;
+ private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
+
+ protected InstitutionalAuthenticationEntryPoint(
+ @Value("${sebserver.gui.entrypoint}") final String guiEntryPoint,
+ @Value("${sebserver.gui.defaultLogo:" + Constants.NO_NAME + "}") final String defaultLogoFileName,
+ final WebserviceURIService webserviceURIService,
+ final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
+ final ResourceLoader resourceLoader) {
+
+ this.guiEntryPoint = guiEntryPoint;
+ this.webserviceURIService = webserviceURIService;
+ this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
+
+ String _defaultLogo = null;
+ if (!Constants.NO_NAME.equals(defaultLogoFileName)) {
+ try {
+
+ final String extension = ImageUploadSelection.SUPPORTED_IMAGE_FILES.stream()
+ .filter(ext -> defaultLogoFileName.endsWith(ext))
+ .findFirst()
+ .orElse(null);
+
+ if (extension == null) {
+ throw new IllegalArgumentException("Image of type: " + defaultLogoFileName + " not supported");
+ }
+
+ final Resource resource = resourceLoader.getResource(defaultLogoFileName);
+ final Reader reader = new InputStreamReader(
+ new Base64InputStream(resource.getInputStream(), true),
+ Charsets.UTF_8);
+
+ _defaultLogo = FileCopyUtils.copyToString(reader);
+
+ } catch (final Exception e) {
+ log.warn("Failed to load default logo image from filesystem: {}", defaultLogoFileName);
+ _defaultLogo = null;
+ }
+
+ this.defaultLogo = _defaultLogo;
+ } else {
+ this.defaultLogo = null;
+ }
+ }
+
+ @Override
+ public void commence(
+ final HttpServletRequest request,
+ final HttpServletResponse response,
+ final AuthenticationException authException) throws IOException, ServletException {
+
+ final String institutionalEndpoint = extractInstitutionalEndpoint(request);
+
+ if (log.isDebugEnabled()) {
+ log.debug("No default gui entrypoint requested: {}", institutionalEndpoint);
+ }
+
+ final String logoImageBase64 = requestLogoImage(institutionalEndpoint);
+ if (StringUtils.isNotBlank(logoImageBase64)) {
+ request.getSession().setAttribute(API.PARAM_LOGO_IMAGE, logoImageBase64);
+ request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, institutionalEndpoint);
+ forwardToEntryPoint(request, response, this.guiEntryPoint);
+ } else {
+ request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ forwardToEntryPoint(request, response, this.guiEntryPoint);
+ }
+ }
+
+ private void forwardToEntryPoint(
+ final HttpServletRequest request,
+ final HttpServletResponse response,
+ final String entryPoint) throws ServletException, IOException {
+
+ final RequestDispatcher dispatcher = request
+ .getServletContext()
+ .getRequestDispatcher(entryPoint);
+
+ dispatcher.forward(request, response);
+ }
+
+ public static String extractInstitutionalEndpoint(final HttpServletRequest request) {
+ final String requestURI = request.getRequestURI();
+
+ if (log.isDebugEnabled()) {
+ log.debug("Trying to verify institution from requested entrypoint url: {}", requestURI);
+ }
+
+ try {
+ return requestURI.substring(
+ requestURI.lastIndexOf(Constants.SLASH) + 1,
+ requestURI.length());
+ } catch (final Exception e) {
+ log.error("Failed to extract institutional URL suffix: {}", e.getMessage());
+ return null;
+ }
+ }
+
+ public static String extractInstitutionalEndpoint() {
+ try {
+ final Object attribute = RWT.getUISession().getHttpSession().getAttribute(INST_SUFFIX_ATTRIBUTE);
+ return (attribute != null) ? String.valueOf(attribute) : null;
+ } catch (final Exception e) {
+ log.warn("Failed to extract institutional endpoint form user session: {}", e.getMessage());
+ return null;
+ }
+ }
+
+ private String requestLogoImage(final String institutionalEndpoint) {
+ if (StringUtils.isBlank(institutionalEndpoint)) {
+ return this.defaultLogo;
+ }
+
+ try {
+
+ final RestTemplate restTemplate = new RestTemplate();
+
+ final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
+ .getClientHttpRequestFactory()
+ .getOrThrow();
+
+ restTemplate.setRequestFactory(clientHttpRequestFactory);
+
+ final ResponseEntity exchange = restTemplate
+ .exchange(
+ this.webserviceURIService.getURIBuilder()
+ .path(API.INFO_ENDPOINT + API.INSTITUTIONAL_LOGO_PATH)
+ .toUriString(),
+ HttpMethod.GET,
+ HttpEntity.EMPTY,
+ String.class,
+ institutionalEndpoint);
+
+ if (exchange.getStatusCodeValue() == HttpStatus.OK.value()) {
+ return exchange.getBody();
+ } else {
+ log.warn("Failed to verify insitution from requested entrypoint url: {}, response: {}",
+ institutionalEndpoint,
+ exchange);
+ }
+ } catch (final Exception e) {
+ log.warn("Failed to verify insitution from requested entrypoint url: {}",
+ institutionalEndpoint,
+ e);
+ }
+
+ return null;
+ }
+
+ /** TODO this seems not to work as expected. Different Theme is only possible in RAP on different
+ * entry-points and since entry-points are statically defined within the RAPConficuration
+ * there is no possibility to apply them dynamically within an institution so far.
+ *
+ * @param institutionalEndpoint
+ * @return */
+// private boolean initInstitutionalBasedThemeEntryPoint(final String institutionalEndpoint) {
+// try {
+// final ApplicationContextImpl appContext = (ApplicationContextImpl) RWT.getApplicationContext();
+// final Map properties = new HashMap<>();
+// properties.put(WebClient.THEME_ID, "sms");
+// appContext.getEntryPointManager().register(
+// institutionalEndpoint,
+// new RAPSpringEntryPointFactory(),
+// properties);
+//
+// return true;
+// } catch (final Exception e) {
+// log.warn("Failed to dynamically set entry point for institution: {}", institutionalEndpoint, e);
+// return false;
+// }
+// }
+
}
\ No newline at end of file
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 583c1213..72898664 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
@@ -14,6 +14,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
@@ -117,6 +118,8 @@ public class ExamForm implements TemplateComposer {
private final static LocTextKey CONFIG_LIST_TITLE_KEY =
new LocTextKey("sebserver.exam.configuration.list.title");
+ private final static LocTextKey CONFIG_LIST_TITLE_TOOLTIP_KEY =
+ new LocTextKey("sebserver.exam.configuration.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private final static LocTextKey CONFIG_NAME_COLUMN_KEY =
new LocTextKey("sebserver.exam.configuration.list.column.name");
private final static LocTextKey CONFIG_DESCRIPTION_COLUMN_KEY =
@@ -128,6 +131,8 @@ public class ExamForm implements TemplateComposer {
private final static LocTextKey INDICATOR_LIST_TITLE_KEY =
new LocTextKey("sebserver.exam.indicator.list.title");
+ private final static LocTextKey INDICATOR_LIST_TITLE_TOOLTIP_KEY =
+ new LocTextKey("sebserver.exam.indicator.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private final static LocTextKey INDICATOR_TYPE_COLUMN_KEY =
new LocTextKey("sebserver.exam.indicator.list.column.type");
private final static LocTextKey INDICATOR_NAME_COLUMN_KEY =
@@ -326,11 +331,12 @@ public class ExamForm implements TemplateComposer {
.addField(FormBuilder.singleSelection(
Domain.EXAM.ATTR_TYPE,
FORM_TYPE_TEXT_KEY,
- String.valueOf(exam.type),
+ (exam.type != null) ? String.valueOf(exam.type) : Exam.ExamType.UNDEFINED.name(),
this.resourceService::examTypeResources)
.withLabelSpan(2)
.withInputSpan(4)
- .withEmptyCellSpan(2))
+ .withEmptyCellSpan(2)
+ .mandatory(!readonly))
.addField(FormBuilder.multiComboSelection(
Domain.EXAM.ATTR_SUPPORTER,
@@ -339,7 +345,8 @@ public class ExamForm implements TemplateComposer {
this.resourceService::examSupporterResources)
.withLabelSpan(2)
.withInputSpan(4)
- .withEmptyCellSpan(2))
+ .withEmptyCellSpan(2)
+ .mandatory(!readonly))
.buildFor(importFromQuizData
? this.restService.getRestCall(ImportAsExam.class)
@@ -398,7 +405,8 @@ public class ExamForm implements TemplateComposer {
this.widgetFactory.labelLocalized(
content,
CustomVariant.TEXT_H3,
- CONFIG_LIST_TITLE_KEY);
+ CONFIG_LIST_TITLE_KEY,
+ CONFIG_LIST_TITLE_TOOLTIP_KEY);
this.widgetFactory.labelSeparator(content);
final EntityTable configurationTable =
@@ -428,6 +436,13 @@ public class ExamForm implements TemplateComposer {
() -> modifyGrant,
this::viewExamConfigPageAction)
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ pageContext,
+ ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP,
+ ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST,
+ ActionDefinition.EXAM_CONFIGURATION_EXPORT,
+ ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY))
+
.compose(pageContext.copyOf(content));
final EntityKey configMapKey = (configurationTable.hasAnyContent())
@@ -447,7 +462,7 @@ public class ExamForm implements TemplateComposer {
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
.withParentEntityKey(entityKey)
.withEntityKey(configMapKey)
- .publishIf(() -> modifyGrant && configurationTable.hasAnyContent())
+ .publishIf(() -> modifyGrant && configurationTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
.withEntityKey(entityKey)
@@ -461,7 +476,7 @@ public class ExamForm implements TemplateComposer {
}
return null;
})
- .publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable)
+ .publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable, false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
.withParentEntityKey(entityKey)
@@ -470,7 +485,7 @@ public class ExamForm implements TemplateComposer {
this::downloadExamConfigAction,
CONFIG_EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation()
- .publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent())
+ .publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
.withSelect(
@@ -478,14 +493,15 @@ public class ExamForm implements TemplateComposer {
this::getExamConfigKey,
CONFIG_EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation()
- .publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent());
+ .publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent(), false);
// List of Indicators
- this.widgetFactory.label(content, "");
+ this.widgetFactory.label(content, StringUtils.EMPTY);
this.widgetFactory.labelLocalized(
content,
CustomVariant.TEXT_H3,
- INDICATOR_LIST_TITLE_KEY);
+ INDICATOR_LIST_TITLE_KEY,
+ INDICATOR_LIST_TITLE_TOOLTIP_KEY);
this.widgetFactory.labelSeparator(content);
final EntityTable indicatorTable =
@@ -520,6 +536,11 @@ public class ExamForm implements TemplateComposer {
.withParentEntityKey(entityKey)
.create())
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ pageContext,
+ ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST,
+ ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST))
+
.compose(pageContext.copyOf(content));
actionBuilder
@@ -534,7 +555,7 @@ public class ExamForm implements TemplateComposer {
indicatorTable::getSelection,
PageAction::applySingleSelectionAsEntityKey,
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
- .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent())
+ .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
.withEntityKey(entityKey)
@@ -542,7 +563,7 @@ public class ExamForm implements TemplateComposer {
indicatorTable::getSelection,
this::deleteSelectedIndicator,
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
- .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent());
+ .publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false);
}
}
@@ -560,7 +581,7 @@ public class ExamForm implements TemplateComposer {
processFormSave,
true,
this.restService,
- t -> log.error("Failed to intially restrict the course for SEB on LMS: {}", t.getMessage()));
+ t -> log.error("Failed to initially restrict the course for SEB on LMS: {}", t.getMessage()));
}
return processFormSave;
@@ -589,7 +610,7 @@ public class ExamForm implements TemplateComposer {
result
.stream()
.map(message -> this.consistencyMessageMapping.get(message.messageCode))
- .filter(message -> message != null)
+ .filter(Objects::nonNull)
.forEach(message -> this.widgetFactory.labelLocalized(
warningPanel,
CustomVariant.MESSAGE,
@@ -699,7 +720,7 @@ public class ExamForm implements TemplateComposer {
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.withQueryParam(QuizData.QUIZ_ATTR_LMS_SETUP_ID, parentEntityKey.modelId)
.call()
- .map(quizzData -> new Exam(quizzData))
+ .map(Exam::new)
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error));
}
@@ -735,7 +756,7 @@ public class ExamForm implements TemplateComposer {
.append(")")
.append("")
.append(" | "),
- (sb1, sb2) -> sb1.append(sb2));
+ StringBuilder::append);
builder.delete(builder.length() - 3, builder.length() - 1);
return builder.toString();
}
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 cccdc95b..634296df 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
@@ -1,279 +1,277 @@
-/*
- * 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.content;
-
-import java.util.function.BiConsumer;
-import java.util.function.BooleanSupplier;
-import java.util.function.Function;
-
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.TableItem;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.model.Domain;
-import ch.ethz.seb.sebserver.gbl.model.Entity;
-import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
-import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
-import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
-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.user.UserRole;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-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.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
-import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
-import ch.ethz.seb.sebserver.gui.table.EntityTable;
-import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
-
-@Lazy
-@Component
-@GuiProfile
-public class ExamList implements TemplateComposer {
-
- static final String EXAM_LIST_COLUMN_STARTTIME =
- "sebserver.exam.list.column.starttime";
- static final LocTextKey PAGE_TITLE_KEY =
- new LocTextKey("sebserver.exam.list.title");
- static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
- new LocTextKey("sebserver.exam.list.action.no.modify.privilege");
- final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
- new LocTextKey("sebserver.exam.info.pleaseSelect");
- final static LocTextKey COLUMN_TITLE_INSTITUTION_KEY =
- new LocTextKey("sebserver.exam.list.column.institution");
- final static LocTextKey COLUMN_TITLE_LMS_KEY =
- new LocTextKey("sebserver.exam.list.column.lmssetup");
- final static LocTextKey COLUMN_TITLE_NAME_KEY =
- new LocTextKey("sebserver.exam.list.column.name");
- final static LocTextKey COLUMN_TITLE_TYPE_KEY =
- new LocTextKey("sebserver.exam.list.column.type");
- final static LocTextKey NO_MODIFY_OF_OUT_DATED_EXAMS =
- new LocTextKey("sebserver.exam.list.modify.out.dated");
- final static LocTextKey EMPTY_LIST_TEXT_KEY =
- new LocTextKey("sebserver.exam.list.empty");
-
- private final TableFilterAttribute institutionFilter;
- private final TableFilterAttribute lmsFilter;
- private final TableFilterAttribute nameFilter =
- new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
- private final TableFilterAttribute typeFilter;
-
- private final PageService pageService;
- private final ResourceService resourceService;
- private final int pageSize;
-
- protected ExamList(
- final PageService pageService,
- final ResourceService resourceService,
- @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
-
- this.pageService = pageService;
- this.resourceService = resourceService;
- this.pageSize = pageSize;
-
- this.institutionFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- Entity.FILTER_ATTR_INSTITUTION,
- this.resourceService::institutionResource);
-
- this.lmsFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- LmsSetup.FILTER_ATTR_LMS_SETUP,
- this.resourceService::lmsSetupResource);
-
- this.typeFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- Exam.FILTER_ATTR_TYPE,
- this.resourceService::examTypeResources);
- }
-
- @Override
- public void compose(final PageContext pageContext) {
-
- final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
- final CurrentUser currentUser = this.resourceService.getCurrentUser();
- final RestService restService = this.resourceService.getRestService();
- final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
-
- // content page layout with title
- final Composite content = widgetFactory.defaultPageLayout(
- pageContext.getParent(),
- PAGE_TITLE_KEY);
-
- final PageActionBuilder actionBuilder = this.pageService
- .pageActionBuilder(pageContext.clearEntityKeys());
-
- final BooleanSupplier isSebAdmin =
- () -> currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
-
- final Function institutionNameFunction =
- this.resourceService.getInstitutionNameFunction();
-
- // table
- final EntityTable table =
- this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
- .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
- .withPaging(this.pageSize)
- .withRowDecorator(decorateOnExamConsistency(this.pageService))
-
- .withStaticFilter(Exam.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
-
- .withColumnIf(
- isSebAdmin,
- () -> new ColumnDefinition(
- Domain.EXAM.ATTR_INSTITUTION_ID,
- COLUMN_TITLE_INSTITUTION_KEY,
- exam -> institutionNameFunction
- .apply(String.valueOf(exam.getInstitutionId())))
- .withFilter(this.institutionFilter))
-
- .withColumn(new ColumnDefinition<>(
- Domain.EXAM.ATTR_LMS_SETUP_ID,
- COLUMN_TITLE_LMS_KEY,
- examLmsSetupNameFunction(this.resourceService))
- .withFilter(this.lmsFilter)
- .sortable())
-
- .withColumn(new ColumnDefinition<>(
- QuizData.QUIZ_ATTR_NAME,
- COLUMN_TITLE_NAME_KEY,
- Exam::getName)
- .withFilter(this.nameFilter)
- .sortable())
-
- .withColumn(new ColumnDefinition<>(
- QuizData.QUIZ_ATTR_START_TIME,
- new LocTextKey(
- EXAM_LIST_COLUMN_STARTTIME,
- i18nSupport.getUsersTimeZoneTitleSuffix()),
- Exam::getStartTime)
- .withFilter(new TableFilterAttribute(
- CriteriaType.DATE,
- QuizData.FILTER_ATTR_START_TIME,
- Utils.toDateTimeUTC(Utils.getMillisecondsNow())
- .minusYears(1)
- .toString()))
- .sortable())
-
- .withColumn(new ColumnDefinition(
- Domain.EXAM.ATTR_TYPE,
- COLUMN_TITLE_TYPE_KEY,
- this.resourceService::localizedExamTypeName)
- .withFilter(this.typeFilter)
- .sortable())
-
- .withDefaultAction(actionBuilder
- .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
- .create())
-
- .compose(pageContext.copyOf(content));
-
- // propagate content actions to action-pane
- final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
- actionBuilder
-
-// Removed as discussed in SEBSERV-52
-// .newAction(ActionDefinition.EXAM_IMPORT)
-// .publishIf(userGrant::im)
-
- .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
- .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
- .publishIf(table::hasAnyContent)
-
- .newAction(ActionDefinition.EXAM_MODIFY_FROM_LIST)
- .withSelect(
- table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
- action -> modifyExam(action, table),
- EMPTY_SELECTION_TEXT_KEY)
- .publishIf(() -> userGrant.im() && table.hasAnyContent());
-
- }
-
- static final PageAction modifyExam(final PageAction action, final EntityTable table) {
- final Exam exam = table.getSingleSelectedROWData();
-
- if (exam == null) {
- throw new PageMessageException(EMPTY_SELECTION_TEXT_KEY);
- }
-
- if (exam.endTime != null) {
- final DateTime now = DateTime.now(DateTimeZone.UTC);
- if (exam.endTime.isBefore(now)) {
- throw new PageMessageException(NO_MODIFY_OF_OUT_DATED_EXAMS);
- }
- }
-
- return action.withEntityKey(action.getSingleSelection());
- }
-
- static final BiConsumer decorateOnExamMapConsistency(
- final PageService pageService) {
-
- return (item, examMap) -> {
- pageService.getRestService().getBuilder(GetExam.class)
- .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examMap.examId))
- .call()
- .ifPresent(exam -> decorateOnExamConsistency(item, exam, pageService));
- };
- }
-
- static final BiConsumer decorateOnExamConsistency(final PageService pageService) {
- return (item, exam) -> decorateOnExamConsistency(item, exam, pageService);
- }
-
- static final void decorateOnExamConsistency(
- final TableItem item,
- final Exam exam,
- final PageService pageService) {
-
- if (exam.getStatus() != ExamStatus.RUNNING) {
- return;
- }
-
- pageService.getRestService().getBuilder(CheckExamConsistency.class)
- .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
- .call()
- .ifPresent(warnings -> {
- if (warnings != null && !warnings.isEmpty()) {
- item.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key);
- }
- });
- }
-
- private static Function examLmsSetupNameFunction(final ResourceService resourceService) {
- return exam -> resourceService.getLmsSetupNameFunction()
- .apply(String.valueOf(exam.lmsSetupId));
- }
-
-}
+/*
+ * 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.content;
+
+import java.util.function.BiConsumer;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.TableItem;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.Entity;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
+import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
+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.user.UserRole;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+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.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
+import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
+
+@Lazy
+@Component
+@GuiProfile
+public class ExamList implements TemplateComposer {
+
+ static final String EXAM_LIST_COLUMN_STARTTIME =
+ "sebserver.exam.list.column.starttime";
+ static final LocTextKey PAGE_TITLE_KEY =
+ new LocTextKey("sebserver.exam.list.title");
+ static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
+ new LocTextKey("sebserver.exam.list.action.no.modify.privilege");
+ final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
+ new LocTextKey("sebserver.exam.info.pleaseSelect");
+ final static LocTextKey COLUMN_TITLE_INSTITUTION_KEY =
+ new LocTextKey("sebserver.exam.list.column.institution");
+ final static LocTextKey COLUMN_TITLE_LMS_KEY =
+ new LocTextKey("sebserver.exam.list.column.lmssetup");
+ final static LocTextKey COLUMN_TITLE_NAME_KEY =
+ new LocTextKey("sebserver.exam.list.column.name");
+ final static LocTextKey COLUMN_TITLE_TYPE_KEY =
+ new LocTextKey("sebserver.exam.list.column.type");
+ final static LocTextKey NO_MODIFY_OF_OUT_DATED_EXAMS =
+ new LocTextKey("sebserver.exam.list.modify.out.dated");
+ final static LocTextKey EMPTY_LIST_TEXT_KEY =
+ new LocTextKey("sebserver.exam.list.empty");
+
+ private final TableFilterAttribute institutionFilter;
+ private final TableFilterAttribute lmsFilter;
+ private final TableFilterAttribute nameFilter =
+ new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
+ private final TableFilterAttribute typeFilter;
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final int pageSize;
+
+ protected ExamList(
+ final PageService pageService,
+ final ResourceService resourceService,
+ @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
+
+ this.pageService = pageService;
+ this.resourceService = resourceService;
+ this.pageSize = pageSize;
+
+ this.institutionFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ Entity.FILTER_ATTR_INSTITUTION,
+ this.resourceService::institutionResource);
+
+ this.lmsFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ LmsSetup.FILTER_ATTR_LMS_SETUP,
+ this.resourceService::lmsSetupResource);
+
+ this.typeFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ Exam.FILTER_ATTR_TYPE,
+ this.resourceService::examTypeResources);
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+
+ final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
+ final CurrentUser currentUser = this.resourceService.getCurrentUser();
+ final RestService restService = this.resourceService.getRestService();
+ final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
+
+ // content page layout with title
+ final Composite content = widgetFactory.defaultPageLayout(
+ pageContext.getParent(),
+ PAGE_TITLE_KEY);
+
+ final PageActionBuilder actionBuilder = this.pageService
+ .pageActionBuilder(pageContext.clearEntityKeys());
+
+ final BooleanSupplier isSebAdmin =
+ () -> currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
+
+ final Function institutionNameFunction =
+ this.resourceService.getInstitutionNameFunction();
+
+ // table
+ final EntityTable table =
+ this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
+ .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
+ .withPaging(this.pageSize)
+ .withRowDecorator(decorateOnExamConsistency(this.pageService))
+
+ .withStaticFilter(Exam.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING)
+
+ .withColumnIf(
+ isSebAdmin,
+ () -> new ColumnDefinition(
+ Domain.EXAM.ATTR_INSTITUTION_ID,
+ COLUMN_TITLE_INSTITUTION_KEY,
+ exam -> institutionNameFunction
+ .apply(String.valueOf(exam.getInstitutionId())))
+ .withFilter(this.institutionFilter))
+
+ .withColumn(new ColumnDefinition<>(
+ Domain.EXAM.ATTR_LMS_SETUP_ID,
+ COLUMN_TITLE_LMS_KEY,
+ examLmsSetupNameFunction(this.resourceService))
+ .withFilter(this.lmsFilter)
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_NAME,
+ COLUMN_TITLE_NAME_KEY,
+ Exam::getName)
+ .withFilter(this.nameFilter)
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_START_TIME,
+ new LocTextKey(
+ EXAM_LIST_COLUMN_STARTTIME,
+ i18nSupport.getUsersTimeZoneTitleSuffix()),
+ Exam::getStartTime)
+ .withFilter(new TableFilterAttribute(
+ CriteriaType.DATE,
+ QuizData.FILTER_ATTR_START_TIME,
+ Utils.toDateTimeUTC(Utils.getMillisecondsNow())
+ .minusYears(1)
+ .toString()))
+ .sortable())
+
+ .withColumn(new ColumnDefinition(
+ Domain.EXAM.ATTR_TYPE,
+ COLUMN_TITLE_TYPE_KEY,
+ this.resourceService::localizedExamTypeName)
+ .withFilter(this.typeFilter)
+ .sortable())
+
+ .withDefaultAction(actionBuilder
+ .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
+ .create())
+
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ pageContext,
+ ActionDefinition.EXAM_VIEW_FROM_LIST,
+ ActionDefinition.EXAM_MODIFY_FROM_LIST))
+
+ .compose(pageContext.copyOf(content));
+
+ // propagate content actions to action-pane
+ final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
+ actionBuilder
+ .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
+ .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
+ .publishIf(table::hasAnyContent, false)
+
+ .newAction(ActionDefinition.EXAM_MODIFY_FROM_LIST)
+ .withSelect(
+ table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
+ action -> modifyExam(action, table),
+ EMPTY_SELECTION_TEXT_KEY)
+ .publishIf(() -> userGrant.im() && table.hasAnyContent(), false);
+
+ }
+
+ static PageAction modifyExam(final PageAction action, final EntityTable table) {
+ final Exam exam = table.getSingleSelectedROWData();
+
+ if (exam == null) {
+ throw new PageMessageException(EMPTY_SELECTION_TEXT_KEY);
+ }
+
+ if (exam.endTime != null) {
+ final DateTime now = DateTime.now(DateTimeZone.UTC);
+ if (exam.endTime.isBefore(now)) {
+ throw new PageMessageException(NO_MODIFY_OF_OUT_DATED_EXAMS);
+ }
+ }
+
+ return action.withEntityKey(action.getSingleSelection());
+ }
+
+ static BiConsumer decorateOnExamMapConsistency(
+ final PageService pageService) {
+
+ return (item, examMap) -> pageService.getRestService().getBuilder(GetExam.class)
+ .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(examMap.examId))
+ .call()
+ .ifPresent(exam -> decorateOnExamConsistency(item, exam, pageService));
+ }
+
+ static BiConsumer decorateOnExamConsistency(final PageService pageService) {
+ return (item, exam) -> decorateOnExamConsistency(item, exam, pageService);
+ }
+
+ static void decorateOnExamConsistency(
+ final TableItem item,
+ final Exam exam,
+ final PageService pageService) {
+
+ if (exam.getStatus() != ExamStatus.RUNNING) {
+ return;
+ }
+
+ pageService.getRestService().getBuilder(CheckExamConsistency.class)
+ .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
+ .call()
+ .ifPresent(warnings -> {
+ if (warnings != null && !warnings.isEmpty()) {
+ item.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key);
+ }
+ });
+ }
+
+ private static Function examLmsSetupNameFunction(final ResourceService resourceService) {
+ return exam -> resourceService.getLmsSetupNameFunction()
+ .apply(String.valueOf(exam.lmsSetupId));
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamSebRestrictionSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamSebRestrictionSettings.java
index 47d18675..1be69f8b 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamSebRestrictionSettings.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamSebRestrictionSettings.java
@@ -1,285 +1,297 @@
-/*
- * Copyright (c) 2020 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.content;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-
-import org.apache.commons.lang3.StringUtils;
-import org.eclipse.swt.widgets.Composite;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.model.EntityKey;
-import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSebRestriction;
-import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction;
-import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-import ch.ethz.seb.sebserver.gui.form.Form;
-import ch.ethz.seb.sebserver.gui.form.FormBuilder;
-import ch.ethz.seb.sebserver.gui.form.FormHandle;
-import ch.ethz.seb.sebserver.gui.service.ResourceService;
-import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
-import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
-import ch.ethz.seb.sebserver.gui.service.page.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ActivateSebRestriction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeactivateSebRestriction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSebRestriction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSebRestriction;
-
-public class ExamSebRestrictionSettings {
-
- private final static LocTextKey SEB_RESTRICTION_ERROR =
- new LocTextKey("sebserver.error.exam.seb.restriction");
- private final static LocTextKey SEB_RESTRICTION_FORM_TITLE =
- new LocTextKey("sebserver.exam.action.sebrestriction.details");
-
- private final static LocTextKey SEB_RESTRICTION_FORM_CONFIG_KEYS =
- new LocTextKey("sebserver.exam.form.sebrestriction.configKeys");
- private final static LocTextKey SEB_RESTRICTION_FORM_BROWSER_KEYS =
- new LocTextKey("sebserver.exam.form.sebrestriction.browserExamKeys");
- private final static LocTextKey SEB_RESTRICTION_FORM_EDX_WHITE_LIST_PATHS =
- new LocTextKey("sebserver.exam.form.sebrestriction.WHITELIST_PATHS");
- private final static LocTextKey SEB_RESTRICTION_FORM_EDX_PERMISSIONS =
- new LocTextKey("sebserver.exam.form.sebrestriction.PERMISSION_COMPONENTS");
- private final static LocTextKey SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS =
- new LocTextKey("sebserver.exam.form.sebrestriction.BLACKLIST_CHAPTERS");
- private final static LocTextKey SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED =
- new LocTextKey("sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED");
-
- static final String PAGE_CONTEXT_ATTR_LMS_TYPE = "ATTR_LMS_TYPE";
-
- static Function settingsFunction(final PageService pageService) {
-
- return action -> {
-
- final PageContext pageContext = action.pageContext();
- final ModalInputDialog> dialog =
- new ModalInputDialog>(
- action.pageContext().getParent().getShell(),
- pageService.getWidgetFactory())
- .setDialogWidth(740)
- .setDialogHeight(400);
-
- final SebRestrictionPropertiesForm bindFormContext = new SebRestrictionPropertiesForm(
- pageService,
- action.pageContext());
-
- final Predicate> doBind = formHandle -> doCreate(
- pageService,
- pageContext,
- formHandle);
-
- dialog.open(
- SEB_RESTRICTION_FORM_TITLE,
- doBind,
- Utils.EMPTY_EXECUTION,
- bindFormContext);
-
- return action;
- };
- }
-
- private static final boolean doCreate(
- final PageService pageService,
- final PageContext pageContext,
- final FormHandle> formHandle) {
-
- final EntityKey entityKey = pageContext.getEntityKey();
- final LmsType lmsType = getLmsType(pageContext);
- SebRestriction bodyValue = null;
- try {
- final Form form = formHandle.getForm();
- final Collection browserKeys = Utils.getListOfLines(
- form.getFieldValue(SebRestriction.ATTR_BROWSER_KEYS));
-
- final Map additionalAttributes = new HashMap<>();
- if (lmsType == LmsType.OPEN_EDX) {
- additionalAttributes.put(
- OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
- form.getFieldValue(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS));
- additionalAttributes.put(
- OpenEdxSebRestriction.ATTR_WHITELIST_PATHS,
- form.getFieldValue(OpenEdxSebRestriction.ATTR_WHITELIST_PATHS));
- additionalAttributes.put(
- OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED,
- form.getFieldValue(OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED));
- additionalAttributes.put(
- OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
- Utils.convertCarriageReturnToListSeparator(
- form.getFieldValue(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS)));
- }
-
- bodyValue = new SebRestriction(
- Long.parseLong(entityKey.modelId),
- null,
- browserKeys,
- additionalAttributes);
-
- } catch (final Exception e) {
- e.printStackTrace();
- }
-
- return !pageService
- .getRestService()
- .getBuilder(SaveSebRestriction.class)
- .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
- .withBody(bodyValue)
- .call()
- .onError(formHandle::handleError)
- .hasError();
- }
-
- private static final class SebRestrictionPropertiesForm
- implements ModalInputDialogComposer> {
-
- private final PageService pageService;
- private final PageContext pageContext;
-
- protected SebRestrictionPropertiesForm(
- final PageService pageService,
- final PageContext pageContext) {
-
- this.pageService = pageService;
- this.pageContext = pageContext;
-
- }
-
- @Override
- public Supplier> compose(final Composite parent) {
- final RestService restService = this.pageService.getRestService();
- final ResourceService resourceService = this.pageService.getResourceService();
- final EntityKey entityKey = this.pageContext.getEntityKey();
- final LmsType lmsType = getLmsType(this.pageContext);
-
- final Composite content = this.pageService
- .getWidgetFactory()
- .createPopupScrollComposite(parent);
-
- final SebRestriction sebRestriction = restService
- .getBuilder(GetSebRestriction.class)
- .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
- .call()
- .getOrThrow();
-
- final PageContext formContext = this.pageContext
- .copyOf(content)
- .clearEntityKeys();
-
- final FormHandle formHandle = this.pageService.formBuilder(
- formContext)
- .withDefaultSpanInput(6)
- .withEmptyCellSeparation(false)
- .readonly(false)
-
- .addField(FormBuilder.text(
- SebRestriction.ATTR_CONFIG_KEYS,
- SEB_RESTRICTION_FORM_CONFIG_KEYS,
- StringUtils.join(sebRestriction.getConfigKeys(), Constants.CARRIAGE_RETURN))
- .asArea(50)
- .readonly(true))
-
- .addField(FormBuilder.text(
- SebRestriction.ATTR_BROWSER_KEYS,
- SEB_RESTRICTION_FORM_BROWSER_KEYS,
- StringUtils.join(sebRestriction.getBrowserExamKeys(), Constants.CARRIAGE_RETURN))
- .asArea())
-
- .addFieldIf(
- () -> lmsType == LmsType.OPEN_EDX,
- () -> FormBuilder.multiCheckboxSelection(
- OpenEdxSebRestriction.ATTR_WHITELIST_PATHS,
- SEB_RESTRICTION_FORM_EDX_WHITE_LIST_PATHS,
- sebRestriction.getAdditionalProperties()
- .get(OpenEdxSebRestriction.ATTR_WHITELIST_PATHS),
- () -> resourceService.sebRestrictionWhiteListResources()))
-
- .addFieldIf(
- () -> lmsType == LmsType.OPEN_EDX,
- () -> FormBuilder.multiCheckboxSelection(
- OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
- SEB_RESTRICTION_FORM_EDX_PERMISSIONS,
- sebRestriction.getAdditionalProperties()
- .get(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS),
- () -> resourceService.sebRestrictionPermissionResources()))
-
- .addFieldIf(
- () -> lmsType == LmsType.OPEN_EDX,
- () -> FormBuilder.text(
- OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
- SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS,
- Utils.convertListSeparatorToCarriageReturn(
- sebRestriction
- .getAdditionalProperties()
- .get(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS)))
- .asArea())
-
- .addFieldIf(
- () -> lmsType == LmsType.OPEN_EDX,
- () -> FormBuilder.checkbox(
- OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED,
- SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED,
- sebRestriction
- .getAdditionalProperties()
- .get(OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED)))
-
- .build();
-
- return () -> formHandle;
- }
- }
-
- private static LmsType getLmsType(final PageContext pageContext) {
- try {
- return LmsType.valueOf(pageContext.getAttribute(PAGE_CONTEXT_ATTR_LMS_TYPE));
- } catch (final Exception e) {
- return null;
- }
- }
-
- public static PageAction setSebRestriction(
- final PageAction action,
- final boolean activateRestriction,
- final RestService restService) {
-
- return setSebRestriction(
- action,
- activateRestriction,
- restService,
- error -> action.pageContext().notifyError(SEB_RESTRICTION_ERROR, error));
- }
-
- public static PageAction setSebRestriction(
- final PageAction action,
- final boolean activateRestriction,
- final RestService restService,
- final Consumer errorHandler) {
-
- restService.getBuilder((activateRestriction)
- ? ActivateSebRestriction.class
- : DeactivateSebRestriction.class)
- .withURIVariable(
- API.PARAM_MODEL_ID,
- action.getEntityKey().modelId)
- .call()
- .onError(errorHandler);
-
- return action;
- }
-
-}
+/*
+ * Copyright (c) 2020 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.content;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.swt.widgets.Composite;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSebRestriction;
+import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+import ch.ethz.seb.sebserver.gui.form.Form;
+import ch.ethz.seb.sebserver.gui.form.FormBuilder;
+import ch.ethz.seb.sebserver.gui.form.FormHandle;
+import ch.ethz.seb.sebserver.gui.service.ResourceService;
+import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
+import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ActivateSebRestriction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeactivateSebRestriction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSebRestriction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSebRestriction;
+
+public class ExamSebRestrictionSettings {
+
+ private final static LocTextKey SEB_RESTRICTION_ERROR =
+ new LocTextKey("sebserver.error.exam.seb.restriction");
+ private final static LocTextKey SEB_RESTRICTION_FORM_TITLE =
+ new LocTextKey("sebserver.exam.action.sebrestriction.details");
+
+ private final static LocTextKey SEB_RESTRICTION_FORM_INFO =
+ new LocTextKey("sebserver.exam.form.sebrestriction.info");
+ private final static LocTextKey SEB_RESTRICTION_FORM_INFO_TEXT =
+ new LocTextKey("sebserver.exam.form.sebrestriction.info-text");
+ private final static LocTextKey SEB_RESTRICTION_FORM_CONFIG_KEYS =
+ new LocTextKey("sebserver.exam.form.sebrestriction.configKeys");
+ private final static LocTextKey SEB_RESTRICTION_FORM_BROWSER_KEYS =
+ new LocTextKey("sebserver.exam.form.sebrestriction.browserExamKeys");
+ private final static LocTextKey SEB_RESTRICTION_FORM_EDX_WHITE_LIST_PATHS =
+ new LocTextKey("sebserver.exam.form.sebrestriction.WHITELIST_PATHS");
+ private final static LocTextKey SEB_RESTRICTION_FORM_EDX_PERMISSIONS =
+ new LocTextKey("sebserver.exam.form.sebrestriction.PERMISSION_COMPONENTS");
+ private final static LocTextKey SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS =
+ new LocTextKey("sebserver.exam.form.sebrestriction.BLACKLIST_CHAPTERS");
+ private final static LocTextKey SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED =
+ new LocTextKey("sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED");
+
+ static final String PAGE_CONTEXT_ATTR_LMS_TYPE = "ATTR_LMS_TYPE";
+
+ static Function settingsFunction(final PageService pageService) {
+
+ return action -> {
+
+ final PageContext pageContext = action.pageContext();
+ final ModalInputDialog> dialog =
+ new ModalInputDialog>(
+ action.pageContext().getParent().getShell(),
+ pageService.getWidgetFactory())
+ .setDialogWidth(740)
+ .setDialogHeight(400);
+
+ final SebRestrictionPropertiesForm bindFormContext = new SebRestrictionPropertiesForm(
+ pageService,
+ action.pageContext());
+
+ final Predicate> doBind = formHandle -> doCreate(
+ pageService,
+ pageContext,
+ formHandle);
+
+ dialog.open(
+ SEB_RESTRICTION_FORM_TITLE,
+ doBind,
+ Utils.EMPTY_EXECUTION,
+ bindFormContext);
+
+ return action;
+ };
+ }
+
+ private static boolean doCreate(
+ final PageService pageService,
+ final PageContext pageContext,
+ final FormHandle> formHandle) {
+
+ final EntityKey entityKey = pageContext.getEntityKey();
+ final LmsType lmsType = getLmsType(pageContext);
+ SebRestriction bodyValue = null;
+ try {
+ final Form form = formHandle.getForm();
+ final Collection browserKeys = Utils.getListOfLines(
+ form.getFieldValue(SebRestriction.ATTR_BROWSER_KEYS));
+
+ final Map additionalAttributes = new HashMap<>();
+ if (lmsType == LmsType.OPEN_EDX) {
+ additionalAttributes.put(
+ OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
+ form.getFieldValue(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS));
+ additionalAttributes.put(
+ OpenEdxSebRestriction.ATTR_WHITELIST_PATHS,
+ form.getFieldValue(OpenEdxSebRestriction.ATTR_WHITELIST_PATHS));
+ additionalAttributes.put(
+ OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED,
+ form.getFieldValue(OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED));
+ additionalAttributes.put(
+ OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
+ Utils.convertCarriageReturnToListSeparator(
+ form.getFieldValue(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS)));
+ }
+
+ bodyValue = new SebRestriction(
+ Long.parseLong(entityKey.modelId),
+ null,
+ browserKeys,
+ additionalAttributes);
+
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+
+ return !pageService
+ .getRestService()
+ .getBuilder(SaveSebRestriction.class)
+ .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
+ .withBody(bodyValue)
+ .call()
+ .onError(formHandle::handleError)
+ .hasError();
+ }
+
+ private static final class SebRestrictionPropertiesForm
+ implements ModalInputDialogComposer> {
+
+ private final PageService pageService;
+ private final PageContext pageContext;
+
+ protected SebRestrictionPropertiesForm(
+ final PageService pageService,
+ final PageContext pageContext) {
+
+ this.pageService = pageService;
+ this.pageContext = pageContext;
+
+ }
+
+ @Override
+ public Supplier> compose(final Composite parent) {
+ final RestService restService = this.pageService.getRestService();
+ final ResourceService resourceService = this.pageService.getResourceService();
+ final EntityKey entityKey = this.pageContext.getEntityKey();
+ final LmsType lmsType = getLmsType(this.pageContext);
+
+ final Composite content = this.pageService
+ .getWidgetFactory()
+ .createPopupScrollComposite(parent);
+
+ final SebRestriction sebRestriction = restService
+ .getBuilder(GetSebRestriction.class)
+ .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
+ .call()
+ .getOrThrow();
+
+ final PageContext formContext = this.pageContext
+ .copyOf(content)
+ .clearEntityKeys();
+
+ final FormHandle formHandle = this.pageService.formBuilder(
+ formContext)
+ .withDefaultSpanInput(6)
+ .withEmptyCellSeparation(false)
+ .readonly(false)
+
+ .addField(FormBuilder.text(
+ "Info",
+ SEB_RESTRICTION_FORM_INFO,
+ pageService.getI18nSupport().getText(SEB_RESTRICTION_FORM_INFO_TEXT))
+ .asArea(50)
+ .asHTML()
+ .readonly(true))
+
+ .addField(FormBuilder.text(
+ SebRestriction.ATTR_CONFIG_KEYS,
+ SEB_RESTRICTION_FORM_CONFIG_KEYS,
+ StringUtils.join(sebRestriction.getConfigKeys(), Constants.CARRIAGE_RETURN))
+ .asArea(50)
+ .readonly(true))
+
+ .addField(FormBuilder.text(
+ SebRestriction.ATTR_BROWSER_KEYS,
+ SEB_RESTRICTION_FORM_BROWSER_KEYS,
+ StringUtils.join(sebRestriction.getBrowserExamKeys(), Constants.CARRIAGE_RETURN))
+ .asArea())
+
+ .addFieldIf(
+ () -> lmsType == LmsType.OPEN_EDX,
+ () -> FormBuilder.multiCheckboxSelection(
+ OpenEdxSebRestriction.ATTR_WHITELIST_PATHS,
+ SEB_RESTRICTION_FORM_EDX_WHITE_LIST_PATHS,
+ sebRestriction.getAdditionalProperties()
+ .get(OpenEdxSebRestriction.ATTR_WHITELIST_PATHS),
+ resourceService::sebRestrictionWhiteListResources))
+
+ .addFieldIf(
+ () -> lmsType == LmsType.OPEN_EDX,
+ () -> FormBuilder.multiCheckboxSelection(
+ OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
+ SEB_RESTRICTION_FORM_EDX_PERMISSIONS,
+ sebRestriction.getAdditionalProperties()
+ .get(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS),
+ resourceService::sebRestrictionPermissionResources))
+
+ .addFieldIf(
+ () -> lmsType == LmsType.OPEN_EDX,
+ () -> FormBuilder.text(
+ OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
+ SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS,
+ Utils.convertListSeparatorToCarriageReturn(
+ sebRestriction
+ .getAdditionalProperties()
+ .get(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS)))
+ .asArea())
+
+ .addFieldIf(
+ () -> lmsType == LmsType.OPEN_EDX,
+ () -> FormBuilder.checkbox(
+ OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED,
+ SEB_RESTRICTION_FORM_EDX_USER_BANNING_ENABLED,
+ sebRestriction
+ .getAdditionalProperties()
+ .get(OpenEdxSebRestriction.ATTR_USER_BANNING_ENABLED)))
+
+ .build();
+
+ return () -> formHandle;
+ }
+ }
+
+ private static LmsType getLmsType(final PageContext pageContext) {
+ try {
+ return LmsType.valueOf(pageContext.getAttribute(PAGE_CONTEXT_ATTR_LMS_TYPE));
+ } catch (final Exception e) {
+ return null;
+ }
+ }
+
+ public static PageAction setSebRestriction(
+ final PageAction action,
+ final boolean activateRestriction,
+ final RestService restService) {
+
+ return setSebRestriction(
+ action,
+ activateRestriction,
+ restService,
+ error -> action.pageContext().notifyError(SEB_RESTRICTION_ERROR, error));
+ }
+
+ public static PageAction setSebRestriction(
+ final PageAction action,
+ final boolean activateRestriction,
+ final RestService restService,
+ final Consumer errorHandler) {
+
+ restService.getBuilder((activateRestriction)
+ ? ActivateSebRestriction.class
+ : DeactivateSebRestriction.class)
+ .withURIVariable(
+ API.PARAM_MODEL_ID,
+ action.getEntityKey().modelId)
+ .call()
+ .onError(errorHandler);
+
+ return action;
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamToConfigBindingForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamToConfigBindingForm.java
index 6da33b40..8f46edb4 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamToConfigBindingForm.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamToConfigBindingForm.java
@@ -1,275 +1,276 @@
-/*
- * 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.content;
-
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-
-import org.apache.commons.lang3.StringUtils;
-import org.eclipse.swt.widgets.Composite;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-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.ExamConfigurationMap;
-import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
-import ch.ethz.seb.sebserver.gui.form.Form;
-import ch.ethz.seb.sebserver.gui.form.FormBuilder;
-import ch.ethz.seb.sebserver.gui.form.FormHandle;
-import ch.ethz.seb.sebserver.gui.service.ResourceService;
-import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
-import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
-import ch.ethz.seb.sebserver.gui.service.page.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
-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.GetExamConfigMapping;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamConfigMapping;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamConfigMapping;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
-
-final class ExamToConfigBindingForm {
-
- private static final Logger log = LoggerFactory.getLogger(ExamToConfigBindingForm.class);
-
- private static final LocTextKey NEW_CONFIG_MAPPING_TILE_TEXT_KEY =
- new LocTextKey("sebserver.exam.configuration.form.title.new");
- private static final LocTextKey CONFIG_MAPPING_TILE_TEXT_KEY =
- new LocTextKey("sebserver.exam.configuration.form.title");
- private static final LocTextKey CONFIG_MAPPING_NAME_TEXT_KEY =
- new LocTextKey("sebserver.exam.configuration.form.name");
- private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
- new LocTextKey("sebserver.exam.configuration.form.description");
- private static final LocTextKey FORM_STATUS_TEXT_KEY =
- new LocTextKey("sebserver.exam.configuration.form.status");
- private static final LocTextKey FORM_ENCRYPT_SECRET_TEXT_KEY =
- new LocTextKey("sebserver.exam.configuration.form.encryptSecret");
- private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
- new LocTextKey("sebserver.exam.configuration.form.encryptSecret.confirm");
- private final static LocTextKey CONFIG_ACTION_NO_CONFIG_MESSAGE =
- new LocTextKey("sebserver.exam.configuration.action.noconfig.message");
-
- static Function bindFunction(final PageService pageService) {
-
- return action -> {
-
- final PageContext pageContext = action.pageContext();
- final EntityKey entityKey = pageContext.getEntityKey();
- final boolean isNew = entityKey == null;
-
- if (isNew) {
- final boolean noConfigsAvailable = pageService.getResourceService()
- .examConfigurationSelectionResources()
- .isEmpty();
-
- if (noConfigsAvailable) {
- throw new PageMessageException(CONFIG_ACTION_NO_CONFIG_MESSAGE);
- }
- }
-
- final ModalInputDialog> dialog =
- new ModalInputDialog>(
- action.pageContext().getParent().getShell(),
- pageService.getWidgetFactory())
- .setLargeDialogWidth();
-
- final BindFormContext bindFormContext = new BindFormContext(
- pageService,
- action.pageContext());
-
- final Predicate> doBind = formHandle -> doCreate(
- pageService,
- pageContext,
- formHandle);
-
- // the default page layout
- final LocTextKey titleKey = (isNew)
- ? NEW_CONFIG_MAPPING_TILE_TEXT_KEY
- : CONFIG_MAPPING_TILE_TEXT_KEY;
-
- dialog.open(
- titleKey,
- doBind,
- Utils.EMPTY_EXECUTION,
- bindFormContext);
-
- return action;
- };
- }
-
- private static final boolean doCreate(
- final PageService pageService,
- final PageContext pageContext,
- final FormHandle formHandle) {
-
- final EntityKey entityKey = pageContext.getEntityKey();
- final boolean isNew = entityKey == null;
-
- final Class extends RestCall> restCall = (isNew)
- ? NewExamConfigMapping.class
- : SaveExamConfigMapping.class;
-
- return !pageService
- .getRestService()
- .getBuilder(restCall)
- .withFormBinding(formHandle.getFormBinding())
- .call()
- .onError(formHandle::handleError)
- .map(mapping -> {
- pageService.executePageAction(
- pageService.pageActionBuilder(pageContext.clearEntityKeys())
- .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
- .withEntityKey(pageContext.getParentEntityKey())
- .create());
- return mapping;
- })
- .hasError();
- }
-
- private static final class BindFormContext implements ModalInputDialogComposer> {
-
- private final PageService pageService;
- private final PageContext pageContext;
-
- protected BindFormContext(
- final PageService pageService,
- final PageContext pageContext) {
-
- this.pageService = pageService;
- this.pageContext = pageContext;
-
- }
-
- @Override
- public Supplier> compose(final Composite parent) {
-
- final Composite grid = this.pageService.getWidgetFactory()
- .createPopupScrollComposite(parent);
-
- final RestService restService = this.pageService.getRestService();
- final ResourceService resourceService = this.pageService.getResourceService();
-
- final EntityKey entityKey = this.pageContext.getEntityKey();
- final EntityKey parentEntityKey = this.pageContext.getParentEntityKey();
- final boolean isNew = entityKey == null;
-
- final Exam exam = (isNew)
- ? restService
- .getBuilder(GetExam.class)
- .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
- .call()
- .onError(error -> this.pageContext.notifyLoadError(EntityType.EXAM, error))
- .getOrThrow()
- : null;
-
- // get data or create new. Handle error if happen
- final ExamConfigurationMap examConfigurationMap = (isNew)
- ? ExamConfigurationMap.createNew(exam)
- : restService
- .getBuilder(GetExamConfigMapping.class)
- .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
- .call()
- .onError(error -> this.pageContext.notifyLoadError(
- EntityType.EXAM_CONFIGURATION_MAP,
- error))
- .getOrThrow();
-
- // new PageContext with actual EntityKey
- final PageContext formContext = this.pageContext.withEntityKey(examConfigurationMap.getEntityKey());
-
- final FormHandle formHandle = this.pageService.formBuilder(
- formContext.copyOf(grid))
- .readonly(false)
- .putStaticValueIf(() -> !isNew,
- Domain.EXAM_CONFIGURATION_MAP.ATTR_ID,
- examConfigurationMap.getModelId())
- .putStaticValue(
- Domain.EXAM_CONFIGURATION_MAP.ATTR_INSTITUTION_ID,
- String.valueOf(examConfigurationMap.getInstitutionId()))
- .putStaticValue(
- Domain.EXAM_CONFIGURATION_MAP.ATTR_EXAM_ID,
- String.valueOf(examConfigurationMap.examId))
-
- .addField(FormBuilder.singleSelection(
- Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID,
- CONFIG_MAPPING_NAME_TEXT_KEY,
- String.valueOf(examConfigurationMap.configurationNodeId),
- resourceService::examConfigurationSelectionResources)
- .withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService)))
-
- .addField(FormBuilder.text(
- Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
- FORM_DESCRIPTION_TEXT_KEY,
- examConfigurationMap.configDescription)
- .asArea()
- .readonly(true))
-
- .addField(FormBuilder.text(
- Domain.CONFIGURATION_NODE.ATTR_STATUS,
- FORM_STATUS_TEXT_KEY,
- resourceService.localizedExamConfigStatusName(examConfigurationMap))
- .readonly(true))
-
- .addField(FormBuilder.text(
- Domain.EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET,
- FORM_ENCRYPT_SECRET_TEXT_KEY)
- .asPasswordField())
- .addField(FormBuilder.text(
- ExamConfigurationMap.ATTR_CONFIRM_ENCRYPT_SECRET,
- FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY)
- .asPasswordField())
-
- .build();
-
- return () -> formHandle;
- }
- }
-
- private static void updateFormValuesFromConfigSelection(final Form form, final ResourceService resourceService) {
- final String configId = form.getFieldValue(Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID);
- if (StringUtils.isBlank(configId)) {
- form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, null);
- form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS, null);
- } else {
- try {
-
- final ConfigurationNode configuration = resourceService
- .getRestService()
- .getBuilder(GetExamConfigNode.class)
- .withURIVariable(API.PARAM_MODEL_ID, configId)
- .call()
- .getOrThrow();
-
- form.setFieldValue(
- Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
- configuration.description);
- form.setFieldValue(
- Domain.CONFIGURATION_NODE.ATTR_STATUS,
- resourceService.localizedExamConfigStatusName(configuration));
-
- } catch (final Exception e) {
- log.error("Failed to update form values from SEB Configuration selection", e);
- form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, null);
- form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS, null);
- }
- }
- }
-
-}
+/*
+ * 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.content;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.swt.widgets.Composite;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+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.ExamConfigurationMap;
+import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
+import ch.ethz.seb.sebserver.gui.form.Form;
+import ch.ethz.seb.sebserver.gui.form.FormBuilder;
+import ch.ethz.seb.sebserver.gui.form.FormHandle;
+import ch.ethz.seb.sebserver.gui.service.ResourceService;
+import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
+import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+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.GetExamConfigMapping;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamConfigMapping;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamConfigMapping;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
+
+final class ExamToConfigBindingForm {
+
+ private static final Logger log = LoggerFactory.getLogger(ExamToConfigBindingForm.class);
+
+ private static final LocTextKey NEW_CONFIG_MAPPING_TILE_TEXT_KEY =
+ new LocTextKey("sebserver.exam.configuration.form.title.new");
+ private static final LocTextKey CONFIG_MAPPING_TILE_TEXT_KEY =
+ new LocTextKey("sebserver.exam.configuration.form.title");
+ private static final LocTextKey CONFIG_MAPPING_NAME_TEXT_KEY =
+ new LocTextKey("sebserver.exam.configuration.form.name");
+ private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
+ new LocTextKey("sebserver.exam.configuration.form.description");
+ private static final LocTextKey FORM_STATUS_TEXT_KEY =
+ new LocTextKey("sebserver.exam.configuration.form.status");
+ private static final LocTextKey FORM_ENCRYPT_SECRET_TEXT_KEY =
+ new LocTextKey("sebserver.exam.configuration.form.encryptSecret");
+ private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
+ new LocTextKey("sebserver.exam.configuration.form.encryptSecret.confirm");
+ private final static LocTextKey CONFIG_ACTION_NO_CONFIG_MESSAGE =
+ new LocTextKey("sebserver.exam.configuration.action.noconfig.message");
+
+ static Function bindFunction(final PageService pageService) {
+
+ return action -> {
+
+ final PageContext pageContext = action.pageContext();
+ final EntityKey entityKey = pageContext.getEntityKey();
+ final boolean isNew = entityKey == null;
+
+ if (isNew) {
+ final boolean noConfigsAvailable = pageService.getResourceService()
+ .examConfigurationSelectionResources()
+ .isEmpty();
+
+ if (noConfigsAvailable) {
+ throw new PageMessageException(CONFIG_ACTION_NO_CONFIG_MESSAGE);
+ }
+ }
+
+ final ModalInputDialog> dialog =
+ new ModalInputDialog>(
+ action.pageContext().getParent().getShell(),
+ pageService.getWidgetFactory())
+ .setLargeDialogWidth();
+
+ final BindFormContext bindFormContext = new BindFormContext(
+ pageService,
+ action.pageContext());
+
+ final Predicate> doBind = formHandle -> doCreate(
+ pageService,
+ pageContext,
+ formHandle);
+
+ // the default page layout
+ final LocTextKey titleKey = (isNew)
+ ? NEW_CONFIG_MAPPING_TILE_TEXT_KEY
+ : CONFIG_MAPPING_TILE_TEXT_KEY;
+
+ dialog.open(
+ titleKey,
+ doBind,
+ Utils.EMPTY_EXECUTION,
+ bindFormContext);
+
+ return action;
+ };
+ }
+
+ private static boolean doCreate(
+ final PageService pageService,
+ final PageContext pageContext,
+ final FormHandle formHandle) {
+
+ final EntityKey entityKey = pageContext.getEntityKey();
+ final boolean isNew = entityKey == null;
+
+ final Class extends RestCall> restCall = (isNew)
+ ? NewExamConfigMapping.class
+ : SaveExamConfigMapping.class;
+
+ return !pageService
+ .getRestService()
+ .getBuilder(restCall)
+ .withFormBinding(formHandle.getFormBinding())
+ .call()
+ .onError(formHandle::handleError)
+ .map(mapping -> {
+ pageService.executePageAction(
+ pageService.pageActionBuilder(pageContext.clearEntityKeys())
+ .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
+ .withEntityKey(pageContext.getParentEntityKey())
+ .create());
+ return mapping;
+ })
+ .hasError();
+ }
+
+ private static final class BindFormContext implements ModalInputDialogComposer> {
+
+ private final PageService pageService;
+ private final PageContext pageContext;
+
+ protected BindFormContext(
+ final PageService pageService,
+ final PageContext pageContext) {
+
+ this.pageService = pageService;
+ this.pageContext = pageContext;
+
+ }
+
+ @Override
+ public Supplier> compose(final Composite parent) {
+
+ final Composite grid = this.pageService.getWidgetFactory()
+ .createPopupScrollComposite(parent);
+
+ final RestService restService = this.pageService.getRestService();
+ final ResourceService resourceService = this.pageService.getResourceService();
+
+ final EntityKey entityKey = this.pageContext.getEntityKey();
+ final EntityKey parentEntityKey = this.pageContext.getParentEntityKey();
+ final boolean isNew = entityKey == null;
+
+ final Exam exam = (isNew)
+ ? restService
+ .getBuilder(GetExam.class)
+ .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
+ .call()
+ .onError(error -> this.pageContext.notifyLoadError(EntityType.EXAM, error))
+ .getOrThrow()
+ : null;
+
+ // get data or create new. Handle error if happen
+ final ExamConfigurationMap examConfigurationMap = (isNew)
+ ? ExamConfigurationMap.createNew(exam)
+ : restService
+ .getBuilder(GetExamConfigMapping.class)
+ .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
+ .call()
+ .onError(error -> this.pageContext.notifyLoadError(
+ EntityType.EXAM_CONFIGURATION_MAP,
+ error))
+ .getOrThrow();
+
+ // new PageContext with actual EntityKey
+ final PageContext formContext = this.pageContext.withEntityKey(examConfigurationMap.getEntityKey());
+
+ final FormHandle formHandle = this.pageService.formBuilder(
+ formContext.copyOf(grid))
+ .readonly(false)
+ .putStaticValueIf(() -> !isNew,
+ Domain.EXAM_CONFIGURATION_MAP.ATTR_ID,
+ examConfigurationMap.getModelId())
+ .putStaticValue(
+ Domain.EXAM_CONFIGURATION_MAP.ATTR_INSTITUTION_ID,
+ String.valueOf(examConfigurationMap.getInstitutionId()))
+ .putStaticValue(
+ Domain.EXAM_CONFIGURATION_MAP.ATTR_EXAM_ID,
+ String.valueOf(examConfigurationMap.examId))
+
+ .addField(FormBuilder.singleSelection(
+ Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID,
+ CONFIG_MAPPING_NAME_TEXT_KEY,
+ String.valueOf(examConfigurationMap.configurationNodeId),
+ resourceService::examConfigurationSelectionResources)
+ .withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService))
+ .mandatory())
+
+ .addField(FormBuilder.text(
+ Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
+ FORM_DESCRIPTION_TEXT_KEY,
+ examConfigurationMap.configDescription)
+ .asArea()
+ .readonly(true))
+
+ .addField(FormBuilder.text(
+ Domain.CONFIGURATION_NODE.ATTR_STATUS,
+ FORM_STATUS_TEXT_KEY,
+ resourceService.localizedExamConfigStatusName(examConfigurationMap))
+ .readonly(true))
+
+ .addField(FormBuilder.text(
+ Domain.EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET,
+ FORM_ENCRYPT_SECRET_TEXT_KEY)
+ .asPasswordField())
+ .addField(FormBuilder.text(
+ ExamConfigurationMap.ATTR_CONFIRM_ENCRYPT_SECRET,
+ FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY)
+ .asPasswordField())
+
+ .build();
+
+ return () -> formHandle;
+ }
+ }
+
+ private static void updateFormValuesFromConfigSelection(final Form form, final ResourceService resourceService) {
+ final String configId = form.getFieldValue(Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID);
+ if (StringUtils.isBlank(configId)) {
+ form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, null);
+ form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS, null);
+ } else {
+ try {
+
+ final ConfigurationNode configuration = resourceService
+ .getRestService()
+ .getBuilder(GetExamConfigNode.class)
+ .withURIVariable(API.PARAM_MODEL_ID, configId)
+ .call()
+ .getOrThrow();
+
+ form.setFieldValue(
+ Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
+ configuration.description);
+ form.setFieldValue(
+ Domain.CONFIGURATION_NODE.ATTR_STATUS,
+ resourceService.localizedExamConfigStatusName(configuration));
+
+ } catch (final Exception e) {
+ log.error("Failed to update form values from SEB Configuration selection", e);
+ form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, null);
+ form.setFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS, null);
+ }
+ }
+ }
+
+}
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 51994ff9..d07481f1 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
@@ -1,203 +1,206 @@
-/*
- * 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.content;
-
-import org.eclipse.swt.widgets.Composite;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-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.exam.QuizData;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
-import ch.ethz.seb.sebserver.gui.form.Form;
-import ch.ethz.seb.sebserver.gui.form.FormBuilder;
-import ch.ethz.seb.sebserver.gui.form.FormHandle;
-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.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
-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.widget.WidgetFactory;
-import io.micrometer.core.instrument.util.StringUtils;
-
-@Lazy
-@Component
-@GuiProfile
-public class IndicatorForm implements TemplateComposer {
-
- private static final LocTextKey NEW_INDICATOR_TILE_TEXT_KEY =
- new LocTextKey("sebserver.exam.indicator.form.title.new");
- private static final LocTextKey INDICATOR_TILE_TEXT_KEY =
- new LocTextKey("sebserver.exam.indicator.form.title");
- private static final LocTextKey FORM_THRESHOLDS_TEXT_KEY =
- new LocTextKey("sebserver.exam.indicator.form.thresholds");
- private static final LocTextKey FORM_COLOR_TEXT_KEY =
- new LocTextKey("sebserver.exam.indicator.form.color");
- private static final LocTextKey FORM_TYPE_TEXT_KEY =
- new LocTextKey("sebserver.exam.indicator.form.type");
- private static final LocTextKey FORM_NAME_TEXT_KEY =
- new LocTextKey("sebserver.exam.indicator.form.name");
- private static final LocTextKey FORM_EXAM_TEXT_KEY =
- new LocTextKey("sebserver.exam.indicator.form.exam");
- private static final LocTextKey FORM_DESC_TEXT_KEY =
- new LocTextKey("sebserver.exam.indicator.form.description");
-
- private static final String INDICATOR_TYPE_DESC_PREFIX =
- "sebserver.exam.indicator.type.description.";
- private static final String TYPE_DESCRIPTION_FIELD_NAME =
- "typeDescription";
-
- private final PageService pageService;
- private final ResourceService resourceService;
- private final I18nSupport i18nSupport;
-
- protected IndicatorForm(
- final PageService pageService,
- final ResourceService resourceService) {
-
- this.pageService = pageService;
- this.resourceService = resourceService;
- this.i18nSupport = pageService.getI18nSupport();
- }
-
- @Override
- public void compose(final PageContext pageContext) {
- final RestService restService = this.resourceService.getRestService();
- final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
- 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()
- .onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
- .getOrThrow();
-
- // get data or create new. Handle error if happen
- final Indicator indicator = (isNew)
- ? Indicator.createNew(exam)
- : restService
- .getBuilder(GetIndicator.class)
- .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
- .call()
- .onError(error -> pageContext.notifyLoadError(EntityType.INDICATOR, error))
- .getOrThrow();
-
- final boolean typeSet = indicator.type != null;
- final String typeDescription = (typeSet)
- ? Utils.formatLineBreaks(this.i18nSupport.getText(INDICATOR_TYPE_DESC_PREFIX + indicator.type.name))
- : Constants.EMPTY_NOTE;
-
- // new PageContext with actual EntityKey
- final PageContext formContext = pageContext.withEntityKey(indicator.getEntityKey());
-
- // the default page layout
- final LocTextKey titleKey = (isNew)
- ? NEW_INDICATOR_TILE_TEXT_KEY
- : INDICATOR_TILE_TEXT_KEY;
- final Composite content = widgetFactory.defaultPageLayout(
- formContext.getParent(),
- titleKey);
-
- final FormHandle formHandle = this.pageService.formBuilder(
- formContext.copyOf(content))
- .readonly(isReadonly)
- .putStaticValueIf(() -> !isNew,
- Domain.INDICATOR.ATTR_ID,
- indicator.getModelId())
- .putStaticValue(
- Domain.EXAM.ATTR_INSTITUTION_ID,
- String.valueOf(exam.getInstitutionId()))
- .putStaticValue(
- Domain.INDICATOR.ATTR_EXAM_ID,
- parentEntityKey.getModelId())
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_NAME,
- FORM_EXAM_TEXT_KEY,
- exam.name)
- .readonly(true))
- .addField(FormBuilder.text(
- Domain.INDICATOR.ATTR_NAME,
- FORM_NAME_TEXT_KEY,
- indicator.name))
-
- .addField(FormBuilder.singleSelection(
- Domain.INDICATOR.ATTR_TYPE,
- FORM_TYPE_TEXT_KEY,
- (indicator.type != null) ? indicator.type.name() : null,
- this.resourceService::indicatorTypeResources)
- .withSelectionListener(this::updateForm))
-
- .addField(FormBuilder.text(
- TYPE_DESCRIPTION_FIELD_NAME,
- FORM_DESC_TEXT_KEY,
- typeDescription)
- .asArea()
- .readonly(true)
- .withInputSpan(6))
-
- .addField(FormBuilder.colorSelection(
- Domain.INDICATOR.ATTR_COLOR,
- FORM_COLOR_TEXT_KEY,
- indicator.defaultColor)
- .withEmptyCellSeparation(false))
- .addField(FormBuilder.thresholdList(
- Domain.THRESHOLD.REFERENCE_NAME,
- FORM_THRESHOLDS_TEXT_KEY,
- indicator))
-
- .buildFor((isNew)
- ? restService.getRestCall(NewIndicator.class)
- : restService.getRestCall(SaveIndicator.class));
-
- // propagate content actions to action-pane
- this.pageService.pageActionBuilder(formContext.clearEntityKeys())
-
- .newAction(ActionDefinition.EXAM_INDICATOR_SAVE)
- .withEntityKey(parentEntityKey)
- .withExec(formHandle::processFormSave)
- .ignoreMoveAwayFromEdit()
- .publishIf(() -> !isReadonly)
-
- .newAction(ActionDefinition.EXAM_INDICATOR_CANCEL_MODIFY)
- .withEntityKey(parentEntityKey)
- .withExec(this.pageService.backToCurrentFunction())
- .publishIf(() -> !isReadonly);
-
- }
-
- private final void updateForm(final Form form) {
- final String typeValue = form.getFieldValue(Domain.INDICATOR.ATTR_TYPE);
- if (StringUtils.isNotBlank(typeValue)) {
- form.setFieldValue(
- TYPE_DESCRIPTION_FIELD_NAME,
- this.i18nSupport.getText(INDICATOR_TYPE_DESC_PREFIX + typeValue));
- } else {
- form.setFieldValue(TYPE_DESCRIPTION_FIELD_NAME, Constants.EMPTY_NOTE);
- }
- }
-
-}
+/*
+ * 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.content;
+
+import org.eclipse.swt.widgets.Composite;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+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.exam.QuizData;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
+import ch.ethz.seb.sebserver.gui.form.Form;
+import ch.ethz.seb.sebserver.gui.form.FormBuilder;
+import ch.ethz.seb.sebserver.gui.form.FormHandle;
+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.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+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.widget.WidgetFactory;
+import io.micrometer.core.instrument.util.StringUtils;
+
+@Lazy
+@Component
+@GuiProfile
+public class IndicatorForm implements TemplateComposer {
+
+ private static final LocTextKey NEW_INDICATOR_TILE_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.form.title.new");
+ private static final LocTextKey INDICATOR_TILE_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.form.title");
+ private static final LocTextKey FORM_THRESHOLDS_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.form.thresholds");
+ private static final LocTextKey FORM_COLOR_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.form.color");
+ private static final LocTextKey FORM_TYPE_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.form.type");
+ private static final LocTextKey FORM_NAME_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.form.name");
+ private static final LocTextKey FORM_EXAM_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.form.exam");
+ private static final LocTextKey FORM_DESC_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.form.description");
+
+ private static final String INDICATOR_TYPE_DESC_PREFIX =
+ "sebserver.exam.indicator.type.description.";
+ private static final String TYPE_DESCRIPTION_FIELD_NAME =
+ "typeDescription";
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final I18nSupport i18nSupport;
+
+ protected IndicatorForm(
+ final PageService pageService,
+ final ResourceService resourceService) {
+
+ this.pageService = pageService;
+ this.resourceService = resourceService;
+ this.i18nSupport = pageService.getI18nSupport();
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final RestService restService = this.resourceService.getRestService();
+ final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
+ 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()
+ .onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
+ .getOrThrow();
+
+ // get data or create new. Handle error if happen
+ final Indicator indicator = (isNew)
+ ? Indicator.createNew(exam)
+ : restService
+ .getBuilder(GetIndicator.class)
+ .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
+ .call()
+ .onError(error -> pageContext.notifyLoadError(EntityType.INDICATOR, error))
+ .getOrThrow();
+
+ final boolean typeSet = indicator.type != null;
+ final String typeDescription = (typeSet)
+ ? Utils.formatLineBreaks(this.i18nSupport.getText(INDICATOR_TYPE_DESC_PREFIX + indicator.type.name))
+ : Constants.EMPTY_NOTE;
+
+ // new PageContext with actual EntityKey
+ final PageContext formContext = pageContext.withEntityKey(indicator.getEntityKey());
+
+ // the default page layout
+ final LocTextKey titleKey = (isNew)
+ ? NEW_INDICATOR_TILE_TEXT_KEY
+ : INDICATOR_TILE_TEXT_KEY;
+ final Composite content = widgetFactory.defaultPageLayout(
+ formContext.getParent(),
+ titleKey);
+
+ final FormHandle formHandle = this.pageService.formBuilder(
+ formContext.copyOf(content))
+ .readonly(isReadonly)
+ .putStaticValueIf(() -> !isNew,
+ Domain.INDICATOR.ATTR_ID,
+ indicator.getModelId())
+ .putStaticValue(
+ Domain.EXAM.ATTR_INSTITUTION_ID,
+ String.valueOf(exam.getInstitutionId()))
+ .putStaticValue(
+ Domain.INDICATOR.ATTR_EXAM_ID,
+ parentEntityKey.getModelId())
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_NAME,
+ FORM_EXAM_TEXT_KEY,
+ exam.name)
+ .readonly(true))
+ .addField(FormBuilder.text(
+ Domain.INDICATOR.ATTR_NAME,
+ FORM_NAME_TEXT_KEY,
+ indicator.name)
+ .mandatory(!isReadonly))
+
+ .addField(FormBuilder.singleSelection(
+ Domain.INDICATOR.ATTR_TYPE,
+ FORM_TYPE_TEXT_KEY,
+ (indicator.type != null) ? indicator.type.name() : null,
+ this.resourceService::indicatorTypeResources)
+ .withSelectionListener(this::updateForm)
+ .mandatory(!isReadonly))
+
+ .addField(FormBuilder.text(
+ TYPE_DESCRIPTION_FIELD_NAME,
+ FORM_DESC_TEXT_KEY,
+ typeDescription)
+ .asArea()
+ .asHTML(true)
+ .readonly(true)
+ .withInputSpan(6))
+
+ .addField(FormBuilder.colorSelection(
+ Domain.INDICATOR.ATTR_COLOR,
+ FORM_COLOR_TEXT_KEY,
+ indicator.defaultColor)
+ .withEmptyCellSeparation(false))
+ .addField(FormBuilder.thresholdList(
+ Domain.THRESHOLD.REFERENCE_NAME,
+ FORM_THRESHOLDS_TEXT_KEY,
+ indicator))
+
+ .buildFor((isNew)
+ ? restService.getRestCall(NewIndicator.class)
+ : restService.getRestCall(SaveIndicator.class));
+
+ // propagate content actions to action-pane
+ this.pageService.pageActionBuilder(formContext.clearEntityKeys())
+
+ .newAction(ActionDefinition.EXAM_INDICATOR_SAVE)
+ .withEntityKey(parentEntityKey)
+ .withExec(formHandle::processFormSave)
+ .ignoreMoveAwayFromEdit()
+ .publishIf(() -> !isReadonly)
+
+ .newAction(ActionDefinition.EXAM_INDICATOR_CANCEL_MODIFY)
+ .withEntityKey(parentEntityKey)
+ .withExec(this.pageService.backToCurrentFunction())
+ .publishIf(() -> !isReadonly);
+
+ }
+
+ private void updateForm(final Form form) {
+ final String typeValue = form.getFieldValue(Domain.INDICATOR.ATTR_TYPE);
+ if (StringUtils.isNotBlank(typeValue)) {
+ form.setFieldValue(
+ TYPE_DESCRIPTION_FIELD_NAME,
+ this.i18nSupport.getText(INDICATOR_TYPE_DESC_PREFIX + typeValue));
+ } else {
+ form.setFieldValue(TYPE_DESCRIPTION_FIELD_NAME, Constants.EMPTY_NOTE);
+ }
+ }
+
+}
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 68236399..7816b146 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
@@ -1,420 +1,428 @@
-/*
- * 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.content;
-
-import java.util.function.BooleanSupplier;
-import java.util.function.Function;
-
-import org.apache.commons.lang3.StringUtils;
-import org.eclipse.swt.widgets.Composite;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.model.Domain;
-import ch.ethz.seb.sebserver.gbl.model.EntityKey;
-import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
-import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
-import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
-import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
-import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-import ch.ethz.seb.sebserver.gbl.util.Result;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-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.service.ResourceService;
-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.PageMessageException;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.ActivateLmsSetup;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.DeactivateLmsSetup;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.NewLmsSetup;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.SaveLmsSetup;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetupAdHoc;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-
-@Lazy
-@Component
-@GuiProfile
-public class LmsSetupForm implements TemplateComposer {
-
- private static final LocTextKey TITLE_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.form.title");
- private static final LocTextKey NEW_TITLE_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.form.title.new");
-
- private static final LocTextKey FORM_SECRET_LMS_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.form.secret.lms");
- private static final LocTextKey FORM_CLIENTNAME_LMS_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.form.clientname.lms");
- private static final LocTextKey FORM_URL_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.form.url");
- private static final LocTextKey FORM_TYPE_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.form.type");
- private static final LocTextKey FORM_NAME_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.form.name");
- private static final LocTextKey FORM_INSTITUTION_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.form.institution");
- private static final LocTextKey FORM_PROXY_KEY =
- new LocTextKey("sebserver.lmssetup.form.proxy");
- private static final LocTextKey FORM_PROXY_HOST_KEY =
- new LocTextKey("sebserver.lmssetup.form.proxy.host");
- private static final LocTextKey FORM_PROXY_PORT_KEY =
- new LocTextKey("sebserver.lmssetup.form.proxy.port");
- private static final LocTextKey FORM_PROXY_AUTH_CREDENTIALS_KEY =
- new LocTextKey("sebserver.lmssetup.form.proxy.auth-credentials");
-
- private final PageService pageService;
- private final ResourceService resourceService;
-
- protected LmsSetupForm(
- final PageService pageService,
- final ResourceService resourceService) {
-
- this.pageService = pageService;
- this.resourceService = resourceService;
- }
-
- @Override
- public void compose(final PageContext pageContext) {
- final CurrentUser currentUser = this.resourceService.getCurrentUser();
- final RestService restService = this.resourceService.getRestService();
- final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
-
- final UserInfo user = currentUser.get();
- final EntityKey entityKey = pageContext.getEntityKey();
- final EntityKey parentEntityKey = pageContext.getParentEntityKey();
- final boolean readonly = pageContext.isReadonly();
-
- final BooleanSupplier isNew = () -> entityKey == null;
- final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean();
- final BooleanSupplier isSEBAdmin = () -> user.hasRole(UserRole.SEB_SERVER_ADMIN);
- final BooleanSupplier isEdit = () -> !readonly;
-
- // get data or create new. handle error if happen
- final LmsSetup lmsSetup = isNew.getAsBoolean()
- ? LmsSetup.createNew((parentEntityKey != null)
- ? Long.valueOf(parentEntityKey.modelId)
- : user.institutionId)
- : restService
- .getBuilder(GetLmsSetup.class)
- .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
- .call()
- .onError(error -> pageContext.notifyLoadError(EntityType.LMS_SETUP, error))
- .getOrThrow();
-
- // new PageContext with actual EntityKey
- final PageContext formContext = pageContext.withEntityKey(lmsSetup.getEntityKey());
- // the default page layout with title
- final LocTextKey titleKey = isNotNew.getAsBoolean()
- ? TITLE_TEXT_KEY
- : NEW_TITLE_TEXT_KEY;
- final Composite content = widgetFactory.defaultPageLayout(
- formContext.getParent(),
- titleKey);
-
- final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(lmsSetup);
- final boolean writeGrant = userGrantCheck.w();
- final boolean modifyGrant = userGrantCheck.m();
- final boolean institutionActive = restService.getBuilder(GetInstitution.class)
- .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(lmsSetup.getInstitutionId()))
- .call()
- .map(inst -> inst.active)
- .getOr(false);
-
- // The LMS Setup form
- final LmsType lmsType = lmsSetup.getLmsType();
- final FormHandle formHandle = this.pageService.formBuilder(
- formContext.copyOf(content), 8)
- .withDefaultSpanLabel(2)
- .withDefaultSpanInput(5)
- .withDefaultSpanEmptyCell(1)
- .readonly(readonly)
- .putStaticValueIf(isNotNew,
- Domain.LMS_SETUP.ATTR_ID,
- lmsSetup.getModelId())
- .putStaticValue(
- Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
- String.valueOf(lmsSetup.getInstitutionId()))
- .putStaticValueIf(isNotNew,
- Domain.LMS_SETUP.ATTR_LMS_TYPE,
- String.valueOf(lmsSetup.getLmsType()))
- .addFieldIf(
- isSEBAdmin,
- () -> FormBuilder.singleSelection(
- Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
- FORM_INSTITUTION_TEXT_KEY,
- String.valueOf(lmsSetup.getInstitutionId()),
- () -> this.resourceService.institutionResource())
- .readonly(true))
- .addField(FormBuilder.text(
- Domain.LMS_SETUP.ATTR_NAME,
- FORM_NAME_TEXT_KEY,
- lmsSetup.getName()))
- .addField(FormBuilder.singleSelection(
- Domain.LMS_SETUP.ATTR_LMS_TYPE,
- FORM_TYPE_TEXT_KEY,
- (lmsType != null) ? lmsType.name() : LmsType.MOCKUP.name(),
- this.resourceService::lmsTypeResources)
- .readonlyIf(isNotNew))
- .addField(FormBuilder.text(
- Domain.LMS_SETUP.ATTR_LMS_URL,
- FORM_URL_TEXT_KEY,
- lmsSetup.getLmsApiUrl()))
- .addField(FormBuilder.text(
- Domain.LMS_SETUP.ATTR_LMS_CLIENTNAME,
- FORM_CLIENTNAME_LMS_TEXT_KEY,
- (lmsSetup.getLmsAuthName() != null) ? lmsSetup.getLmsAuthName() : null))
- .addFieldIf(
- isEdit,
- () -> FormBuilder.text(
- Domain.LMS_SETUP.ATTR_LMS_CLIENTSECRET,
- FORM_SECRET_LMS_TEXT_KEY)
- .asPasswordField())
-
- .addFieldIf(
- () -> readonly,
- () -> FormBuilder.text(
- Domain.LMS_SETUP.ATTR_LMS_PROXY_HOST,
- FORM_PROXY_KEY,
- (StringUtils.isNotBlank(lmsSetup.getProxyHost()))
- ? lmsSetup.getProxyHost() + Constants.URL_PORT_SEPARATOR + lmsSetup.proxyPort
- : null))
-
- .addFieldIf(
- isEdit,
- () -> FormBuilder.text(
- Domain.LMS_SETUP.ATTR_LMS_PROXY_HOST,
- FORM_PROXY_HOST_KEY,
- (StringUtils.isNotBlank(lmsSetup.getProxyHost())) ? lmsSetup.getProxyHost() : null)
- .withInputSpan(3)
- .withEmptyCellSpan(0))
- .addFieldIf(
- isEdit,
- () -> FormBuilder.text(
- Domain.LMS_SETUP.ATTR_LMS_PROXY_PORT,
- FORM_PROXY_PORT_KEY,
- (lmsSetup.getProxyPort() != null) ? String.valueOf(lmsSetup.getProxyPort()) : null)
- .asNumber(number -> {
- if (StringUtils.isNotBlank(number)) {
- Integer.parseInt(number);
- }
- })
- .withInputSpan(1)
- .withLabelSpan(1)
- .withEmptyCellSeparation(false)
- .withEmptyCellSpan(0))
- .addFieldIf(
- isEdit,
- () -> FormBuilder.text(
- Domain.LMS_SETUP.ATTR_LMS_PROXY_AUTH_USERNAME,
- FORM_PROXY_AUTH_CREDENTIALS_KEY,
- (lmsSetup.getProxyAuthUsername() != null) ? lmsSetup.getProxyAuthUsername() : null)
- .withInputSpan(3)
- .withEmptyCellSpan(0))
- .addFieldIf(
- isEdit,
- () -> FormBuilder.text(Domain.LMS_SETUP.ATTR_LMS_PROXY_AUTH_SECRET)
- .asPasswordField()
- .withInputSpan(2)
- .withLabelSpan(0)
- .withEmptyCellSeparation(false)
- .withEmptyCellSpan(0))
-
- .buildFor((entityKey == null)
- ? restService.getRestCall(NewLmsSetup.class)
- : restService.getRestCall(SaveLmsSetup.class));
-
- // propagate content actions to action-pane
- this.pageService.pageActionBuilder(formContext.clearEntityKeys())
-
- .newAction(ActionDefinition.LMS_SETUP_NEW)
- .publishIf(() -> writeGrant && readonly && institutionActive)
-
- .newAction(ActionDefinition.LMS_SETUP_MODIFY)
- .withEntityKey(entityKey)
- .publishIf(() -> modifyGrant && readonly && institutionActive)
-
- .newAction(ActionDefinition.LMS_SETUP_TEST_AND_SAVE)
- .withEntityKey(entityKey)
- .withExec(action -> this.testAdHoc(action, formHandle))
- .ignoreMoveAwayFromEdit()
- .publishIf(() -> modifyGrant && !readonly)
-
- .newAction(ActionDefinition.LMS_SETUP_DEACTIVATE)
- .withEntityKey(entityKey)
- .withSimpleRestCall(restService, DeactivateLmsSetup.class)
- .withConfirm(this.pageService.confirmDeactivation(lmsSetup))
- .publishIf(() -> writeGrant && readonly && institutionActive && lmsSetup.isActive())
-
- .newAction(ActionDefinition.LMS_SETUP_ACTIVATE)
- .withEntityKey(entityKey)
- .withExec(action -> activate(action, formHandle))
- .publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive())
-
- .newAction(ActionDefinition.LMS_SETUP_SAVE)
- .withEntityKey(entityKey)
- .withExec(formHandle::processFormSave)
- .ignoreMoveAwayFromEdit()
- .publishIf(() -> !readonly)
-
- .newAction(ActionDefinition.LMS_SETUP_CANCEL_MODIFY)
- .withEntityKey(entityKey)
- .withExec(this.pageService.backToCurrentFunction())
- .publishIf(() -> !readonly);
- }
-
- /** Save and test connection before activation */
- private PageAction activate(final PageAction action, final FormHandle formHandle) {
- // first test the LMS Setup. If this fails the action execution will stops
- final PageAction testLmsSetup = this.testLmsSetup(action, formHandle, false);
- // if LMS Setup test was successful, the activation action applies
- this.resourceService.getRestService().getBuilder(ActivateLmsSetup.class)
- .withURIVariable(
- API.PARAM_MODEL_ID,
- action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
- .call()
- .onError(error -> action.pageContext().notifyActivationError(EntityType.LMS_SETUP, error));
-
- return testLmsSetup;
- }
-
- /** LmsSetup test action implementation */
- private PageAction testAdHoc(final PageAction action, final FormHandle formHandle) {
-
- // reset previous errors
- formHandle.process(
- Utils.truePredicate(),
- fieldAccessor -> fieldAccessor.resetError());
-
- // first test the connection on ad hoc object
- final Result result = this.resourceService.getRestService()
- .getBuilder(TestLmsSetupAdHoc.class)
- .withFormBinding(formHandle.getFormBinding())
- .call();
-
- // ... and handle the response
- if (result.hasError()) {
- if (formHandle.handleError(result.getError())) {
- final Exception error = result.getError();
- if (error instanceof RestCallError) {
- throw (RestCallError) error;
- } else {
- throw new RuntimeException("Cause: ", error);
- }
- }
- }
-
- return handleTestResult(
- action,
- a -> {
- // try to save the LmsSetup
- final PageAction processFormSave = formHandle.processFormSave(a);
- processFormSave.pageContext().publishInfo(
- new LocTextKey("sebserver.lmssetup.action.test.ok"));
- return processFormSave;
- },
- result.getOrThrow());
- }
-
- /** LmsSetup test action implementation */
- private PageAction testLmsSetup(
- final PageAction action,
- final FormHandle formHandle, final boolean saveFirst) {
-
- if (saveFirst) {
- final Result postResult = formHandle.doAPIPost();
- if (postResult.hasError()) {
- formHandle.handleError(postResult.getError());
- postResult.getOrThrow();
- }
- }
-
- // Call the testing endpoint with the specified data to test
- final EntityKey entityKey = action.getEntityKey();
- final RestService restService = this.resourceService.getRestService();
- final Result result = restService.getBuilder(TestLmsSetup.class)
- .withURIVariable(API.PARAM_MODEL_ID, entityKey.getModelId())
- .call();
-
- // ... and handle the response
- if (result.hasError()) {
- if (formHandle.handleError(result.getError())) {
- throw new PageMessageException(
- new LocTextKey("sebserver.lmssetup.action.test.missingParameter"));
- }
- }
-
- return handleTestResult(
- action,
- a -> {
- action.pageContext().publishInfo(
- new LocTextKey("sebserver.lmssetup.action.test.ok"));
-
- return action;
- },
- result.getOrThrow());
- }
-
- private PageAction handleTestResult(
- final PageAction action,
- final Function onOK,
- final LmsSetupTestResult testResult) {
-
- if (testResult.isOk()) {
- return onOK.apply(action);
- }
-
- testResult.errors
- .stream()
- .findFirst()
- .ifPresent(error -> {
- switch (error.errorType) {
- case TOKEN_REQUEST: {
- throw new PageMessageException(new LocTextKey(
- "sebserver.lmssetup.action.test.tokenRequestError",
- error.message));
- }
- case QUIZ_ACCESS_API_REQUEST: {
- throw new PageMessageException(new LocTextKey(
- "sebserver.lmssetup.action.test.quizRequestError",
- error.message));
- }
- case QUIZ_RESTRICTION_API_REQUEST: {
- // NOTE: quiz restriction is not mandatory for functional LmsSetup
- // so this error is ignored here
- break;
- }
- default: {
- throw new PageMessageException(new LocTextKey(
- "sebserver.lmssetup.action.test.unknownError",
- error.message));
- }
- }
- });
-
- return onOK.apply(action);
- }
-
-}
+/*
+ * 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.content;
+
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+
+import ch.ethz.seb.sebserver.gui.form.Form;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.swt.widgets.Composite;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
+import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
+import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+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.service.ResourceService;
+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.PageMessageException;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.ActivateLmsSetup;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.DeactivateLmsSetup;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.NewLmsSetup;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.SaveLmsSetup;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetupAdHoc;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+
+@Lazy
+@Component
+@GuiProfile
+public class LmsSetupForm implements TemplateComposer {
+
+ private static final LocTextKey TITLE_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.form.title");
+ private static final LocTextKey NEW_TITLE_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.form.title.new");
+
+ private static final LocTextKey FORM_SECRET_LMS_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.form.secret.lms");
+ private static final LocTextKey FORM_CLIENTNAME_LMS_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.form.clientname.lms");
+ private static final LocTextKey FORM_URL_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.form.url");
+ private static final LocTextKey FORM_TYPE_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.form.type");
+ private static final LocTextKey FORM_NAME_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.form.name");
+ private static final LocTextKey FORM_INSTITUTION_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.form.institution");
+ private static final LocTextKey FORM_PROXY_KEY =
+ new LocTextKey("sebserver.lmssetup.form.proxy");
+ private static final LocTextKey FORM_PROXY_HOST_KEY =
+ new LocTextKey("sebserver.lmssetup.form.proxy.host");
+ private static final LocTextKey FORM_PROXY_PORT_KEY =
+ new LocTextKey("sebserver.lmssetup.form.proxy.port");
+ private static final LocTextKey FORM_PROXY_AUTH_CREDENTIALS_KEY =
+ new LocTextKey("sebserver.lmssetup.form.proxy.auth-credentials");
+ public static final LocTextKey LMS_SETUP_TEST_OK =
+ new LocTextKey("sebserver.lmssetup.action.test.ok");
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+
+ protected LmsSetupForm(
+ final PageService pageService,
+ final ResourceService resourceService) {
+
+ this.pageService = pageService;
+ this.resourceService = resourceService;
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final CurrentUser currentUser = this.resourceService.getCurrentUser();
+ final RestService restService = this.resourceService.getRestService();
+ final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
+
+ final UserInfo user = currentUser.get();
+ final EntityKey entityKey = pageContext.getEntityKey();
+ final EntityKey parentEntityKey = pageContext.getParentEntityKey();
+ final boolean readonly = pageContext.isReadonly();
+
+ final BooleanSupplier isNew = () -> entityKey == null;
+ final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean();
+ final BooleanSupplier isSEBAdmin = () -> user.hasRole(UserRole.SEB_SERVER_ADMIN);
+ final BooleanSupplier isEdit = () -> !readonly;
+
+ // get data or create new. handle error if happen
+ final LmsSetup lmsSetup = isNew.getAsBoolean()
+ ? LmsSetup.createNew((parentEntityKey != null)
+ ? Long.valueOf(parentEntityKey.modelId)
+ : user.institutionId)
+ : restService
+ .getBuilder(GetLmsSetup.class)
+ .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
+ .call()
+ .onError(error -> pageContext.notifyLoadError(EntityType.LMS_SETUP, error))
+ .getOrThrow();
+
+ // new PageContext with actual EntityKey
+ final PageContext formContext = pageContext.withEntityKey(lmsSetup.getEntityKey());
+ // the default page layout with title
+ final LocTextKey titleKey = isNotNew.getAsBoolean()
+ ? TITLE_TEXT_KEY
+ : NEW_TITLE_TEXT_KEY;
+ final Composite content = widgetFactory.defaultPageLayout(
+ formContext.getParent(),
+ titleKey);
+
+ final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(lmsSetup);
+ final boolean writeGrant = userGrantCheck.w();
+ final boolean modifyGrant = userGrantCheck.m();
+ final boolean institutionActive = restService.getBuilder(GetInstitution.class)
+ .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(lmsSetup.getInstitutionId()))
+ .call()
+ .map(inst -> inst.active)
+ .getOr(false);
+
+ // The LMS Setup form
+ final LmsType lmsType = lmsSetup.getLmsType();
+ final FormHandle formHandle = this.pageService.formBuilder(
+ formContext.copyOf(content), 8)
+ .withDefaultSpanLabel(2)
+ .withDefaultSpanInput(5)
+ .withDefaultSpanEmptyCell(1)
+ .readonly(readonly)
+ .putStaticValueIf(isNotNew,
+ Domain.LMS_SETUP.ATTR_ID,
+ lmsSetup.getModelId())
+ .putStaticValue(
+ Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
+ String.valueOf(lmsSetup.getInstitutionId()))
+ .putStaticValueIf(isNotNew,
+ Domain.LMS_SETUP.ATTR_LMS_TYPE,
+ String.valueOf(lmsSetup.getLmsType()))
+
+ .addFieldIf(
+ isSEBAdmin,
+ () -> FormBuilder.singleSelection(
+ Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
+ FORM_INSTITUTION_TEXT_KEY,
+ String.valueOf(lmsSetup.getInstitutionId()),
+ this.resourceService::institutionResource)
+ .readonly(true))
+
+ .addField(FormBuilder.text(
+ Domain.LMS_SETUP.ATTR_NAME,
+ FORM_NAME_TEXT_KEY,
+ lmsSetup.getName())
+ .mandatory(!readonly))
+
+ .addField(FormBuilder.singleSelection(
+ Domain.LMS_SETUP.ATTR_LMS_TYPE,
+ FORM_TYPE_TEXT_KEY,
+ (lmsType != null) ? lmsType.name() : LmsType.MOCKUP.name(),
+ this.resourceService::lmsTypeResources)
+ .readonlyIf(isNotNew)
+ .mandatory(!readonly))
+
+ .addField(FormBuilder.text(
+ Domain.LMS_SETUP.ATTR_LMS_URL,
+ FORM_URL_TEXT_KEY,
+ lmsSetup.getLmsApiUrl())
+ .mandatory(!readonly))
+
+ .addField(FormBuilder.text(
+ Domain.LMS_SETUP.ATTR_LMS_CLIENTNAME,
+ FORM_CLIENTNAME_LMS_TEXT_KEY,
+ lmsSetup.getLmsAuthName())
+ .mandatory(!readonly))
+
+ .addFieldIf(
+ isEdit,
+ () -> FormBuilder.text(
+ Domain.LMS_SETUP.ATTR_LMS_CLIENTSECRET,
+ FORM_SECRET_LMS_TEXT_KEY)
+ .asPasswordField()
+ .mandatory(!readonly))
+
+ .addFieldIf(
+ () -> readonly,
+ () -> FormBuilder.text(
+ Domain.LMS_SETUP.ATTR_LMS_PROXY_HOST,
+ FORM_PROXY_KEY,
+ (StringUtils.isNotBlank(lmsSetup.getProxyHost()))
+ ? lmsSetup.getProxyHost() + Constants.URL_PORT_SEPARATOR + lmsSetup.proxyPort
+ : null))
+
+ .addFieldIf(
+ isEdit,
+ () -> FormBuilder.text(
+ Domain.LMS_SETUP.ATTR_LMS_PROXY_HOST,
+ FORM_PROXY_HOST_KEY,
+ (StringUtils.isNotBlank(lmsSetup.getProxyHost())) ? lmsSetup.getProxyHost() : null)
+ .withInputSpan(3)
+ .withEmptyCellSpan(0))
+ .addFieldIf(
+ isEdit,
+ () -> FormBuilder.text(
+ Domain.LMS_SETUP.ATTR_LMS_PROXY_PORT,
+ FORM_PROXY_PORT_KEY,
+ (lmsSetup.getProxyPort() != null) ? String.valueOf(lmsSetup.getProxyPort()) : null)
+ .asNumber(number -> {
+ if (StringUtils.isNotBlank(number)) {
+ Integer.parseInt(number);
+ }
+ })
+ .withInputSpan(1)
+ .withLabelSpan(1)
+ .withEmptyCellSeparation(false)
+ .withEmptyCellSpan(0))
+ .addFieldIf(
+ isEdit,
+ () -> FormBuilder.text(
+ Domain.LMS_SETUP.ATTR_LMS_PROXY_AUTH_USERNAME,
+ FORM_PROXY_AUTH_CREDENTIALS_KEY,
+ lmsSetup.getProxyAuthUsername())
+ .withInputSpan(3)
+ .withEmptyCellSpan(0))
+ .addFieldIf(
+ isEdit,
+ () -> FormBuilder.text(Domain.LMS_SETUP.ATTR_LMS_PROXY_AUTH_SECRET)
+ .asPasswordField()
+ .withInputSpan(2)
+ .withLabelSpan(0)
+ .withEmptyCellSeparation(false)
+ .withEmptyCellSpan(0))
+
+ .buildFor((entityKey == null)
+ ? restService.getRestCall(NewLmsSetup.class)
+ : restService.getRestCall(SaveLmsSetup.class));
+
+ // propagate content actions to action-pane
+ this.pageService.pageActionBuilder(formContext.clearEntityKeys())
+
+ .newAction(ActionDefinition.LMS_SETUP_NEW)
+ .publishIf(() -> writeGrant && readonly && institutionActive)
+
+ .newAction(ActionDefinition.LMS_SETUP_MODIFY)
+ .withEntityKey(entityKey)
+ .publishIf(() -> modifyGrant && readonly && institutionActive)
+
+ .newAction(ActionDefinition.LMS_SETUP_DEACTIVATE)
+ .withEntityKey(entityKey)
+ .withSimpleRestCall(restService, DeactivateLmsSetup.class)
+ .withConfirm(this.pageService.confirmDeactivation(lmsSetup))
+ .publishIf(() -> writeGrant && readonly && institutionActive && lmsSetup.isActive())
+
+ .newAction(ActionDefinition.LMS_SETUP_ACTIVATE)
+ .withEntityKey(entityKey)
+ .withExec(action -> activate(action, formHandle, restService))
+ .publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive())
+
+ .newAction(ActionDefinition.LMS_SETUP_SAVE)
+ .withEntityKey(entityKey)
+ .withExec(formHandle::processFormSave)
+ .ignoreMoveAwayFromEdit()
+ .publishIf(() -> !readonly)
+
+ .newAction(ActionDefinition.LMS_SETUP_SAVE_AND_ACTIVATE)
+ .withEntityKey(entityKey)
+ .withExec(action -> {
+ this.testAdHoc(action, formHandle);
+ PageAction newAction = formHandle.saveAndActivate(action);
+ pageContext.publishInfo(LMS_SETUP_TEST_OK);
+ return newAction;
+ })
+ .ignoreMoveAwayFromEdit()
+ .publishIf(() -> !readonly && !lmsSetup.isActive())
+
+ .newAction(ActionDefinition.LMS_SETUP_CANCEL_MODIFY)
+ .withEntityKey(entityKey)
+ .withExec(this.pageService.backToCurrentFunction())
+ .publishIf(() -> !readonly);
+ }
+
+ /** Save and test connection before activation */
+ public static PageAction activate(
+ final PageAction action,
+ final FormHandle formHandle,
+ final RestService restService) {
+
+ // first test the LMS Setup. If this fails the action execution will stops
+ final PageAction testLmsSetup = testLmsSetup(action, formHandle, restService);
+ // if LMS Setup test was successful, the activation action applies
+ restService.getBuilder(ActivateLmsSetup.class)
+ .withURIVariable(
+ API.PARAM_MODEL_ID,
+ action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
+ .call()
+ .onError(error -> action.pageContext().notifyActivationError(EntityType.LMS_SETUP, error));
+
+ return testLmsSetup;
+ }
+
+ /** LmsSetup test action implementation */
+ private PageAction testAdHoc(final PageAction action, final FormHandle formHandle) {
+
+ // reset previous errors
+ formHandle.process(
+ Utils.truePredicate(),
+ Form.FormFieldAccessor::resetError);
+
+ // first test the connection on ad hoc object
+ final Result result = this.resourceService.getRestService()
+ .getBuilder(TestLmsSetupAdHoc.class)
+ .withFormBinding(formHandle.getFormBinding())
+ .call();
+
+ // ... and handle the response
+ if (result.hasError()) {
+ if (formHandle.handleError(result.getError())) {
+ final Exception error = result.getError();
+ if (error instanceof RestCallError) {
+ throw (RestCallError) error;
+ } else {
+ throw new RuntimeException("Cause: ", error);
+ }
+ }
+ }
+
+ return action;
+ }
+
+ /** LmsSetup test action implementation */
+ public static PageAction testLmsSetup(
+ final PageAction action,
+ final FormHandle formHandle,
+ final RestService restService) {
+
+ // Call the testing endpoint with the specified data to test
+ final EntityKey entityKey = action.getEntityKey();
+ final Result result = restService.getBuilder(TestLmsSetup.class)
+ .withURIVariable(API.PARAM_MODEL_ID, entityKey.getModelId())
+ .call();
+
+ // ... and handle the response
+ if (result.hasError()) {
+ if (formHandle != null && formHandle.handleError(result.getError())) {
+ throw new PageMessageException(
+ new LocTextKey("sebserver.lmssetup.action.test.missingParameter"));
+ }
+
+ result.getOrThrow();
+ }
+
+ return handleTestResult(
+ action,
+ a -> {
+ action.pageContext().publishInfo(
+ new LocTextKey("sebserver.lmssetup.action.test.ok"));
+
+ return action;
+ },
+ result.getOrThrow());
+ }
+
+ private static PageAction handleTestResult(
+ final PageAction action,
+ final Function onOK,
+ final LmsSetupTestResult testResult) {
+
+ if (testResult.isOk()) {
+ return onOK.apply(action);
+ }
+
+ testResult.errors
+ .stream()
+ .findFirst()
+ .ifPresent(error -> {
+ switch (error.errorType) {
+ case TOKEN_REQUEST: {
+ throw new PageMessageException(new LocTextKey(
+ "sebserver.lmssetup.action.test.tokenRequestError",
+ error.message));
+ }
+ case QUIZ_ACCESS_API_REQUEST: {
+ throw new PageMessageException(new LocTextKey(
+ "sebserver.lmssetup.action.test.quizRequestError",
+ error.message));
+ }
+ case QUIZ_RESTRICTION_API_REQUEST: {
+ // NOTE: quiz restriction is not mandatory for functional LmsSetup
+ // so this error is ignored here
+ break;
+ }
+ default: {
+ throw new PageMessageException(new LocTextKey(
+ "sebserver.lmssetup.action.test.unknownError",
+ error.message));
+ }
+ }
+ });
+
+ return onOK.apply(action);
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java
index 32c1e0d2..afd7f843 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java
@@ -1,185 +1,204 @@
-/*
- * 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.content;
-
-import java.util.function.Function;
-
-import org.eclipse.swt.widgets.Composite;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.model.Domain;
-import ch.ethz.seb.sebserver.gbl.model.Entity;
-import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
-import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
-import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-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.LocTextKey;
-import ch.ethz.seb.sebserver.gui.service.page.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
-import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupPage;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
-import ch.ethz.seb.sebserver.gui.table.EntityTable;
-import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-
-@Lazy
-@Component
-@GuiProfile
-public class LmsSetupList implements TemplateComposer {
-
- private static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
- new LocTextKey("sebserver.lmssetup.list.action.no.modify.privilege");
- private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.info.pleaseSelect");
- private static final LocTextKey ACTIVITY_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.list.column.active");
- private static final LocTextKey TYPE_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.list.column.type");
- private static final LocTextKey NAME_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.list.column.name");
- private static final LocTextKey INSTITUTION_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.list.column.institution");
- private static final LocTextKey EMPTY_LIST_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.list.empty");
- private static final LocTextKey TITLE_TEXT_KEY =
- new LocTextKey("sebserver.lmssetup.list.title");
-
- private final TableFilterAttribute institutionFilter;
- private final TableFilterAttribute nameFilter =
- new TableFilterAttribute(CriteriaType.TEXT, Entity.FILTER_ATTR_NAME);
- private final TableFilterAttribute typeFilter;
- private final TableFilterAttribute activityFilter;
-
- private final PageService pageService;
- private final ResourceService resourceService;
- private final int pageSize;
-
- protected LmsSetupList(
- final PageService pageService,
- final ResourceService resourceService,
- @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
-
- this.pageService = pageService;
- this.resourceService = resourceService;
- this.pageSize = pageSize;
-
- this.institutionFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- Entity.FILTER_ATTR_INSTITUTION,
- this.resourceService::institutionResource);
-
- this.typeFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- LmsSetup.FILTER_ATTR_LMS_TYPE,
- this.resourceService::lmsTypeResources);
-
- this.activityFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- UserInfo.FILTER_ATTR_ACTIVE,
- this.resourceService::activityResources);
- }
-
- @Override
- public void compose(final PageContext pageContext) {
- final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
- final CurrentUser currentUser = this.resourceService.getCurrentUser();
- final RestService restService = this.resourceService.getRestService();
-
- // content page layout with title
- final Composite content = widgetFactory.defaultPageLayout(
- pageContext.getParent(),
- TITLE_TEXT_KEY);
-
- final boolean isSEBAdmin = currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
- final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
-
- // table
- final EntityTable table =
- this.pageService.entityTableBuilder(restService.getRestCall(GetLmsSetupPage.class))
- .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
- .withPaging(this.pageSize)
- .withColumnIf(
- () -> isSEBAdmin,
- () -> new ColumnDefinition<>(
- Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
- INSTITUTION_TEXT_KEY,
- lmsSetupInstitutionNameFunction(this.resourceService))
- .withFilter(this.institutionFilter))
- .withColumn(new ColumnDefinition<>(
- Domain.LMS_SETUP.ATTR_NAME,
- NAME_TEXT_KEY,
- LmsSetup::getName)
- .withFilter(this.nameFilter)
- .sortable())
- .withColumn(new ColumnDefinition<>(
- Domain.LMS_SETUP.ATTR_LMS_TYPE,
- TYPE_TEXT_KEY,
- this::lmsSetupTypeName)
- .withFilter(this.typeFilter)
- .localized()
- .sortable())
-
- .withColumn(new ColumnDefinition<>(
- Domain.LMS_SETUP.ATTR_ACTIVE,
- ACTIVITY_TEXT_KEY,
- this.pageService.getResourceService(). localizedActivityFunction())
- .withFilter(this.activityFilter)
- .sortable())
- .withDefaultAction(actionBuilder
- .newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
- .create())
- .compose(pageContext.copyOf(content));
-
- // propagate content actions to action-pane
- final GrantCheck userGrant = currentUser.grantCheck(EntityType.LMS_SETUP);
- actionBuilder
-
- .newAction(ActionDefinition.LMS_SETUP_NEW)
- .publishIf(userGrant::iw)
-
- .newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
- .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
- .publishIf(() -> table.hasAnyContent())
-
- .newAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST)
- .withSelect(
- table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
- PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
- .publishIf(() -> userGrant.im() && table.hasAnyContent());
-
- }
-
- private String lmsSetupTypeName(final LmsSetup lmsSetup) {
- if (lmsSetup.lmsType == null) {
- return Constants.EMPTY_NOTE;
- }
-
- return this.resourceService.getI18nSupport()
- .getText(ResourceService.LMSSETUP_TYPE_PREFIX + lmsSetup.lmsType.name());
- }
-
- private static Function lmsSetupInstitutionNameFunction(final ResourceService resourceService) {
- return lmsSetup -> resourceService.getInstitutionNameFunction()
- .apply(String.valueOf(lmsSetup.institutionId));
- }
-
-}
+/*
+ * 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.content;
+
+import java.util.function.Function;
+
+import org.eclipse.swt.widgets.Composite;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.Entity;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
+import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
+import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+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.LocTextKey;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupPage;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
+import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+
+@Lazy
+@Component
+@GuiProfile
+public class LmsSetupList implements TemplateComposer {
+
+ private static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION =
+ new LocTextKey("sebserver.lmssetup.list.action.no.modify.privilege");
+ private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.info.pleaseSelect");
+ private static final LocTextKey ACTIVITY_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.list.column.active");
+ private static final LocTextKey TYPE_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.list.column.type");
+ private static final LocTextKey NAME_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.list.column.name");
+ private static final LocTextKey INSTITUTION_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.list.column.institution");
+ private static final LocTextKey EMPTY_LIST_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.list.empty");
+ private static final LocTextKey TITLE_TEXT_KEY =
+ new LocTextKey("sebserver.lmssetup.list.title");
+
+ private final TableFilterAttribute institutionFilter;
+ private final TableFilterAttribute nameFilter =
+ new TableFilterAttribute(CriteriaType.TEXT, Entity.FILTER_ATTR_NAME);
+ private final TableFilterAttribute typeFilter;
+ private final TableFilterAttribute activityFilter;
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final int pageSize;
+
+ protected LmsSetupList(
+ final PageService pageService,
+ final ResourceService resourceService,
+ @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
+
+ this.pageService = pageService;
+ this.resourceService = resourceService;
+ this.pageSize = pageSize;
+
+ this.institutionFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ Entity.FILTER_ATTR_INSTITUTION,
+ this.resourceService::institutionResource);
+
+ this.typeFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ LmsSetup.FILTER_ATTR_LMS_TYPE,
+ this.resourceService::lmsTypeResources);
+
+ this.activityFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ UserInfo.FILTER_ATTR_ACTIVE,
+ this.resourceService::activityResources);
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
+ final CurrentUser currentUser = this.resourceService.getCurrentUser();
+ final RestService restService = this.resourceService.getRestService();
+
+ // content page layout with title
+ final Composite content = widgetFactory.defaultPageLayout(
+ pageContext.getParent(),
+ TITLE_TEXT_KEY);
+
+ final boolean isSEBAdmin = currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
+ final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
+
+ // table
+ final EntityTable table =
+ this.pageService.entityTableBuilder(restService.getRestCall(GetLmsSetupPage.class))
+ .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
+ .withPaging(this.pageSize)
+ .withColumnIf(
+ () -> isSEBAdmin,
+ () -> new ColumnDefinition<>(
+ Domain.LMS_SETUP.ATTR_INSTITUTION_ID,
+ INSTITUTION_TEXT_KEY,
+ lmsSetupInstitutionNameFunction(this.resourceService))
+ .withFilter(this.institutionFilter))
+ .withColumn(new ColumnDefinition<>(
+ Domain.LMS_SETUP.ATTR_NAME,
+ NAME_TEXT_KEY,
+ LmsSetup::getName)
+ .withFilter(this.nameFilter)
+ .sortable())
+ .withColumn(new ColumnDefinition<>(
+ Domain.LMS_SETUP.ATTR_LMS_TYPE,
+ TYPE_TEXT_KEY,
+ this::lmsSetupTypeName)
+ .withFilter(this.typeFilter)
+ .localized()
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ Domain.LMS_SETUP.ATTR_ACTIVE,
+ ACTIVITY_TEXT_KEY,
+ this.pageService.getResourceService(). localizedActivityFunction())
+ .withFilter(this.activityFilter)
+ .sortable())
+
+ .withDefaultAction(actionBuilder
+ .newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
+ .create())
+
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ ActionDefinition.LMS_SETUP_TOGGLE_ACTIVITY,
+ ActionDefinition.LMS_SETUP_ACTIVATE,
+ ActionDefinition.LMS_SETUP_DEACTIVATE,
+ pageContext,
+ ActionDefinition.LMS_SETUP_VIEW_FROM_LIST,
+ ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST,
+ ActionDefinition.LMS_SETUP_TOGGLE_ACTIVITY))
+
+ .compose(pageContext.copyOf(content));
+
+ // propagate content actions to action-pane
+ final GrantCheck userGrant = currentUser.grantCheck(EntityType.LMS_SETUP);
+ actionBuilder
+
+ .newAction(ActionDefinition.LMS_SETUP_NEW)
+ .publishIf(userGrant::iw)
+
+ .newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
+ .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
+ .publishIf(table::hasAnyContent, false)
+
+ .newAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST)
+ .withSelect(
+ table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION),
+ PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
+ .publishIf(() -> userGrant.im() && table.hasAnyContent(), false)
+
+ .newAction(ActionDefinition.LMS_SETUP_TOGGLE_ACTIVITY)
+ .withExec(this.pageService.activationToggleActionFunction(
+ table,
+ EMPTY_SELECTION_TEXT_KEY,
+ action -> LmsSetupForm.testLmsSetup(action, null, restService)))
+ .withConfirm(this.pageService.confirmDeactivation(table))
+ .publishIf(() -> userGrant.im() && table.hasAnyContent(), false);
+
+ }
+
+ private String lmsSetupTypeName(final LmsSetup lmsSetup) {
+ if (lmsSetup.lmsType == null) {
+ return Constants.EMPTY_NOTE;
+ }
+
+ return this.resourceService.getI18nSupport()
+ .getText(ResourceService.LMSSETUP_TYPE_PREFIX + lmsSetup.lmsType.name());
+ }
+
+ private static Function lmsSetupInstitutionNameFunction(final ResourceService resourceService) {
+ return lmsSetup -> resourceService.getInstitutionNameFunction()
+ .apply(String.valueOf(lmsSetup.institutionId));
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java
index 78afce85..f3129987 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java
@@ -1,258 +1,277 @@
-/*
- * 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.content;
-
-import java.util.Collection;
-
-import org.eclipse.swt.widgets.Composite;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-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.session.ClientConnection.ConnectionStatus;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
-import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-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.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
-import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
-import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
-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.session.GetClientConnectionData;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientEventPage;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
-import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
-import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
-import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
-
-@Lazy
-@Component
-@GuiProfile
-public class MonitoringClientConnection implements TemplateComposer {
-
- private static final LocTextKey PAGE_TITLE_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.title");
-
- private static final LocTextKey EVENT_LIST_TITLE_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.eventlist.title");
- private static final LocTextKey EMPTY_LIST_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.eventlist.empty");
- private static final LocTextKey LIST_COLUMN_TYPE_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.eventlist.type");
- private static final LocTextKey LIST_COLUMN_CLIENT_TIME_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.eventlist.clienttime");
- private static final LocTextKey LIST_COLUMN_SERVER_TIME_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.eventlist.servertime");
- private static final LocTextKey LIST_COLUMN_VALUE_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.eventlist.value");
- private static final LocTextKey LIST_COLUMN_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.eventlist.text");
- private static final LocTextKey CONFIRM_QUIT =
- new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
-
- private final ServerPushService serverPushService;
- private final PageService pageService;
- private final ResourceService resourceService;
- private final I18nSupport i18nSupport;
- private final InstructionProcessor instructionProcessor;
- private final long pollInterval;
- private final int pageSize;
-
- private final TableFilterAttribute typeFilter;
- private final TableFilterAttribute textFilter =
- new TableFilterAttribute(CriteriaType.TEXT, ClientEvent.FILTER_ATTR_TEXT);
-
- protected MonitoringClientConnection(
- final ServerPushService serverPushService,
- final PageService pageService,
- final ResourceService resourceService,
- final InstructionProcessor instructionProcessor,
- @Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval,
- @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
-
- this.serverPushService = serverPushService;
- this.pageService = pageService;
- this.resourceService = resourceService;
- this.i18nSupport = resourceService.getI18nSupport();
- this.instructionProcessor = instructionProcessor;
- this.pollInterval = pollInterval;
- this.pageSize = pageSize;
-
- this.typeFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- Domain.CLIENT_EVENT.ATTR_TYPE,
- this.resourceService::clientEventTypeResources);
- }
-
- @Override
- public void compose(final PageContext pageContext) {
- final RestService restService = this.resourceService.getRestService();
- final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
- final CurrentUser currentUser = this.resourceService.getCurrentUser();
- final EntityKey parentEntityKey = pageContext.getParentEntityKey();
- final EntityKey entityKey = pageContext.getEntityKey();
- final String connectionToken = pageContext.getAttribute(Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN);
-
- if (connectionToken == null) {
- pageContext.notifyUnexpectedError(new IllegalAccessException("connectionToken has null reference"));
- return;
- }
-
- // content page layout with title
- final Composite content = widgetFactory.defaultPageLayout(
- pageContext.getParent(),
- PAGE_TITLE_KEY);
-
- final Exam exam = restService.getBuilder(GetExam.class)
- .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
- .call()
- .onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
- .getOrThrow();
-
- final Collection indicators = restService.getBuilder(GetIndicators.class)
- .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, parentEntityKey.modelId)
- .call()
- .getOrThrow();
-
- final RestCall.RestCallBuilder getConnectionData =
- restService.getBuilder(GetClientConnectionData.class)
- .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
- .withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
-
- final ClientConnectionData connectionData = getConnectionData
- .call()
- .getOrThrow();
-
- final ClientConnectionDetails clientConnectionDetails = new ClientConnectionDetails(
- this.pageService,
- pageContext.copyOf(content),
- exam,
- getConnectionData,
- indicators);
-
- this.serverPushService.runServerPush(
- new ServerPushContext(content, Utils.truePredicate()),
- this.pollInterval,
- clientConnectionDetails::updateData,
- clientConnectionDetails::updateGUI);
-
- widgetFactory.labelLocalized(
- content,
- CustomVariant.TEXT_H3,
- EVENT_LIST_TITLE_KEY);
-
- // client event table for this connection
- this.pageService.entityTableBuilder(restService.getRestCall(GetClientEventPage.class))
- .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
- .withPaging(this.pageSize)
- .withRestCallAdapter(restCallBuilder -> restCallBuilder.withQueryParam(
- ClientEvent.FILTER_ATTR_CONECTION_ID,
- entityKey.modelId))
- .withColumn(new ColumnDefinition(
- Domain.CLIENT_EVENT.ATTR_TYPE,
- LIST_COLUMN_TYPE_KEY,
- this.resourceService::getEventTypeName)
- .withFilter(this.typeFilter)
- .sortable()
- .widthProportion(2))
- .withColumn(new ColumnDefinition<>(
- Domain.CLIENT_EVENT.ATTR_TEXT,
- LIST_COLUMN_TEXT_KEY,
- ClientEvent::getText)
- .withFilter(this.textFilter)
- .sortable()
- .withCellTooltip()
- .widthProportion(4))
- .withColumn(new ColumnDefinition<>(
- Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
- LIST_COLUMN_VALUE_KEY,
- ClientEvent::getValue)
- .widthProportion(1))
- .withColumn(new ColumnDefinition<>(
- Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
- new LocTextKey(LIST_COLUMN_CLIENT_TIME_KEY.name,
- this.i18nSupport.getUsersTimeZoneTitleSuffix()),
- this::getClientTime)
- .sortable()
- .widthProportion(1))
- .withColumn(new ColumnDefinition<>(
- Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
- new LocTextKey(LIST_COLUMN_SERVER_TIME_KEY.name,
- this.i18nSupport.getUsersTimeZoneTitleSuffix()),
- this::getServerTime)
- .sortable()
- .widthProportion(1))
-
- .compose(pageContext.copyOf(content));
-
- this.pageService
- .pageActionBuilder(
- pageContext
- .clearAttributes()
- .clearEntityKeys())
-
- .newAction(ActionDefinition.MONITOR_EXAM_FROM_DETAILS)
- .withEntityKey(parentEntityKey)
- .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER))
-
- .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_QUIT)
- .withConfirm(() -> CONFIRM_QUIT)
- .withExec(action -> {
- this.instructionProcessor.propagateSebQuitInstruction(
- exam.id,
- connectionToken,
- pageContext);
- return action;
- })
- .noEventPropagation()
- .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER) &&
- connectionData.clientConnection.status == ConnectionStatus.ACTIVE);
-
- }
-
- private final String getClientTime(final ClientEvent event) {
- if (event == null || event.getClientTime() == null) {
- return Constants.EMPTY_NOTE;
- }
-
- return this.i18nSupport
- .formatDisplayTime(Utils.toDateTimeUTC(event.getClientTime()));
- }
-
- private final String getServerTime(final ClientEvent event) {
- if (event == null || event.getServerTime() == null) {
- return Constants.EMPTY_NOTE;
- }
-
- return this.i18nSupport
- .formatDisplayTime(Utils.toDateTimeUTC(event.getServerTime()));
- }
-
-}
+/*
+ * 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.content;
+
+import java.util.Collection;
+
+import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
+import org.eclipse.swt.widgets.Composite;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+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.session.ClientConnection.ConnectionStatus;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
+import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+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.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
+import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+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.session.GetClientConnectionData;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientEventPage;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
+import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
+
+@Lazy
+@Component
+@GuiProfile
+public class MonitoringClientConnection implements TemplateComposer {
+
+ private static final LocTextKey PAGE_TITLE_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.title");
+
+ private static final LocTextKey EVENT_LIST_TITLE_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.eventlist.title");
+ private static final LocTextKey EMPTY_LIST_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.eventlist.empty");
+ private static final LocTextKey LIST_COLUMN_TYPE_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.eventlist.type");
+ private static final LocTextKey LIST_COLUMN_CLIENT_TIME_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.eventlist.clienttime");
+ private static final LocTextKey LIST_COLUMN_SERVER_TIME_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.eventlist.servertime");
+ private static final LocTextKey LIST_COLUMN_VALUE_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.eventlist.value");
+ private static final LocTextKey LIST_COLUMN_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.eventlist.text");
+ private static final LocTextKey CONFIRM_QUIT =
+ new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
+
+ private final ServerPushService serverPushService;
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final I18nSupport i18nSupport;
+ private final InstructionProcessor instructionProcessor;
+ private final SebClientLogDetailsPopup sebClientLogDetailsPopup;
+ private final long pollInterval;
+ private final int pageSize;
+
+ private final TableFilterAttribute typeFilter;
+ private final TableFilterAttribute textFilter =
+ new TableFilterAttribute(CriteriaType.TEXT, ClientEvent.FILTER_ATTR_TEXT);
+
+ protected MonitoringClientConnection(
+ final ServerPushService serverPushService,
+ final PageService pageService,
+ final ResourceService resourceService,
+ final InstructionProcessor instructionProcessor,
+ final SebClientLogDetailsPopup sebClientLogDetailsPopup,
+ @Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval,
+ @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
+
+ this.serverPushService = serverPushService;
+ this.pageService = pageService;
+ this.resourceService = resourceService;
+ this.i18nSupport = resourceService.getI18nSupport();
+ this.instructionProcessor = instructionProcessor;
+ this.pollInterval = pollInterval;
+ this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
+ this.pageSize = pageSize;
+
+ this.typeFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ Domain.CLIENT_EVENT.ATTR_TYPE,
+ this.resourceService::clientEventTypeResources);
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final RestService restService = this.resourceService.getRestService();
+ final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
+ final CurrentUser currentUser = this.resourceService.getCurrentUser();
+ final EntityKey parentEntityKey = pageContext.getParentEntityKey();
+ final EntityKey entityKey = pageContext.getEntityKey();
+ final String connectionToken = pageContext.getAttribute(Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN);
+
+ if (connectionToken == null) {
+ pageContext.notifyUnexpectedError(new IllegalAccessException("connectionToken has null reference"));
+ return;
+ }
+
+ // content page layout with title
+ final Composite content = widgetFactory.defaultPageLayout(
+ pageContext.getParent(),
+ PAGE_TITLE_KEY);
+
+ final Exam exam = restService.getBuilder(GetExam.class)
+ .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
+ .call()
+ .onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
+ .getOrThrow();
+
+ final Collection indicators = restService.getBuilder(GetIndicators.class)
+ .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, parentEntityKey.modelId)
+ .call()
+ .getOrThrow();
+
+ final RestCall.RestCallBuilder getConnectionData =
+ restService.getBuilder(GetClientConnectionData.class)
+ .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
+ .withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
+
+ final ClientConnectionData connectionData = getConnectionData
+ .call()
+ .getOrThrow();
+
+ final ClientConnectionDetails clientConnectionDetails = new ClientConnectionDetails(
+ this.pageService,
+ pageContext.copyOf(content),
+ exam,
+ getConnectionData,
+ indicators);
+
+ this.serverPushService.runServerPush(
+ new ServerPushContext(content, Utils.truePredicate()),
+ this.pollInterval,
+ context1 -> clientConnectionDetails.updateData(),
+ context -> clientConnectionDetails.updateGUI());
+
+ widgetFactory.labelLocalized(
+ content,
+ CustomVariant.TEXT_H3,
+ EVENT_LIST_TITLE_KEY);
+
+ PageService.PageActionBuilder actionBuilder = this.pageService
+ .pageActionBuilder(
+ pageContext
+ .clearAttributes()
+ .clearEntityKeys());
+
+
+ // client event table for this connection
+ this.pageService.entityTableBuilder(restService.getRestCall(GetExtendedClientEventPage.class))
+ .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
+ .withPaging(this.pageSize)
+ .withRestCallAdapter(restCallBuilder -> restCallBuilder.withQueryParam(
+ ClientEvent.FILTER_ATTR_CONECTION_ID,
+ entityKey.modelId))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_TYPE,
+ LIST_COLUMN_TYPE_KEY,
+ this.resourceService::getEventTypeName)
+ .withFilter(this.typeFilter)
+ .sortable()
+ .widthProportion(2))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_TEXT,
+ LIST_COLUMN_TEXT_KEY,
+ ClientEvent::getText)
+ .withFilter(this.textFilter)
+ .sortable()
+ .withCellTooltip()
+ .widthProportion(4))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
+ LIST_COLUMN_VALUE_KEY,
+ ClientEvent::getValue)
+ .widthProportion(1))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
+ new LocTextKey(LIST_COLUMN_CLIENT_TIME_KEY.name,
+ this.i18nSupport.getUsersTimeZoneTitleSuffix()),
+ this::getClientTime)
+ .sortable()
+ .widthProportion(1))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
+ new LocTextKey(LIST_COLUMN_SERVER_TIME_KEY.name,
+ this.i18nSupport.getUsersTimeZoneTitleSuffix()),
+ this::getServerTime)
+ .sortable()
+ .widthProportion(1))
+
+ .withDefaultAction(t -> actionBuilder
+ .newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
+ .withExec(action -> sebClientLogDetailsPopup.showDetails(action, t.getSingleSelectedROWData()))
+ .noEventPropagation()
+ .create())
+
+ .compose(pageContext.copyOf(content));
+
+
+ actionBuilder
+ .newAction(ActionDefinition.MONITOR_EXAM_BACK_TO_OVERVIEW)
+ .withEntityKey(parentEntityKey)
+ .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER))
+
+ .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_QUIT)
+ .withConfirm(() -> CONFIRM_QUIT)
+ .withExec(action -> {
+ this.instructionProcessor.propagateSebQuitInstruction(
+ exam.id,
+ connectionToken,
+ pageContext);
+ return action;
+ })
+ .noEventPropagation()
+ .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER) &&
+ connectionData.clientConnection.status == ConnectionStatus.ACTIVE);
+
+ }
+
+ private String getClientTime(final ClientEvent event) {
+ if (event == null || event.getClientTime() == null) {
+ return Constants.EMPTY_NOTE;
+ }
+
+ return this.i18nSupport
+ .formatDisplayTime(Utils.toDateTimeUTC(event.getClientTime()));
+ }
+
+ private String getServerTime(final ClientEvent event) {
+ if (event == null || event.getServerTime() == null) {
+ return Constants.EMPTY_NOTE;
+ }
+
+ return this.i18nSupport
+ .formatDisplayTime(Utils.toDateTimeUTC(event.getServerTime()));
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java
index 2156c827..2f0ac924 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java
@@ -1,355 +1,361 @@
-/*
- * 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.content;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
-import java.util.function.BooleanSupplier;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
-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.api.EntityType;
-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.session.ClientConnection;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
-import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-import ch.ethz.seb.sebserver.gbl.util.Tuple;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-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.LocTextKey;
-import ch.ethz.seb.sebserver.gui.service.page.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
-import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
-import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
-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.session.GetClientConnectionDataList;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
-import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
-import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
-
-@Lazy
-@Component
-@GuiProfile
-public class MonitoringRunningExam implements TemplateComposer {
-
- private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class);
-
- private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.emptySelection");
- private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.exam.connection.emptySelection.active");
- private static final LocTextKey CONFIRM_QUIT_SELECTED =
- new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected.confirm");
- private static final LocTextKey CONFIRM_QUIT_ALL =
- new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm");
- private static final LocTextKey CONFIRM_DISABLE_SELECTED =
- new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm");
-
- private final ServerPushService serverPushService;
- private final PageService pageService;
- private final ResourceService resourceService;
- private final InstructionProcessor instructionProcessor;
- private final long pollInterval;
-
- protected MonitoringRunningExam(
- final ServerPushService serverPushService,
- final PageService pageService,
- final ResourceService resourceService,
- final InstructionProcessor instructionProcessor,
- @Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval) {
-
- this.serverPushService = serverPushService;
- this.pageService = pageService;
- this.resourceService = resourceService;
- this.instructionProcessor = instructionProcessor;
- this.pollInterval = pollInterval;
- }
-
- @Override
- public void compose(final PageContext pageContext) {
- final RestService restService = this.resourceService.getRestService();
- final EntityKey entityKey = pageContext.getEntityKey();
- final CurrentUser currentUser = this.resourceService.getCurrentUser();
- final Exam exam = restService.getBuilder(GetExam.class)
- .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
- .call()
- .getOrThrow();
-
- final Collection indicators = restService.getBuilder(GetIndicators.class)
- .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, entityKey.modelId)
- .call()
- .getOrThrow();
-
- final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
- pageContext.getParent(),
- new LocTextKey("sebserver.monitoring.exam", exam.name));
-
- final Composite tablePane = new Composite(content, SWT.NONE);
- tablePane.setLayout(new GridLayout());
- final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
- gridData.heightHint = 100;
- tablePane.setLayoutData(gridData);
-
- final PageActionBuilder actionBuilder = this.pageService
- .pageActionBuilder(pageContext.clearEntityKeys());
-
- final RestCall>.RestCallBuilder restCall =
- restService.getBuilder(GetClientConnectionDataList.class)
- .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId());
-
- final ClientConnectionTable clientTable = new ClientConnectionTable(
- this.pageService,
- tablePane,
- exam,
- indicators,
- restCall);
-
- clientTable.withDefaultAction(
- actionBuilder
- .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
- .withParentEntityKey(entityKey)
- .create(),
- this.pageService);
-
- this.serverPushService.runServerPush(
- new ServerPushContext(content, Utils.truePredicate()),
- this.pollInterval,
- context -> clientTable.updateValues(),
- updateTableGUI(clientTable));
-
- final BooleanSupplier privilege = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
-
- actionBuilder
-
- .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
- .withParentEntityKey(entityKey)
- .withExec(pageAction -> {
- final Tuple singleSelection = clientTable.getSingleSelection();
- if (singleSelection == null) {
- throw new PageMessageException(EMPTY_SELECTION_TEXT_KEY);
- }
-
- final PageAction copyOfPageAction = PageAction.copyOf(pageAction);
- copyOfPageAction.withEntityKey(new EntityKey(
- singleSelection._1,
- EntityType.CLIENT_CONNECTION));
- copyOfPageAction.withAttribute(
- Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
- singleSelection._2);
-
- return copyOfPageAction;
- })
- .publishIf(privilege)
-
- .newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL)
- .withEntityKey(entityKey)
- .withConfirm(() -> CONFIRM_QUIT_ALL)
- .withExec(action -> this.quitSebClients(action, clientTable, true))
- .noEventPropagation()
- .publishIf(privilege)
-
- .newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
- .withEntityKey(entityKey)
- .withConfirm(() -> CONFIRM_QUIT_SELECTED)
- .withSelect(
- () -> this.selectionForQuitInstruction(clientTable),
- action -> this.quitSebClients(action, clientTable, false),
- EMPTY_ACTIVE_SELECTION_TEXT_KEY)
- .noEventPropagation()
- .publishIf(privilege)
-
- .newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION)
- .withEntityKey(entityKey)
- .withConfirm(() -> CONFIRM_DISABLE_SELECTED)
- .withSelect(
- clientTable::getSelection,
- action -> this.disableSebClients(action, clientTable, false),
- EMPTY_SELECTION_TEXT_KEY)
- .noEventPropagation()
- .publishIf(privilege);
-
- if (privilege.getAsBoolean()) {
-
- if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
- .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
- .noEventPropagation()
- .withSwitchAction(
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
- .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
- .noEventPropagation()
- .create())
- .publish();
- } else {
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
- .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
- .noEventPropagation()
- .withSwitchAction(
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
- .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
- .noEventPropagation()
- .create())
- .publish();
- }
-
- if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) {
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
- .withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
- .noEventPropagation()
- .withSwitchAction(
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
- .withExec(
- hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
- .noEventPropagation()
- .create())
- .publish();
- } else {
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
- .withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
- .noEventPropagation()
- .withSwitchAction(
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
- .withExec(
- showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
- .noEventPropagation()
- .create())
- .publish();
- }
-
- if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
- .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
- .noEventPropagation()
- .withSwitchAction(
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
- .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
- .noEventPropagation()
- .create())
- .publish();
- } else {
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
- .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
- .noEventPropagation()
- .withSwitchAction(
- actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
- .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
- .noEventPropagation()
- .create())
- .publish();
- }
-
- }
- }
-
- private static final Function showStateViewAction(
- final ClientConnectionTable clientTable,
- final ConnectionStatus status) {
-
- return action -> {
- clientTable.showStatus(status);
- clientTable.removeSelection();
- return action;
- };
- }
-
- private static final Function hideStateViewAction(
- final ClientConnectionTable clientTable,
- final ConnectionStatus status) {
-
- return action -> {
- clientTable.hideStatus(status);
- clientTable.removeSelection();
- return action;
- };
- }
-
- private Set selectionForQuitInstruction(final ClientConnectionTable clientTable) {
- final Set connectionTokens = clientTable.getConnectionTokens(
- ClientConnection.getStatusPredicate(ConnectionStatus.ACTIVE),
- true);
- if (connectionTokens == null || connectionTokens.isEmpty()) {
- return Collections.emptySet();
- }
-
- return clientTable.getSelection();
- }
-
- private PageAction quitSebClients(
- final PageAction action,
- final ClientConnectionTable clientTable,
- final boolean all) {
-
- this.instructionProcessor.propagateSebQuitInstruction(
- clientTable.getExam().id,
- statesPredicate -> clientTable.getConnectionTokens(
- statesPredicate,
- !all),
- action.pageContext());
-
- clientTable.removeSelection();
- return action;
- }
-
- private PageAction disableSebClients(
- final PageAction action,
- final ClientConnectionTable clientTable,
- final boolean all) {
-
- this.instructionProcessor.disableConnection(
- clientTable.getExam().id,
- statesPredicate -> clientTable.getConnectionTokens(
- statesPredicate,
- !all),
- action.pageContext());
-
- clientTable.removeSelection();
- return action;
- }
-
- private final Consumer updateTableGUI(final ClientConnectionTable clientTable) {
- return context -> {
- if (!context.isDisposed()) {
- try {
- clientTable.updateGUI();
- context.layout();
- } catch (final Exception e) {
- if (log.isWarnEnabled()) {
- log.warn("Unexpected error while trying to update GUI: ", e);
- }
- }
- }
- };
- }
-
-}
+/*
+ * 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.content;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+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.api.EntityType;
+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.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
+import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gbl.util.Tuple;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+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.LocTextKey;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
+import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+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.session.GetClientConnectionDataList;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
+import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
+
+@Lazy
+@Component
+@GuiProfile
+public class MonitoringRunningExam implements TemplateComposer {
+
+ private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class);
+
+ private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.emptySelection");
+ private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.exam.connection.emptySelection.active");
+ private static final LocTextKey CONFIRM_QUIT_SELECTED =
+ new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected.confirm");
+ private static final LocTextKey CONFIRM_QUIT_ALL =
+ new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm");
+ private static final LocTextKey CONFIRM_DISABLE_SELECTED =
+ new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm");
+
+ private final ServerPushService serverPushService;
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final InstructionProcessor instructionProcessor;
+ private final long pollInterval;
+
+ protected MonitoringRunningExam(
+ final ServerPushService serverPushService,
+ final PageService pageService,
+ final ResourceService resourceService,
+ final InstructionProcessor instructionProcessor,
+ @Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval) {
+
+ this.serverPushService = serverPushService;
+ this.pageService = pageService;
+ this.resourceService = resourceService;
+ this.instructionProcessor = instructionProcessor;
+ this.pollInterval = pollInterval;
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final RestService restService = this.resourceService.getRestService();
+ final EntityKey entityKey = pageContext.getEntityKey();
+ final CurrentUser currentUser = this.resourceService.getCurrentUser();
+ final Exam exam = restService.getBuilder(GetExam.class)
+ .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
+ .call()
+ .getOrThrow();
+
+ final Collection indicators = restService.getBuilder(GetIndicators.class)
+ .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, entityKey.modelId)
+ .call()
+ .getOrThrow();
+
+ final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
+ pageContext.getParent(),
+ new LocTextKey("sebserver.monitoring.exam", exam.name));
+
+ final Composite tablePane = new Composite(content, SWT.NONE);
+ tablePane.setLayout(new GridLayout());
+ final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
+ gridData.heightHint = 100;
+ tablePane.setLayoutData(gridData);
+
+ final PageActionBuilder actionBuilder = this.pageService
+ .pageActionBuilder(pageContext.clearEntityKeys());
+
+ final RestCall>.RestCallBuilder restCall =
+ restService.getBuilder(GetClientConnectionDataList.class)
+ .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId());
+
+ final ClientConnectionTable clientTable = new ClientConnectionTable(
+ this.pageService,
+ tablePane,
+ exam,
+ indicators,
+ restCall);
+
+ clientTable
+ .withDefaultAction(
+ actionBuilder
+ .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
+ .withParentEntityKey(entityKey)
+ .create(),
+ this.pageService)
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ pageContext,
+ ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION,
+ ActionDefinition.MONITOR_EXAM_QUIT_SELECTED,
+ ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION));
+
+ this.serverPushService.runServerPush(
+ new ServerPushContext(content, Utils.truePredicate()),
+ this.pollInterval,
+ context -> clientTable.updateValues(),
+ updateTableGUI(clientTable));
+
+ final BooleanSupplier privilege = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
+
+ actionBuilder
+
+ .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
+ .withParentEntityKey(entityKey)
+ .withExec(pageAction -> {
+ final Tuple singleSelection = clientTable.getSingleSelection();
+ if (singleSelection == null) {
+ throw new PageMessageException(EMPTY_SELECTION_TEXT_KEY);
+ }
+
+ final PageAction copyOfPageAction = PageAction.copyOf(pageAction);
+ copyOfPageAction.withEntityKey(new EntityKey(
+ singleSelection._1,
+ EntityType.CLIENT_CONNECTION));
+ copyOfPageAction.withAttribute(
+ Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
+ singleSelection._2);
+
+ return copyOfPageAction;
+ })
+ .publishIf(privilege, false)
+
+ .newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL)
+ .withEntityKey(entityKey)
+ .withConfirm(() -> CONFIRM_QUIT_ALL)
+ .withExec(action -> this.quitSebClients(action, clientTable, true))
+ .noEventPropagation()
+ .publishIf(privilege)
+
+ .newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
+ .withEntityKey(entityKey)
+ .withConfirm(() -> CONFIRM_QUIT_SELECTED)
+ .withSelect(
+ () -> this.selectionForQuitInstruction(clientTable),
+ action -> this.quitSebClients(action, clientTable, false),
+ EMPTY_ACTIVE_SELECTION_TEXT_KEY)
+ .noEventPropagation()
+ .publishIf(privilege, false)
+
+ .newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION)
+ .withEntityKey(entityKey)
+ .withConfirm(() -> CONFIRM_DISABLE_SELECTED)
+ .withSelect(
+ clientTable::getSelection,
+ action -> this.disableSebClients(action, clientTable, false),
+ EMPTY_SELECTION_TEXT_KEY)
+ .noEventPropagation()
+ .publishIf(privilege, false);
+
+ if (privilege.getAsBoolean()) {
+
+ if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
+ .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
+ .noEventPropagation()
+ .withSwitchAction(
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
+ .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
+ .noEventPropagation()
+ .create())
+ .publish();
+ } else {
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
+ .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
+ .noEventPropagation()
+ .withSwitchAction(
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
+ .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
+ .noEventPropagation()
+ .create())
+ .publish();
+ }
+
+ if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) {
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
+ .withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
+ .noEventPropagation()
+ .withSwitchAction(
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
+ .withExec(
+ hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
+ .noEventPropagation()
+ .create())
+ .publish();
+ } else {
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
+ .withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
+ .noEventPropagation()
+ .withSwitchAction(
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
+ .withExec(
+ showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
+ .noEventPropagation()
+ .create())
+ .publish();
+ }
+
+ if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
+ .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
+ .noEventPropagation()
+ .withSwitchAction(
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
+ .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
+ .noEventPropagation()
+ .create())
+ .publish();
+ } else {
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
+ .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
+ .noEventPropagation()
+ .withSwitchAction(
+ actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
+ .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
+ .noEventPropagation()
+ .create())
+ .publish();
+ }
+
+ }
+ }
+
+ private static Function showStateViewAction(
+ final ClientConnectionTable clientTable,
+ final ConnectionStatus status) {
+
+ return action -> {
+ clientTable.showStatus(status);
+ clientTable.removeSelection();
+ return action;
+ };
+ }
+
+ private static Function hideStateViewAction(
+ final ClientConnectionTable clientTable,
+ final ConnectionStatus status) {
+
+ return action -> {
+ clientTable.hideStatus(status);
+ clientTable.removeSelection();
+ return action;
+ };
+ }
+
+ private Set selectionForQuitInstruction(final ClientConnectionTable clientTable) {
+ final Set connectionTokens = clientTable.getConnectionTokens(
+ ClientConnection.getStatusPredicate(ConnectionStatus.ACTIVE),
+ true);
+ if (connectionTokens == null || connectionTokens.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ return clientTable.getSelection();
+ }
+
+ private PageAction quitSebClients(
+ final PageAction action,
+ final ClientConnectionTable clientTable,
+ final boolean all) {
+
+ this.instructionProcessor.propagateSebQuitInstruction(
+ clientTable.getExam().id,
+ statesPredicate -> clientTable.getConnectionTokens(
+ statesPredicate,
+ !all),
+ action.pageContext());
+
+ clientTable.removeSelection();
+ return action;
+ }
+
+ private PageAction disableSebClients(
+ final PageAction action,
+ final ClientConnectionTable clientTable,
+ final boolean all) {
+
+ this.instructionProcessor.disableConnection(
+ clientTable.getExam().id,
+ statesPredicate -> clientTable.getConnectionTokens(
+ statesPredicate,
+ !all),
+ action.pageContext());
+
+ clientTable.removeSelection();
+ return action;
+ }
+
+ private Consumer updateTableGUI(final ClientConnectionTable clientTable) {
+ return context -> {
+ if (!context.isDisposed()) {
+ try {
+ clientTable.updateGUI();
+ context.layout();
+ } catch (final Exception e) {
+ if (log.isWarnEnabled()) {
+ log.warn("Unexpected error while trying to update GUI: ", e);
+ }
+ }
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExamList.java
index bf01aebc..9ecc734e 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExamList.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExamList.java
@@ -1,144 +1,148 @@
-/*
- * 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.content;
-
-import org.eclipse.swt.widgets.Composite;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-
-import ch.ethz.seb.sebserver.gbl.model.Domain;
-import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
-import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
-import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-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.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
-import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetRunningExamPage;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
-import ch.ethz.seb.sebserver.gui.table.EntityTable;
-import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-
-@Lazy
-@Component
-@GuiProfile
-public class MonitoringRunningExamList implements TemplateComposer {
-
- private static final LocTextKey PAGE_TITLE_KEY =
- new LocTextKey("sebserver.monitoring.exam.list.title");
- private final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.exam.info.pleaseSelect");
- private final static LocTextKey COLUMN_TITLE_NAME_KEY =
- new LocTextKey("sebserver.monitoring.exam.list.column.name");
- private final static LocTextKey COLUMN_TITLE_TYPE_KEY =
- new LocTextKey("sebserver.monitoring.exam.list.column.type");
- private final static LocTextKey EMPTY_LIST_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.exam.list.empty");
-
- private final TableFilterAttribute nameFilter =
- new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
- private final TableFilterAttribute typeFilter;
-
- private final PageService pageService;
- private final ResourceService resourceService;
- private final int pageSize;
-
- protected MonitoringRunningExamList(
- final PageService pageService,
- final ResourceService resourceService,
- @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
-
- this.pageService = pageService;
- this.resourceService = resourceService;
- this.pageSize = pageSize;
-
- this.typeFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- Exam.FILTER_ATTR_TYPE,
- this.resourceService::examTypeResources);
- }
-
- @Override
- public void compose(final PageContext pageContext) {
- final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
- final CurrentUser currentUser = this.resourceService.getCurrentUser();
- final RestService restService = this.resourceService.getRestService();
- final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
-
- // content page layout with title
- final Composite content = widgetFactory.defaultPageLayout(
- pageContext.getParent(),
- PAGE_TITLE_KEY);
-
- final PageActionBuilder actionBuilder = this.pageService
- .pageActionBuilder(pageContext.clearEntityKeys());
-
- // table
- final EntityTable table =
- this.pageService.entityTableBuilder(restService.getRestCall(GetRunningExamPage.class))
- .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
- .withPaging(this.pageSize)
- .withRowDecorator(ExamList.decorateOnExamConsistency(this.pageService))
-
- .withColumn(new ColumnDefinition<>(
- QuizData.QUIZ_ATTR_NAME,
- COLUMN_TITLE_NAME_KEY,
- Exam::getName)
- .withFilter(this.nameFilter)
- .sortable())
-
- .withColumn(new ColumnDefinition(
- Domain.EXAM.ATTR_TYPE,
- COLUMN_TITLE_TYPE_KEY,
- this.resourceService::localizedExamTypeName)
- .withFilter(this.typeFilter)
- .sortable())
-
- .withColumn(new ColumnDefinition<>(
- QuizData.QUIZ_ATTR_START_TIME,
- new LocTextKey(
- "sebserver.monitoring.exam.list.column.startTime",
- i18nSupport.getUsersTimeZoneTitleSuffix()),
- Exam::getStartTime)
- .sortable())
-
- .withColumn(new ColumnDefinition<>(
- QuizData.QUIZ_ATTR_END_TIME,
- new LocTextKey(
- "sebserver.monitoring.exam.list.column.endTime",
- i18nSupport.getUsersTimeZoneTitleSuffix()),
- Exam::getEndTime)
- .sortable())
-
- .withDefaultAction(actionBuilder
- .newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
- .create())
-
- .compose(pageContext.copyOf(content));
-
- actionBuilder
-
- .newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
- .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
- .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER));
-
- }
-
-}
+/*
+ * 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.content;
+
+import org.eclipse.swt.widgets.Composite;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
+import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+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.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetRunningExamPage;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
+import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+
+@Lazy
+@Component
+@GuiProfile
+public class MonitoringRunningExamList implements TemplateComposer {
+
+ private static final LocTextKey PAGE_TITLE_KEY =
+ new LocTextKey("sebserver.monitoring.exam.list.title");
+ private final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.exam.info.pleaseSelect");
+ private final static LocTextKey COLUMN_TITLE_NAME_KEY =
+ new LocTextKey("sebserver.monitoring.exam.list.column.name");
+ private final static LocTextKey COLUMN_TITLE_TYPE_KEY =
+ new LocTextKey("sebserver.monitoring.exam.list.column.type");
+ private final static LocTextKey EMPTY_LIST_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.exam.list.empty");
+
+ private final TableFilterAttribute nameFilter =
+ new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
+ private final TableFilterAttribute typeFilter;
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final int pageSize;
+
+ protected MonitoringRunningExamList(
+ final PageService pageService,
+ final ResourceService resourceService,
+ @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
+
+ this.pageService = pageService;
+ this.resourceService = resourceService;
+ this.pageSize = pageSize;
+
+ this.typeFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ Exam.FILTER_ATTR_TYPE,
+ this.resourceService::examTypeResources);
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
+ final CurrentUser currentUser = this.resourceService.getCurrentUser();
+ final RestService restService = this.resourceService.getRestService();
+ final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
+
+ // content page layout with title
+ final Composite content = widgetFactory.defaultPageLayout(
+ pageContext.getParent(),
+ PAGE_TITLE_KEY);
+
+ final PageActionBuilder actionBuilder = this.pageService
+ .pageActionBuilder(pageContext.clearEntityKeys());
+
+ // table
+ final EntityTable table =
+ this.pageService.entityTableBuilder(restService.getRestCall(GetRunningExamPage.class))
+ .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
+ .withPaging(this.pageSize)
+ .withRowDecorator(ExamList.decorateOnExamConsistency(this.pageService))
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_NAME,
+ COLUMN_TITLE_NAME_KEY,
+ Exam::getName)
+ .withFilter(this.nameFilter)
+ .sortable())
+
+ .withColumn(new ColumnDefinition(
+ Domain.EXAM.ATTR_TYPE,
+ COLUMN_TITLE_TYPE_KEY,
+ this.resourceService::localizedExamTypeName)
+ .withFilter(this.typeFilter)
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_START_TIME,
+ new LocTextKey(
+ "sebserver.monitoring.exam.list.column.startTime",
+ i18nSupport.getUsersTimeZoneTitleSuffix()),
+ Exam::getStartTime)
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_END_TIME,
+ new LocTextKey(
+ "sebserver.monitoring.exam.list.column.endTime",
+ i18nSupport.getUsersTimeZoneTitleSuffix()),
+ Exam::getEndTime)
+ .sortable())
+
+ .withDefaultAction(actionBuilder
+ .newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
+ .create())
+
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ pageContext,
+ ActionDefinition.MONITOR_EXAM_FROM_LIST))
+
+ .compose(pageContext.copyOf(content));
+
+ actionBuilder
+
+ .newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
+ .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
+ .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
+
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java
index 3fc04ca1..18d69c43 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java
@@ -1,367 +1,373 @@
-/*
- * 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.content;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.function.BooleanSupplier;
-import java.util.function.Function;
-
-import org.eclipse.swt.widgets.Composite;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.model.Entity;
-import ch.ethz.seb.sebserver.gbl.model.EntityKey;
-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.user.UserRole;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
-import ch.ethz.seb.sebserver.gui.form.FormBuilder;
-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.PageContext.AttributeKeys;
-import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
-import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
-import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizPage;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
-import ch.ethz.seb.sebserver.gui.table.EntityTable;
-import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-
-@Lazy
-@Component
-@GuiProfile
-public class QuizDiscoveryList implements TemplateComposer {
-
- // localized text keys
- private static final LocTextKey QUIZ_DETAILS_URL_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.quiz.details.url");
- private static final LocTextKey QUIZ_DETAILS_DESCRIPTION_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.quiz.details.description");
- private static final LocTextKey QUIZ_DETAILS_START_TIME_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.quiz.details.starttime");
- private static final LocTextKey QUIZ_DETAILS_END_TIME_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.quiz.details.endtime");
- private static final LocTextKey TITLE_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.list.title");
- private static final LocTextKey EMPTY_LIST_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.list.empty");
- private final static LocTextKey EMPTY_SELECTION_TEXT =
- new LocTextKey("sebserver.quizdiscovery.info.pleaseSelect");
- private final static LocTextKey INSTITUION_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.list.column.institution");
- private final static LocTextKey LMS_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.list.column.lmssetup");
- private final static LocTextKey NAME_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.list.column.name");
- private final static LocTextKey START_TIME_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.list.column.starttime");
- private final static LocTextKey END_TIME_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.list.column.endtime");
- private final static LocTextKey DETAILS_TITLE_TEXT_KEY =
- new LocTextKey("sebserver.quizdiscovery.quiz.details.title");
- private final static LocTextKey NO_IMPORT_OF_OUT_DATED_QUIZ =
- new LocTextKey("sebserver.quizdiscovery.quiz.import.out.dated");
-
- private final static String TEXT_KEY_ADDITIONAL_ATTR_PREFIX =
- "sebserver.quizdiscovery.quiz.details.additional.";
-
- // filter attribute models
- private final TableFilterAttribute institutionFilter;
- private final TableFilterAttribute lmsFilter;
- private final TableFilterAttribute nameFilter =
- new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
-
- // dependencies
- private final WidgetFactory widgetFactory;
- private final ResourceService resourceService;
- private final PageService pageService;
- private final int pageSize;
-
- protected QuizDiscoveryList(
- final PageService pageService,
- final ResourceService resourceService,
- @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
-
- this.pageService = pageService;
- this.widgetFactory = pageService.getWidgetFactory();
- this.resourceService = resourceService;
- this.pageSize = pageSize;
-
- this.institutionFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- Entity.FILTER_ATTR_INSTITUTION,
- this.resourceService::institutionResource);
-
- this.lmsFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- LmsSetup.FILTER_ATTR_LMS_SETUP,
- this.resourceService::lmsSetupResource);
- }
-
- @Override
- public void compose(final PageContext pageContext) {
- final CurrentUser currentUser = this.resourceService.getCurrentUser();
- final RestService restService = this.resourceService.getRestService();
- final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
-
- // content page layout with title
- final Composite content = this.widgetFactory.defaultPageLayout(
- pageContext.getParent(),
- TITLE_TEXT_KEY);
-
- final PageActionBuilder actionBuilder =
- this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
-
- final BooleanSupplier isSebAdmin =
- () -> currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
-
- final Function institutionNameFunction =
- this.resourceService.getInstitutionNameFunction();
-
- // table
- final EntityTable table =
- this.pageService.entityTableBuilder(restService.getRestCall(GetQuizPage.class))
- .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
- .withPaging(this.pageSize)
-
- .withColumnIf(
- isSebAdmin,
- () -> new ColumnDefinition(
- QuizData.QUIZ_ATTR_INSTITUION_ID,
- INSTITUION_TEXT_KEY,
- quiz -> institutionNameFunction
- .apply(String.valueOf(quiz.institutionId)))
- .withFilter(this.institutionFilter))
-
- .withColumn(new ColumnDefinition<>(
- QuizData.QUIZ_ATTR_LMS_SETUP_ID,
- LMS_TEXT_KEY,
- quizDataLmsSetupNameFunction(this.resourceService))
- .withFilter(this.lmsFilter)
- .sortable())
-
- .withColumn(new ColumnDefinition<>(
- QuizData.QUIZ_ATTR_NAME,
- NAME_TEXT_KEY,
- QuizData::getName)
- .withFilter(this.nameFilter)
- .sortable())
-
- .withColumn(new ColumnDefinition<>(
- QuizData.QUIZ_ATTR_START_TIME,
- new LocTextKey(
- START_TIME_TEXT_KEY.name,
- i18nSupport.getUsersTimeZoneTitleSuffix()),
- QuizData::getStartTime)
- .withFilter(new TableFilterAttribute(
- CriteriaType.DATE,
- QuizData.FILTER_ATTR_START_TIME,
- Utils.toDateTimeUTC(Utils.getMillisecondsNow())
- .minusYears(1)
- .toString()))
- .sortable())
-
- .withColumn(new ColumnDefinition<>(
- QuizData.QUIZ_ATTR_END_TIME,
- new LocTextKey(
- END_TIME_TEXT_KEY.name,
- i18nSupport.getUsersTimeZoneTitleSuffix()),
- QuizData::getEndTime)
- .sortable())
-
- .withDefaultAction(t -> actionBuilder
- .newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
- .withExec(action -> this.showDetails(
- action,
- t.getSingleSelectedROWData(),
- institutionNameFunction))
- .noEventPropagation()
- .create())
-
- .compose(pageContext.copyOf(content));
-
- // propagate content actions to action-pane
- final GrantCheck examGrant = currentUser.grantCheck(EntityType.EXAM);
- actionBuilder
-
-// Removed as discussed in SEBSERV-52
-// .newAction(ActionDefinition.LMS_SETUP_NEW)
-// .publishIf(lmsSetupGrant::iw)
-
- .newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
- .withSelect(
- table::getSelection,
- action -> this.showDetails(
- action,
- table.getSingleSelectedROWData(),
- institutionNameFunction),
- EMPTY_SELECTION_TEXT)
- .noEventPropagation()
- .publishIf(table::hasAnyContent)
-
- .newAction(ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT)
- .withSelect(
- table::getSelection,
- action -> this.importQuizData(action, table),
- EMPTY_SELECTION_TEXT)
- .publishIf(() -> examGrant.im() && table.hasAnyContent());
- }
-
- private static Function quizDataLmsSetupNameFunction(final ResourceService resourceService) {
- return quizzData -> resourceService.getLmsSetupNameFunction()
- .apply(String.valueOf(quizzData.lmsSetupId));
- }
-
- private PageAction importQuizData(final PageAction action, final EntityTable table) {
- action.getSingleSelection();
- final QuizData selectedROWData = table.getSingleSelectedROWData();
-
- if (selectedROWData.endTime != null) {
- final DateTime now = DateTime.now(DateTimeZone.UTC);
- if (selectedROWData.endTime.isBefore(now)) {
- throw new PageMessageException(NO_IMPORT_OF_OUT_DATED_QUIZ);
- }
- }
-
- return action
- .withEntityKey(action.getSingleSelection())
- .withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP))
- .withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, "true");
- }
-
- private PageAction showDetails(
- final PageAction action,
- final QuizData quizData,
- final Function institutionNameFunction) {
-
- action.getSingleSelection();
-
- final ModalInputDialog dialog = new ModalInputDialog(
- action.pageContext().getParent().getShell(),
- this.widgetFactory)
- .setLargeDialogWidth();
-
- dialog.open(
- DETAILS_TITLE_TEXT_KEY,
- action.pageContext(),
- pc -> createDetailsForm(quizData, pc, institutionNameFunction));
-
- return action;
- }
-
- private static final Collection ADDITIONAL_HTML_ATTRIBUTES = Arrays.asList(
- "course_summary");
-
- private void createDetailsForm(
- final QuizData quizData,
- final PageContext pc,
- final Function institutionNameFunction) {
-
- final Composite parent = pc.getParent();
- final Composite grid = this.widgetFactory.createPopupScrollComposite(parent);
-
- final FormBuilder formbuilder = this.pageService.formBuilder(pc.copyOf(grid))
- .withDefaultSpanInput(6)
- .withEmptyCellSeparation(false)
- .readonly(true)
- .addFieldIf(
- () -> this.resourceService.getCurrentUser().get().hasRole(UserRole.SEB_SERVER_ADMIN),
- () -> FormBuilder.text(
- QuizData.QUIZ_ATTR_INSTITUION_ID,
- INSTITUION_TEXT_KEY,
- institutionNameFunction.apply(quizData.getModelId())))
- .addField(FormBuilder.singleSelection(
- QuizData.QUIZ_ATTR_LMS_SETUP_ID,
- LMS_TEXT_KEY,
- String.valueOf(quizData.lmsSetupId),
- () -> this.resourceService.lmsSetupResource()))
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_NAME,
- NAME_TEXT_KEY,
- quizData.name))
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_DESCRIPTION,
- QUIZ_DETAILS_DESCRIPTION_TEXT_KEY,
- quizData.description)
- .asHTML())
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_START_TIME,
- QUIZ_DETAILS_START_TIME_TEXT_KEY,
- this.widgetFactory.getI18nSupport().formatDisplayDateWithTimeZone(quizData.startTime)))
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_END_TIME,
- QUIZ_DETAILS_END_TIME_TEXT_KEY,
- this.widgetFactory.getI18nSupport().formatDisplayDateWithTimeZone(quizData.endTime)))
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_START_URL,
- QUIZ_DETAILS_URL_TEXT_KEY,
- quizData.startURL));
-
- if (!quizData.additionalAttributes.isEmpty()) {
- quizData.additionalAttributes
- .entrySet()
- .stream()
- .forEach(entry -> {
- LocTextKey titleKey = new LocTextKey(TEXT_KEY_ADDITIONAL_ATTR_PREFIX + entry.getKey());
- if (!this.pageService.getI18nSupport().hasText(titleKey)) {
- titleKey = new LocTextKey(entry.getKey());
- }
- formbuilder
- .addField(FormBuilder.text(
- entry.getKey(),
- titleKey,
- toAdditionalValue(entry.getKey(), entry.getValue()))
- .asHTML(ADDITIONAL_HTML_ATTRIBUTES.contains(entry.getKey())));
- });
- }
-
- formbuilder.build();
- }
-
- private String toAdditionalValue(final String name, final String value) {
- if ("timecreated".equals(name)) {
- try {
- return this.pageService
- .getI18nSupport()
- .formatDisplayDate(Utils.toDateTimeUTCUnix(Long.parseLong(value)));
- } catch (final Exception e) {
- return value;
- }
- } else if ("timelimit".equals(name)) {
- try {
- return this.pageService
- .getI18nSupport()
- .formatDisplayTime(Utils.toDateTimeUTCUnix(Long.parseLong(value)));
- } catch (final Exception e) {
- return value;
- }
- } else {
- return value;
- }
- }
-
-}
+/*
+ * 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.content;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import org.eclipse.swt.widgets.Composite;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Entity;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+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.user.UserRole;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
+import ch.ethz.seb.sebserver.gui.form.FormBuilder;
+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.PageContext.AttributeKeys;
+import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizPage;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
+import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+
+@Lazy
+@Component
+@GuiProfile
+public class QuizDiscoveryList implements TemplateComposer {
+
+ // localized text keys
+
+ private static final LocTextKey TITLE_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.list.title");
+ private static final LocTextKey EMPTY_LIST_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.list.empty");
+ private final static LocTextKey EMPTY_SELECTION_TEXT =
+ new LocTextKey("sebserver.quizdiscovery.info.pleaseSelect");
+ private final static LocTextKey INSTITUTION_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.list.column.institution");
+ private final static LocTextKey LMS_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.list.column.lmssetup");
+ private final static LocTextKey NAME_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.list.column.name");
+ private final static LocTextKey START_TIME_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.list.column.starttime");
+ private final static LocTextKey END_TIME_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.list.column.endtime");
+ private final static LocTextKey DETAILS_TITLE_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.quiz.details.title");
+ private static final LocTextKey QUIZ_DETAILS_URL_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.quiz.details.url");
+ private static final LocTextKey QUIZ_DETAILS_INSTITUTION_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.quiz.details.institution");
+ private static final LocTextKey QUIZ_DETAILS_LMS_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.quiz.details.lmssetup");
+ private static final LocTextKey QUIZ_DETAILS_NAME_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.quiz.details.name");
+ private static final LocTextKey QUIZ_DETAILS_DESCRIPTION_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.quiz.details.description");
+ private static final LocTextKey QUIZ_DETAILS_START_TIME_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.quiz.details.starttime");
+ private static final LocTextKey QUIZ_DETAILS_END_TIME_TEXT_KEY =
+ new LocTextKey("sebserver.quizdiscovery.quiz.details.endtime");
+ private final static LocTextKey NO_IMPORT_OF_OUT_DATED_QUIZ =
+ new LocTextKey("sebserver.quizdiscovery.quiz.import.out.dated");
+
+ private final static String TEXT_KEY_ADDITIONAL_ATTR_PREFIX =
+ "sebserver.quizdiscovery.quiz.details.additional.";
+
+ // filter attribute models
+ private final TableFilterAttribute institutionFilter;
+ private final TableFilterAttribute lmsFilter;
+ private final TableFilterAttribute nameFilter =
+ new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
+
+ // dependencies
+ private final WidgetFactory widgetFactory;
+ private final ResourceService resourceService;
+ private final PageService pageService;
+ private final int pageSize;
+
+ protected QuizDiscoveryList(
+ final PageService pageService,
+ final ResourceService resourceService,
+ @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
+
+ this.pageService = pageService;
+ this.widgetFactory = pageService.getWidgetFactory();
+ this.resourceService = resourceService;
+ this.pageSize = pageSize;
+
+ this.institutionFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ Entity.FILTER_ATTR_INSTITUTION,
+ this.resourceService::institutionResource);
+
+ this.lmsFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ LmsSetup.FILTER_ATTR_LMS_SETUP,
+ this.resourceService::lmsSetupResource);
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final CurrentUser currentUser = this.resourceService.getCurrentUser();
+ final RestService restService = this.resourceService.getRestService();
+ final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
+
+ // content page layout with title
+ final Composite content = this.widgetFactory.defaultPageLayout(
+ pageContext.getParent(),
+ TITLE_TEXT_KEY);
+
+ final PageActionBuilder actionBuilder =
+ this.pageService.pageActionBuilder(pageContext.clearEntityKeys());
+
+ final BooleanSupplier isSebAdmin =
+ () -> currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN);
+
+ final Function institutionNameFunction =
+ this.resourceService.getInstitutionNameFunction();
+
+ // table
+ final EntityTable table =
+ this.pageService.entityTableBuilder(restService.getRestCall(GetQuizPage.class))
+ .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
+ .withPaging(this.pageSize)
+
+ .withColumnIf(
+ isSebAdmin,
+ () -> new ColumnDefinition(
+ QuizData.QUIZ_ATTR_INSTITUTION_ID,
+ INSTITUTION_TEXT_KEY,
+ quiz -> institutionNameFunction
+ .apply(String.valueOf(quiz.institutionId)))
+ .withFilter(this.institutionFilter))
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_LMS_SETUP_ID,
+ LMS_TEXT_KEY,
+ quizDataLmsSetupNameFunction(this.resourceService))
+ .withFilter(this.lmsFilter)
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_NAME,
+ NAME_TEXT_KEY,
+ QuizData::getName)
+ .withFilter(this.nameFilter)
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_START_TIME,
+ new LocTextKey(
+ START_TIME_TEXT_KEY.name,
+ i18nSupport.getUsersTimeZoneTitleSuffix()),
+ QuizData::getStartTime)
+ .withFilter(new TableFilterAttribute(
+ CriteriaType.DATE,
+ QuizData.FILTER_ATTR_START_TIME,
+ Utils.toDateTimeUTC(Utils.getMillisecondsNow())
+ .minusYears(1)
+ .toString()))
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_END_TIME,
+ new LocTextKey(
+ END_TIME_TEXT_KEY.name,
+ i18nSupport.getUsersTimeZoneTitleSuffix()),
+ QuizData::getEndTime)
+ .sortable())
+
+ .withDefaultAction(t -> actionBuilder
+ .newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
+ .withExec(action -> this.showDetails(
+ action,
+ t.getSingleSelectedROWData(),
+ institutionNameFunction))
+ .noEventPropagation()
+ .create())
+
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ pageContext,
+ ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS,
+ ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT))
+
+ .compose(pageContext.copyOf(content));
+
+ // propagate content actions to action-pane
+ final GrantCheck examGrant = currentUser.grantCheck(EntityType.EXAM);
+ actionBuilder
+ .newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
+ .withSelect(
+ table::getSelection,
+ action -> this.showDetails(
+ action,
+ table.getSingleSelectedROWData(),
+ institutionNameFunction),
+ EMPTY_SELECTION_TEXT)
+ .noEventPropagation()
+ .publishIf(table::hasAnyContent, false)
+
+ .newAction(ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT)
+ .withSelect(
+ table::getSelection,
+ action -> this.importQuizData(action, table),
+ EMPTY_SELECTION_TEXT)
+ .publishIf(() -> examGrant.im() && table.hasAnyContent(), false);
+ }
+
+ private static Function quizDataLmsSetupNameFunction(final ResourceService resourceService) {
+ return quizzData -> resourceService.getLmsSetupNameFunction()
+ .apply(String.valueOf(quizzData.lmsSetupId));
+ }
+
+ private PageAction importQuizData(final PageAction action, final EntityTable table) {
+ action.getSingleSelection();
+ final QuizData selectedROWData = table.getSingleSelectedROWData();
+
+ if (selectedROWData.endTime != null) {
+ final DateTime now = DateTime.now(DateTimeZone.UTC);
+ if (selectedROWData.endTime.isBefore(now)) {
+ throw new PageMessageException(NO_IMPORT_OF_OUT_DATED_QUIZ);
+ }
+ }
+
+ return action
+ .withEntityKey(action.getSingleSelection())
+ .withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP))
+ .withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, Constants.TRUE_STRING);
+ }
+
+ private PageAction showDetails(
+ final PageAction action,
+ final QuizData quizData,
+ final Function institutionNameFunction) {
+
+ action.getSingleSelection();
+
+ final ModalInputDialog dialog = new ModalInputDialog(
+ action.pageContext().getParent().getShell(),
+ this.widgetFactory)
+ .setLargeDialogWidth();
+
+ dialog.open(
+ DETAILS_TITLE_TEXT_KEY,
+ action.pageContext(),
+ pc -> createDetailsForm(quizData, pc, institutionNameFunction));
+
+ return action;
+ }
+
+ private static final Collection ADDITIONAL_HTML_ATTRIBUTES = Arrays.asList(
+ "course_summary");
+
+ private void createDetailsForm(
+ final QuizData quizData,
+ final PageContext pc,
+ final Function institutionNameFunction) {
+
+ final Composite parent = pc.getParent();
+ final Composite grid = this.widgetFactory.createPopupScrollComposite(parent);
+
+ final FormBuilder formbuilder = this.pageService.formBuilder(pc.copyOf(grid))
+ .withDefaultSpanInput(6)
+ .withEmptyCellSeparation(false)
+ .readonly(true)
+ .addFieldIf(
+ () -> this.resourceService.getCurrentUser().get().hasRole(UserRole.SEB_SERVER_ADMIN),
+ () -> FormBuilder.text(
+ QuizData.QUIZ_ATTR_INSTITUTION_ID,
+ QUIZ_DETAILS_INSTITUTION_TEXT_KEY,
+ institutionNameFunction.apply(quizData.getModelId())))
+ .addField(FormBuilder.singleSelection(
+ QuizData.QUIZ_ATTR_LMS_SETUP_ID,
+ QUIZ_DETAILS_LMS_TEXT_KEY,
+ String.valueOf(quizData.lmsSetupId),
+ this.resourceService::lmsSetupResource))
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_NAME,
+ QUIZ_DETAILS_NAME_TEXT_KEY,
+ quizData.name))
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_DESCRIPTION,
+ QUIZ_DETAILS_DESCRIPTION_TEXT_KEY,
+ quizData.description)
+ .asHTML())
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_START_TIME,
+ QUIZ_DETAILS_START_TIME_TEXT_KEY,
+ this.widgetFactory.getI18nSupport().formatDisplayDateWithTimeZone(quizData.startTime)))
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_END_TIME,
+ QUIZ_DETAILS_END_TIME_TEXT_KEY,
+ this.widgetFactory.getI18nSupport().formatDisplayDateWithTimeZone(quizData.endTime)))
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_START_URL,
+ QUIZ_DETAILS_URL_TEXT_KEY,
+ quizData.startURL));
+
+ if (!quizData.additionalAttributes.isEmpty()) {
+ quizData.additionalAttributes
+ .forEach((key, value) -> {
+ LocTextKey titleKey = new LocTextKey(TEXT_KEY_ADDITIONAL_ATTR_PREFIX + key);
+ if (!this.pageService.getI18nSupport().hasText(titleKey)) {
+ titleKey = new LocTextKey(key);
+ }
+ formbuilder
+ .addField(FormBuilder.text(
+ key,
+ titleKey,
+ toAdditionalValue(key, value))
+ .asHTML(ADDITIONAL_HTML_ATTRIBUTES.contains(key)));
+ });
+ }
+
+ formbuilder.build();
+ }
+
+ private String toAdditionalValue(final String name, final String value) {
+ if (QuizData.ATTR_ADDITIONAL_CREATION_TIME.equals(name)) {
+ try {
+ return this.pageService
+ .getI18nSupport()
+ .formatDisplayDate(Utils.toDateTimeUTCUnix(Long.parseLong(value)));
+ } catch (final Exception e) {
+ return value;
+ }
+ } else if (QuizData.ATTR_ADDITIONAL_TIME_LIMIT.equals(name)) {
+ try {
+ return this.pageService
+ .getI18nSupport()
+ .formatDisplayTime(Utils.toDateTimeUTCUnix(Long.parseLong(value)));
+ } catch (final Exception e) {
+ return value;
+ }
+ } else {
+ return value;
+ }
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogDetailsPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogDetailsPopup.java
new file mode 100644
index 00000000..99317e16
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogDetailsPopup.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2020 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.content;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gui.form.FormBuilder;
+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.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+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.session.GetClientConnection;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+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;
+
+@Lazy
+@Component
+@GuiProfile
+public class SebClientLogDetailsPopup {
+
+ private static final Logger log = LoggerFactory.getLogger(SebClientLogDetailsPopup.class);
+
+ private static final LocTextKey DETAILS_TITLE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.details.title");
+ private static final LocTextKey DETAILS_EVENT_TILE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.details.event.title");
+ private static final LocTextKey DETAILS_CONNECTION_TILE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.details.connection.title");
+ private static final LocTextKey DETAILS_EXAM_TILE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.details.exam.title");
+
+ private static final LocTextKey FORM_TYPE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.type");
+ private static final LocTextKey FORM_SERVERTIME_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.server-time");
+ private static final LocTextKey FORM_CLIENTTIME_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.client-time");
+ private static final LocTextKey FORM_VALUE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.value");
+ private static final LocTextKey FORM_MESSAGE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.message");
+
+ private static final LocTextKey FORM_SESSION_ID_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.connection.session-id");
+ private static final LocTextKey FORM_ADDRESS_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.connection.address");
+ private static final LocTextKey FORM_TOKEN_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.connection.token");
+ private static final LocTextKey FORM_STATUS_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.connection.status");
+ private static final LocTextKey FORM_EXAM_NAME_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.exam.name");
+ private static final LocTextKey FORM_DESC_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.exam.description");
+ private static final LocTextKey FORM_EXAM_TYPE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.exam.type");
+ private static final LocTextKey FORM_START_TIME_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.exam.startTime");
+ private static final LocTextKey FORM_END_TIME_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.form.column.exam.endTime");
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final RestService restService;
+ private final I18nSupport i18nSupport;
+ private final WidgetFactory widgetFactory;
+
+ public SebClientLogDetailsPopup(
+ final PageService pageService,
+ final WidgetFactory widgetFactory) {
+
+ this.pageService = pageService;
+ this.widgetFactory = widgetFactory;
+ this.resourceService = pageService.getResourceService();
+ this.restService = pageService.getRestService();
+ this.i18nSupport = pageService.getI18nSupport();
+ }
+
+ PageAction showDetails(final PageAction action, final ExtendedClientEvent clientEvent) {
+ action.getSingleSelection();
+
+ final ModalInputDialog dialog = new ModalInputDialog<>(
+ action.pageContext().getParent().getShell(),
+ this.widgetFactory);
+ dialog.setDialogWidth(600);
+
+ dialog.open(
+ DETAILS_TITLE_TEXT_KEY,
+ action.pageContext(),
+ pc -> createDetailsForm(clientEvent, pc));
+
+ return action;
+ }
+
+ private void createDetailsForm(final ExtendedClientEvent clientEvent, final PageContext pc) {
+
+ final Composite parent = pc.getParent();
+ final Composite content = this.widgetFactory.createPopupScrollComposite(parent);
+
+ // Event Details Title
+ this.widgetFactory.labelLocalized(
+ content,
+ WidgetFactory.CustomVariant.TEXT_H3,
+ DETAILS_EVENT_TILE_TEXT_KEY);
+
+ PageContext formContext = pc.copyOf(content);
+
+ this.pageService.formBuilder(formContext)
+ .withDefaultSpanInput(6)
+ .withEmptyCellSeparation(false)
+ .readonly(true)
+ .addField(FormBuilder.text(
+ Domain.CLIENT_EVENT.TYPE_NAME,
+ FORM_TYPE_TEXT_KEY,
+ this.resourceService.getEventTypeName(clientEvent)))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
+ FORM_CLIENTTIME_TEXT_KEY,
+ this.i18nSupport.formatDisplayDateTime(clientEvent.clientTime) + " " +
+ this.i18nSupport.getUsersTimeZoneTitleSuffix()))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
+ FORM_SERVERTIME_TEXT_KEY,
+ this.i18nSupport.formatDisplayDateTime(clientEvent.serverTime) + " " +
+ this.i18nSupport.getUsersTimeZoneTitleSuffix()))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
+ FORM_VALUE_TEXT_KEY,
+ (clientEvent.numValue != null)
+ ? String.valueOf(clientEvent.numValue)
+ : Constants.EMPTY_NOTE))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_EVENT.ATTR_TEXT,
+ FORM_MESSAGE_TEXT_KEY,
+ clientEvent.text)
+ .asArea())
+ .build();
+
+ // SEB Client Connection Title
+ this.widgetFactory.labelLocalized(
+ content,
+ WidgetFactory.CustomVariant.TEXT_H3,
+ DETAILS_CONNECTION_TILE_TEXT_KEY);
+
+ final ClientConnection connection = this.restService.getBuilder(GetClientConnection.class)
+ .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(clientEvent.connectionId))
+ .call()
+ .get(
+ error -> log.error("Failed to get ClientConnection for id {}", clientEvent.connectionId, error),
+ () -> ClientConnection.EMPTY_CLIENT_CONNECTION);
+
+ this.pageService.formBuilder(formContext)
+ .withDefaultSpanInput(6)
+ .withEmptyCellSeparation(false)
+ .readonly(true)
+ .addField(FormBuilder.text(
+ Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
+ FORM_SESSION_ID_TEXT_KEY,
+ connection.userSessionId))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
+ FORM_ADDRESS_TEXT_KEY,
+ connection.clientAddress))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
+ FORM_TOKEN_TEXT_KEY,
+ connection.connectionToken))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_CONNECTION.ATTR_STATUS,
+ FORM_STATUS_TEXT_KEY,
+ this.resourceService.localizedClientConnectionStatusName(connection.status)))
+ .build();
+
+ // Exam Details Title
+ this.widgetFactory.labelLocalized(
+ content,
+ WidgetFactory.CustomVariant.TEXT_H3,
+ DETAILS_EXAM_TILE_TEXT_KEY);
+
+ final Exam exam = this.restService.getBuilder(GetExam.class)
+ .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(clientEvent.examId))
+ .call()
+ .get(
+ error -> log.error("Failed to get Exam for id {}", clientEvent.examId, error),
+ () -> Exam.EMPTY_EXAM);
+
+ this.pageService.formBuilder(formContext)
+ .withDefaultSpanInput(6)
+ .withEmptyCellSeparation(false)
+ .readonly(true)
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_NAME,
+ FORM_EXAM_NAME_TEXT_KEY,
+ exam.name))
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_DESCRIPTION,
+ FORM_DESC_TEXT_KEY,
+ exam.description))
+ .addField(FormBuilder.text(
+ Domain.EXAM.ATTR_TYPE,
+ FORM_EXAM_TYPE_TEXT_KEY,
+ this.resourceService.localizedExamTypeName(exam)))
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_START_TIME,
+ FORM_START_TIME_TEXT_KEY,
+ this.i18nSupport.formatDisplayDateWithTimeZone(exam.startTime)))
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_END_TIME,
+ FORM_END_TIME_TEXT_KEY,
+ this.i18nSupport.formatDisplayDateWithTimeZone(exam.endTime)))
+ .build();
+
+ }
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogs.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogs.java
index f534984b..b21bc162 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogs.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientLogs.java
@@ -1,387 +1,205 @@
-/*
- * 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.content;
-
-import java.util.Map;
-import java.util.function.Function;
-
-import org.eclipse.swt.widgets.Composite;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Component;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.model.Domain;
-import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
-import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
-import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
-import ch.ethz.seb.sebserver.gui.form.FormBuilder;
-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.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
-import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
-import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-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.logs.GetExtendedClientEventPage;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnection;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
-import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
-import ch.ethz.seb.sebserver.gui.table.EntityTable;
-import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
-
-@Lazy
-@Component
-@GuiProfile
-public class SebClientLogs implements TemplateComposer {
-
- private static final Logger log = LoggerFactory.getLogger(SebClientLogs.class);
-
- private static final LocTextKey DETAILS_TITLE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.details.title");
- private static final LocTextKey TITLE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.list.title");
- private static final LocTextKey EMPTY_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.list.empty");
-
- private static final LocTextKey EXAM_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.list.column.exam");
- private static final LocTextKey CLIENT_SESSION_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.list.column.client-session");
- private static final LocTextKey TYPE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.list.column.type");
- private static final LocTextKey TIME_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.list.column.time");
- private static final LocTextKey VALUE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.list.column.value");
-
- private static final LocTextKey DETAILS_EVENT_TILE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.details.event.title");
- private static final LocTextKey DETAILS_CONNECTION_TILE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.details.connection.title");
- private static final LocTextKey DETAILS_EXAM_TILE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.details.exam.title");
-
- private static final LocTextKey FORM_TYPE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.type");
- private static final LocTextKey FORM_SERVERTIME_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.server-time");
- private static final LocTextKey FORM_CLIENTTIME_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.client-time");
- private static final LocTextKey FORM_VALUE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.value");
- private static final LocTextKey FORM_MESSAGE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.message");
-
- private static final LocTextKey FORM_SESSION_ID_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.connection.session-id");
- private static final LocTextKey FORM_ADDRESS_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.connection.address");
- private static final LocTextKey FORM_TOKEN_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.connection.token");
- private static final LocTextKey FORM_STATUS_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.connection.status");
-
- private static final LocTextKey FORM_EXAM_NAME_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.exam.name");
- private static final LocTextKey FORM_DESC_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.exam.description");
- private static final LocTextKey FORM_EXAM_TYPE_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.exam.type");
- private static final LocTextKey FORM_START_TIME_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.exam.startTime");
- private static final LocTextKey FORM_END_TIME_TEXT_KEY =
- new LocTextKey("sebserver.seblogs.form.column.exam.endTime");
-
- private final static LocTextKey EMPTY_SELECTION_TEXT =
- new LocTextKey("sebserver.seblogs.info.pleaseSelect");
-
- private final TableFilterAttribute examFilter;
- private final TableFilterAttribute clientSessionFilter;
- private final TableFilterAttribute eventTypeFilter;
-
- private final PageService pageService;
- private final ResourceService resourceService;
- private final RestService restService;
- private final I18nSupport i18nSupport;
- private final WidgetFactory widgetFactory;
- private final int pageSize;
-
- public SebClientLogs(
- final PageService pageService,
- @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
-
- this.pageService = pageService;
- this.resourceService = pageService.getResourceService();
- this.restService = this.resourceService.getRestService();
- this.i18nSupport = this.resourceService.getI18nSupport();
- this.widgetFactory = pageService.getWidgetFactory();
- this.pageSize = pageSize;
-
- this.examFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- ExtendedClientEvent.FILTER_ATTRIBUTE_EXAM,
- this.resourceService::getExamLogSelectionResources);
-
- this.clientSessionFilter = new TableFilterAttribute(
- CriteriaType.TEXT,
- ClientConnection.FILTER_ATTR_SESSION_ID);
-
- this.eventTypeFilter = new TableFilterAttribute(
- CriteriaType.SINGLE_SELECTION,
- ClientEvent.FILTER_ATTR_TYPE,
- this.resourceService::clientEventTypeResources);
- }
-
- @Override
- public void compose(final PageContext pageContext) {
- final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
- // content page layout with title
- final Composite content = widgetFactory.defaultPageLayout(
- pageContext.getParent(),
- TITLE_TEXT_KEY);
-
- final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(
- pageContext
- .clearEntityKeys()
- .clearAttributes());
-
- // table
- final EntityTable table = this.pageService.entityTableBuilder(
- this.restService.getRestCall(GetExtendedClientEventPage.class))
- .withEmptyMessage(EMPTY_TEXT_KEY)
- .withPaging(this.pageSize)
-
- .withColumn(new ColumnDefinition<>(
- Domain.CLIENT_CONNECTION.ATTR_EXAM_ID,
- EXAM_TEXT_KEY,
- examNameFunction())
- .withFilter(this.examFilter)
- .widthProportion(2))
-
- .withColumn(new ColumnDefinition<>(
- Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
- CLIENT_SESSION_TEXT_KEY,
- ExtendedClientEvent::getUserSessionId)
- .withFilter(this.clientSessionFilter)
- .sortable()
- .widthProportion(2))
-
- .withColumn(new ColumnDefinition(
- Domain.CLIENT_EVENT.TYPE_NAME,
- TYPE_TEXT_KEY,
- this.resourceService::getEventTypeName)
- .withFilter(this.eventTypeFilter)
- .sortable()
- .widthProportion(1))
-
- .withColumn(new ColumnDefinition<>(
- Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
- new LocTextKey(
- TIME_TEXT_KEY.name,
- this.i18nSupport.getUsersTimeZoneTitleSuffix()),
- this::getEventTime)
- .withFilter(new TableFilterAttribute(
- CriteriaType.DATE_TIME_RANGE,
- ClientEvent.FILTER_ATTR_SERVER_TIME_FROM_TO,
- Utils.toDateTimeUTC(Utils.getMillisecondsNow())
- .minusYears(1)
- .toString()))
- .sortable()
- .widthProportion(2))
-
- .withColumn(new ColumnDefinition(
- Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
- VALUE_TEXT_KEY,
- clientEvent -> (clientEvent.numValue != null)
- ? String.valueOf(clientEvent.numValue)
- : Constants.EMPTY_NOTE)
- .widthProportion(1))
-
- .withDefaultAction(t -> actionBuilder
- .newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
- .withExec(action -> this.showDetails(action, t.getSingleSelectedROWData()))
- .noEventPropagation()
- .create())
-
- .compose(pageContext.copyOf(content));
-
- actionBuilder
- .newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
- .withSelect(
- table::getSelection,
- action -> this.showDetails(action, table.getSingleSelectedROWData()),
- EMPTY_SELECTION_TEXT)
- .noEventPropagation()
- .publishIf(table::hasAnyContent);
- }
-
- private PageAction showDetails(final PageAction action, final ExtendedClientEvent clientEvent) {
- action.getSingleSelection();
-
- final ModalInputDialog dialog = new ModalInputDialog<>(
- action.pageContext().getParent().getShell(),
- this.widgetFactory);
- dialog.setDialogWidth(600);
-
- dialog.open(
- DETAILS_TITLE_TEXT_KEY,
- action.pageContext(),
- pc -> createDetailsForm(clientEvent, pc));
-
- return action;
- }
-
- private void createDetailsForm(final ExtendedClientEvent clientEvent, final PageContext pc) {
-
- final Composite parent = pc.getParent();
- final Composite content = this.widgetFactory.createPopupScrollComposite(parent);
-
- // Event Details Title
- this.widgetFactory.labelLocalized(
- content,
- CustomVariant.TEXT_H3,
- DETAILS_EVENT_TILE_TEXT_KEY);
-
- this.pageService.formBuilder(pc.copyOf(content))
- .withDefaultSpanInput(6)
- .withEmptyCellSeparation(false)
- .readonly(true)
- .addField(FormBuilder.text(
- Domain.CLIENT_EVENT.TYPE_NAME,
- FORM_TYPE_TEXT_KEY,
- this.resourceService.getEventTypeName(clientEvent)))
- .addField(FormBuilder.text(
- Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
- FORM_CLIENTTIME_TEXT_KEY,
- this.i18nSupport.formatDisplayDateTime(clientEvent.clientTime) + " " +
- this.i18nSupport.getUsersTimeZoneTitleSuffix()))
- .addField(FormBuilder.text(
- Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
- FORM_SERVERTIME_TEXT_KEY,
- this.i18nSupport.formatDisplayDateTime(clientEvent.serverTime) + " " +
- this.i18nSupport.getUsersTimeZoneTitleSuffix()))
- .addField(FormBuilder.text(
- Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
- FORM_VALUE_TEXT_KEY,
- (clientEvent.numValue != null)
- ? String.valueOf(clientEvent.numValue)
- : Constants.EMPTY_NOTE))
- .addField(FormBuilder.text(
- Domain.CLIENT_EVENT.ATTR_TEXT,
- FORM_MESSAGE_TEXT_KEY,
- clientEvent.text)
- .asArea())
- .build();
-
- // SEB Client Connection Title
- this.widgetFactory.labelLocalized(
- content,
- CustomVariant.TEXT_H3,
- DETAILS_CONNECTION_TILE_TEXT_KEY);
-
- final ClientConnection connection = this.restService.getBuilder(GetClientConnection.class)
- .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(clientEvent.connectionId))
- .call()
- .get(
- error -> log.error("Failed to get ClientConnection for id {}", clientEvent.connectionId, error),
- () -> ClientConnection.EMPTY_CLIENT_CONNECTION);
-
- this.pageService.formBuilder(pc.copyOf(content))
- .withDefaultSpanInput(6)
- .withEmptyCellSeparation(false)
- .readonly(true)
- .addField(FormBuilder.text(
- Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
- FORM_SESSION_ID_TEXT_KEY,
- connection.userSessionId))
- .addField(FormBuilder.text(
- Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
- FORM_ADDRESS_TEXT_KEY,
- connection.clientAddress))
- .addField(FormBuilder.text(
- Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
- FORM_TOKEN_TEXT_KEY,
- connection.connectionToken))
- .addField(FormBuilder.text(
- Domain.CLIENT_CONNECTION.ATTR_STATUS,
- FORM_STATUS_TEXT_KEY,
- this.resourceService.localizedClientConnectionStatusName(connection.status)))
- .build();
-
- // Exam Details Title
- this.widgetFactory.labelLocalized(
- content,
- CustomVariant.TEXT_H3,
- DETAILS_EXAM_TILE_TEXT_KEY);
-
- final Exam exam = this.restService.getBuilder(GetExam.class)
- .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(clientEvent.examId))
- .call()
- .get(
- error -> log.error("Failed to get Exam for id {}", clientEvent.examId, error),
- () -> Exam.EMPTY_EXAM);
-
- this.pageService.formBuilder(pc.copyOf(content))
- .withDefaultSpanInput(6)
- .withEmptyCellSeparation(false)
- .readonly(true)
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_NAME,
- FORM_EXAM_NAME_TEXT_KEY,
- exam.name))
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_DESCRIPTION,
- FORM_DESC_TEXT_KEY,
- exam.description))
- .addField(FormBuilder.text(
- Domain.EXAM.ATTR_TYPE,
- FORM_EXAM_TYPE_TEXT_KEY,
- this.resourceService.localizedExamTypeName(exam)))
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_START_TIME,
- FORM_START_TIME_TEXT_KEY,
- this.i18nSupport.formatDisplayDateWithTimeZone(exam.startTime)))
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_END_TIME,
- FORM_END_TIME_TEXT_KEY,
- this.i18nSupport.formatDisplayDateWithTimeZone(exam.endTime)))
- .build();
-
- }
-
- private Function examNameFunction() {
- final Map examNameMapping = this.resourceService.getExamNameMapping();
- return event -> examNameMapping.get(event.examId);
- }
-
- private final String getEventTime(final ExtendedClientEvent event) {
- if (event == null || event.serverTime == null) {
- return Constants.EMPTY_NOTE;
- }
-
- return this.i18nSupport
- .formatDisplayDateTime(Utils.toDateTimeUTC(event.serverTime));
- }
-
-}
+/*
+ * 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.content;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
+import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+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.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
+import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+import org.eclipse.swt.widgets.Composite;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.function.Function;
+
+@Lazy
+@Component
+@GuiProfile
+public class SebClientLogs implements TemplateComposer {
+
+ private static final LocTextKey TITLE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.list.title");
+ private static final LocTextKey EMPTY_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.list.empty");
+
+ private static final LocTextKey EXAM_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.list.column.exam");
+ private static final LocTextKey CLIENT_SESSION_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.list.column.client-session");
+ private static final LocTextKey TYPE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.list.column.type");
+ private static final LocTextKey TIME_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.list.column.time");
+ private static final LocTextKey VALUE_TEXT_KEY =
+ new LocTextKey("sebserver.seblogs.list.column.value");
+ private final static LocTextKey EMPTY_SELECTION_TEXT =
+ new LocTextKey("sebserver.seblogs.info.pleaseSelect");
+
+ private final TableFilterAttribute examFilter;
+ private final TableFilterAttribute clientSessionFilter;
+ private final TableFilterAttribute eventTypeFilter;
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final RestService restService;
+ private final I18nSupport i18nSupport;
+ private final SebClientLogDetailsPopup sebClientLogDetailsPopup;
+ private final int pageSize;
+
+ public SebClientLogs(
+ final PageService pageService,
+ final SebClientLogDetailsPopup sebClientLogDetailsPopup,
+ @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
+
+ this.pageService = pageService;
+ this.resourceService = pageService.getResourceService();
+ this.restService = this.resourceService.getRestService();
+ this.i18nSupport = this.resourceService.getI18nSupport();
+ this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
+ this.pageSize = pageSize;
+
+ this.examFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ ExtendedClientEvent.FILTER_ATTRIBUTE_EXAM,
+ this.resourceService::getExamLogSelectionResources);
+
+ this.clientSessionFilter = new TableFilterAttribute(
+ CriteriaType.TEXT,
+ ClientConnection.FILTER_ATTR_SESSION_ID);
+
+ this.eventTypeFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ ClientEvent.FILTER_ATTR_TYPE,
+ this.resourceService::clientEventTypeResources);
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
+ // content page layout with title
+ final Composite content = widgetFactory.defaultPageLayout(
+ pageContext.getParent(),
+ TITLE_TEXT_KEY);
+
+ final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(
+ pageContext
+ .clearEntityKeys()
+ .clearAttributes());
+
+ // table
+ final EntityTable table = this.pageService.entityTableBuilder(
+ this.restService.getRestCall(GetExtendedClientEventPage.class))
+ .withEmptyMessage(EMPTY_TEXT_KEY)
+ .withPaging(this.pageSize)
+
+ .withColumn(new ColumnDefinition<>(
+ Domain.CLIENT_CONNECTION.ATTR_EXAM_ID,
+ EXAM_TEXT_KEY,
+ examNameFunction())
+ .withFilter(this.examFilter)
+ .widthProportion(2))
+
+ .withColumn(new ColumnDefinition<>(
+ Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
+ CLIENT_SESSION_TEXT_KEY,
+ ExtendedClientEvent::getUserSessionId)
+ .withFilter(this.clientSessionFilter)
+ .sortable()
+ .widthProportion(2))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.TYPE_NAME,
+ TYPE_TEXT_KEY,
+ this.resourceService::getEventTypeName)
+ .withFilter(this.eventTypeFilter)
+ .sortable()
+ .widthProportion(1))
+
+ .withColumn(new ColumnDefinition<>(
+ Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
+ new LocTextKey(
+ TIME_TEXT_KEY.name,
+ this.i18nSupport.getUsersTimeZoneTitleSuffix()),
+ this::getEventTime)
+ .withFilter(new TableFilterAttribute(
+ CriteriaType.DATE_TIME_RANGE,
+ ClientEvent.FILTER_ATTR_SERVER_TIME_FROM_TO,
+ Utils.toDateTimeUTC(Utils.getMillisecondsNow())
+ .minusYears(1)
+ .toString()))
+ .sortable()
+ .widthProportion(2))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
+ VALUE_TEXT_KEY,
+ clientEvent -> (clientEvent.numValue != null)
+ ? String.valueOf(clientEvent.numValue)
+ : Constants.EMPTY_NOTE)
+ .widthProportion(1))
+
+ .withDefaultAction(t -> actionBuilder
+ .newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
+ .withExec(action -> sebClientLogDetailsPopup.showDetails(action, t.getSingleSelectedROWData()))
+ .noEventPropagation()
+ .create())
+
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ pageContext,
+ ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS))
+
+ .compose(pageContext.copyOf(content));
+
+ actionBuilder
+ .newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
+ .withSelect(
+ table::getSelection,
+ action -> sebClientLogDetailsPopup.showDetails(action, table.getSingleSelectedROWData()),
+ EMPTY_SELECTION_TEXT)
+ .noEventPropagation()
+ .publishIf(table::hasAnyContent, false);
+ }
+
+
+
+ private Function examNameFunction() {
+ final Map examNameMapping = this.resourceService.getExamNameMapping();
+ return event -> examNameMapping.get(event.examId);
+ }
+
+ private String getEventTime(final ExtendedClientEvent event) {
+ if (event == null || event.serverTime == null) {
+ return Constants.EMPTY_NOTE;
+ }
+
+ return this.i18nSupport
+ .formatDisplayDateTime(Utils.toDateTimeUTC(event.serverTime));
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigCreationPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigCreationPopup.java
index 33998111..bc65a020 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigCreationPopup.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigCreationPopup.java
@@ -1,194 +1,194 @@
-/*
- * 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.content;
-
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-
-import org.apache.commons.lang3.BooleanUtils;
-import org.eclipse.swt.widgets.Composite;
-
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.model.Domain;
-import ch.ethz.seb.sebserver.gbl.model.EntityKey;
-import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCreationInfo;
-import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
-import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-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.service.i18n.LocTextKey;
-import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
-import ch.ethz.seb.sebserver.gui.service.page.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.CopyConfiguration;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
-
-final class SebExamConfigCreationPopup {
-
- static final LocTextKey FORM_COPY_TEXT_KEY =
- new LocTextKey("sebserver.examconfig.action.copy.dialog");
- static final LocTextKey FORM_COPY_AS_TEMPLATE_TEXT_KEY =
- new LocTextKey("sebserver.examconfig.action.copy-as-template.dialog");
- static final LocTextKey FORM_CREATE_FROM_TEMPLATE_TEXT_KEY =
- new LocTextKey("sebserver.configtemplate.action.create-config.dialog");
-
- static Function configCreationFunction(
- final PageService pageService,
- final PageContext pageContext) {
-
- final boolean copyAsTemplate = BooleanUtils.toBoolean(
- pageContext.getAttribute(PageContext.AttributeKeys.COPY_AS_TEMPLATE));
- final boolean createFromTemplate = BooleanUtils.toBoolean(
- pageContext.getAttribute(PageContext.AttributeKeys.CREATE_FROM_TEMPLATE));
-
- return action -> {
-
- final ModalInputDialog> dialog =
- new ModalInputDialog>(
- action.pageContext().getParent().getShell(),
- pageService.getWidgetFactory())
- .setLargeDialogWidth();
-
- final CreationFormContext formContext = new CreationFormContext(
- pageService,
- pageContext,
- copyAsTemplate,
- createFromTemplate);
-
- final Predicate> doCopy = formHandle -> doCreate(
- pageService,
- pageContext,
- copyAsTemplate,
- createFromTemplate,
- formHandle);
-
- final LocTextKey title = (copyAsTemplate)
- ? FORM_COPY_AS_TEMPLATE_TEXT_KEY
- : (createFromTemplate)
- ? FORM_CREATE_FROM_TEMPLATE_TEXT_KEY
- : FORM_COPY_TEXT_KEY;
-
- dialog.open(
- title,
- doCopy,
- Utils.EMPTY_EXECUTION,
- formContext);
-
- return action;
- };
- }
-
- private static final boolean doCreate(
- final PageService pageService,
- final PageContext pageContext,
- final boolean copyAsTemplate,
- final boolean createFromTemplate,
- final FormHandle formHandle) {
-
- // create either a new configuration form template or from other configuration
- final Class extends RestCall> restCall = (createFromTemplate)
- ? NewExamConfig.class
- : CopyConfiguration.class;
-
- final ConfigurationNode newConfig = pageService
- .getRestService()
- .getBuilder(restCall)
- .withFormBinding(formHandle.getFormBinding())
- .call()
- .onError(formHandle::handleError)
- .getOr(null);
-
- if (newConfig == null) {
- return false;
- }
-
- // view either new template or configuration
- final PageAction viewCopy = (copyAsTemplate)
- ? pageService.pageActionBuilder(pageContext)
- .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW)
- .withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
- .create()
- : pageService.pageActionBuilder(pageContext)
- .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
- .withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
- .create();
-
- pageService.executePageAction(viewCopy);
-
- return true;
- }
-
- private static final class CreationFormContext implements ModalInputDialogComposer> {
-
- private final PageService pageService;
- private final PageContext pageContext;
- private final boolean copyAsTemplate;
- private final boolean createFromTemplate;
-
- protected CreationFormContext(
- final PageService pageService,
- final PageContext pageContext,
- final boolean copyAsTemplate,
- final boolean createFromTemplate) {
-
- this.pageService = pageService;
- this.pageContext = pageContext;
- this.copyAsTemplate = copyAsTemplate;
- this.createFromTemplate = createFromTemplate;
- }
-
- @Override
- public Supplier> compose(final Composite parent) {
-
- final Composite grid = this.pageService.getWidgetFactory()
- .createPopupScrollComposite(parent);
-
- final EntityKey entityKey = this.pageContext.getEntityKey();
- final FormHandle formHandle = this.pageService.formBuilder(
- this.pageContext.copyOf(grid))
- .readonly(false)
- .putStaticValueIf(
- () -> !this.createFromTemplate,
- Domain.CONFIGURATION_NODE.ATTR_ID,
- entityKey.getModelId())
- .putStaticValue(
- Domain.CONFIGURATION_NODE.ATTR_TYPE,
- (this.copyAsTemplate)
- ? ConfigurationType.TEMPLATE.name()
- : ConfigurationType.EXAM_CONFIG.name())
- .putStaticValueIf(
- () -> this.createFromTemplate,
- Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
- entityKey.getModelId())
- .addField(FormBuilder.text(
- Domain.CONFIGURATION_NODE.ATTR_NAME,
- SebExamConfigPropForm.FORM_NAME_TEXT_KEY))
- .addField(FormBuilder.text(
- Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
- SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
- .asArea())
- .addFieldIf(
- () -> !this.copyAsTemplate && !this.createFromTemplate,
- () -> FormBuilder.checkbox(
- ConfigCreationInfo.ATTR_COPY_WITH_HISTORY,
- SebExamConfigPropForm.FORM_HISTORY_TEXT_KEY))
- .build();
-
- return () -> formHandle;
- }
-
- }
-
-}
+/*
+ * 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.content;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import org.apache.commons.lang3.BooleanUtils;
+import org.eclipse.swt.widgets.Composite;
+
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCreationInfo;
+import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
+import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+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.service.i18n.LocTextKey;
+import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.CopyConfiguration;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
+
+final class SebExamConfigCreationPopup {
+
+ static final LocTextKey FORM_COPY_TEXT_KEY =
+ new LocTextKey("sebserver.examconfig.action.copy.dialog");
+ static final LocTextKey FORM_COPY_AS_TEMPLATE_TEXT_KEY =
+ new LocTextKey("sebserver.examconfig.action.copy-as-template.dialog");
+ static final LocTextKey FORM_CREATE_FROM_TEMPLATE_TEXT_KEY =
+ new LocTextKey("sebserver.configtemplate.action.create-config.dialog");
+
+ static Function configCreationFunction(
+ final PageService pageService,
+ final PageContext pageContext) {
+
+ final boolean copyAsTemplate = BooleanUtils.toBoolean(
+ pageContext.getAttribute(PageContext.AttributeKeys.COPY_AS_TEMPLATE));
+ final boolean createFromTemplate = BooleanUtils.toBoolean(
+ pageContext.getAttribute(PageContext.AttributeKeys.CREATE_FROM_TEMPLATE));
+
+ return action -> {
+
+ final ModalInputDialog> dialog =
+ new ModalInputDialog>(
+ action.pageContext().getParent().getShell(),
+ pageService.getWidgetFactory())
+ .setLargeDialogWidth();
+
+ final CreationFormContext formContext = new CreationFormContext(
+ pageService,
+ pageContext,
+ copyAsTemplate,
+ createFromTemplate);
+
+ final Predicate> doCopy = formHandle -> doCreate(
+ pageService,
+ pageContext,
+ copyAsTemplate,
+ createFromTemplate,
+ formHandle);
+
+ final LocTextKey title = (copyAsTemplate)
+ ? FORM_COPY_AS_TEMPLATE_TEXT_KEY
+ : (createFromTemplate)
+ ? FORM_CREATE_FROM_TEMPLATE_TEXT_KEY
+ : FORM_COPY_TEXT_KEY;
+
+ dialog.open(
+ title,
+ doCopy,
+ Utils.EMPTY_EXECUTION,
+ formContext);
+
+ return action;
+ };
+ }
+
+ private static boolean doCreate(
+ final PageService pageService,
+ final PageContext pageContext,
+ final boolean copyAsTemplate,
+ final boolean createFromTemplate,
+ final FormHandle formHandle) {
+
+ // create either a new configuration form template or from other configuration
+ final Class extends RestCall> restCall = (createFromTemplate)
+ ? NewExamConfig.class
+ : CopyConfiguration.class;
+
+ final ConfigurationNode newConfig = pageService
+ .getRestService()
+ .getBuilder(restCall)
+ .withFormBinding(formHandle.getFormBinding())
+ .call()
+ .onError(formHandle::handleError)
+ .getOr(null);
+
+ if (newConfig == null) {
+ return false;
+ }
+
+ // view either new template or configuration
+ final PageAction viewCopy = (copyAsTemplate)
+ ? pageService.pageActionBuilder(pageContext)
+ .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW)
+ .withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
+ .create()
+ : pageService.pageActionBuilder(pageContext)
+ .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
+ .withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
+ .create();
+
+ pageService.executePageAction(viewCopy);
+
+ return true;
+ }
+
+ private static final class CreationFormContext implements ModalInputDialogComposer> {
+
+ private final PageService pageService;
+ private final PageContext pageContext;
+ private final boolean copyAsTemplate;
+ private final boolean createFromTemplate;
+
+ protected CreationFormContext(
+ final PageService pageService,
+ final PageContext pageContext,
+ final boolean copyAsTemplate,
+ final boolean createFromTemplate) {
+
+ this.pageService = pageService;
+ this.pageContext = pageContext;
+ this.copyAsTemplate = copyAsTemplate;
+ this.createFromTemplate = createFromTemplate;
+ }
+
+ @Override
+ public Supplier> compose(final Composite parent) {
+
+ final Composite grid = this.pageService.getWidgetFactory()
+ .createPopupScrollComposite(parent);
+
+ final EntityKey entityKey = this.pageContext.getEntityKey();
+ final FormHandle formHandle = this.pageService.formBuilder(
+ this.pageContext.copyOf(grid))
+ .readonly(false)
+ .putStaticValueIf(
+ () -> !this.createFromTemplate,
+ Domain.CONFIGURATION_NODE.ATTR_ID,
+ entityKey.getModelId())
+ .putStaticValue(
+ Domain.CONFIGURATION_NODE.ATTR_TYPE,
+ (this.copyAsTemplate)
+ ? ConfigurationType.TEMPLATE.name()
+ : ConfigurationType.EXAM_CONFIG.name())
+ .putStaticValueIf(
+ () -> this.createFromTemplate,
+ Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
+ entityKey.getModelId())
+ .addField(FormBuilder.text(
+ Domain.CONFIGURATION_NODE.ATTR_NAME,
+ SebExamConfigPropForm.FORM_NAME_TEXT_KEY))
+ .addField(FormBuilder.text(
+ Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
+ SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
+ .asArea())
+ .addFieldIf(
+ () -> !this.copyAsTemplate && !this.createFromTemplate,
+ () -> FormBuilder.checkbox(
+ ConfigCreationInfo.ATTR_COPY_WITH_HISTORY,
+ SebExamConfigPropForm.FORM_HISTORY_TEXT_KEY))
+ .build();
+
+ return () -> formHandle;
+ }
+
+ }
+
+}
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 d625df8d..cee362d8 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
@@ -183,6 +183,11 @@ public enum ActionDefinition {
ImageIcon.SAVE,
PageStateDefinitionImpl.LMS_SETUP_VIEW,
ActionCategory.FORM),
+ LMS_SETUP_SAVE_AND_ACTIVATE(
+ new LocTextKey("sebserver.form.action.save.activate"),
+ ImageIcon.ACTIVE,
+ PageStateDefinitionImpl.LMS_SETUP_VIEW,
+ ActionCategory.FORM),
LMS_SETUP_ACTIVATE(
new LocTextKey("sebserver.lmssetup.action.activate"),
ImageIcon.TOGGLE_OFF,
@@ -193,6 +198,11 @@ public enum ActionDefinition {
ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.LMS_SETUP_VIEW,
ActionCategory.FORM),
+ LMS_SETUP_TOGGLE_ACTIVITY(
+ new LocTextKey("sebserver.overall.action.toggle-activity"),
+ ImageIcon.SWITCH,
+ PageStateDefinitionImpl.LMS_SETUP_LIST,
+ ActionCategory.LMS_SETUP_LIST),
QUIZ_DISCOVERY_VIEW_LIST(
new LocTextKey("sebserver.quizdiscovery.action.list"),
@@ -593,7 +603,7 @@ public enum ActionDefinition {
ImageIcon.SEND_QUIT,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FORM),
- MONITOR_EXAM_FROM_DETAILS(
+ MONITOR_EXAM_BACK_TO_OVERVIEW(
new LocTextKey("sebserver.monitoring.exam.action.detail.view"),
ImageIcon.SHOW,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormHandle.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormHandle.java
index 39b65bdc..92e7375c 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/form/FormHandle.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/FormHandle.java
@@ -1,185 +1,185 @@
-/*
- * Copyright (c) 2018 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.function.Consumer;
-import java.util.function.Predicate;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.api.APIMessage;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.model.Entity;
-import ch.ethz.seb.sebserver.gbl.util.Result;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-import ch.ethz.seb.sebserver.gui.form.Form.FormFieldAccessor;
-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.FieldValidationError;
-import ch.ethz.seb.sebserver.gui.service.page.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
-
-public class FormHandle {
-
- private static final Logger log = LoggerFactory.getLogger(FormHandle.class);
-
- public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "sebserver.form.validation.fieldError.";
-
- private final PageService pageService;
- private final PageContext pageContext;
- private final Form form;
- private final RestCall post;
- private final I18nSupport i18nSupport;
-
- FormHandle(
- final PageService pageService,
- final PageContext pageContext,
- final Form form,
- final RestCall post) {
-
- this.pageService = pageService;
- this.pageContext = pageContext;
- this.form = form;
- this.post = post;
- this.i18nSupport = pageService.getI18nSupport();
- }
-
- public PageContext getContext() {
- return this.pageContext;
- }
-
- public FormBinding getFormBinding() {
- return this.form;
- }
-
- public Form getForm() {
- return this.form;
- }
-
- /** Process an API post request to send and save the form field values
- * to the webservice and publishes a page event to return to read-only-view
- * to indicate that the data was successfully saved or process an validation
- * error indication if there are some validation errors.
- *
- * @param action the save action context
- * @return the new Action context for read-only-view */
- public final PageAction processFormSave(final PageAction action) {
- return handleFormPost(doAPIPost(), action);
- }
-
- public final PageAction saveAndActivate(final PageAction action) {
- final PageAction handleFormPost = handleFormPost(doAPIPost(), action);
- final EntityType entityType = this.post.getEntityType();
- this.pageService.getRestService().getBuilder(entityType, CallType.ACTIVATION_ACTIVATE)
- .withURIVariable(API.PARAM_MODEL_ID, handleFormPost.getEntityKey().getModelId())
- .call()
- .getOrThrow();
- return handleFormPost;
- }
-
- /** process a form post by first resetting all field validation errors (if there are some)
- * then collecting all input data from the form by form-binding to a either a JSON string in
- * HTTP PUT case or to an form-URL-encoded string on HTTP POST case. And PUT or POST the data
- * to the webservice by using the defined RestCall and return the response result of the RestCall.
- *
- * @return the response result of the post (or put) RestCall */
- public Result doAPIPost() {
- // reset all errors that may still be displayed
- this.form.process(
- Utils.truePredicate(),
- fieldAccessor -> fieldAccessor.resetError());
-
- // post
- return this.post
- .newBuilder()
- .withFormBinding(this.form)
- .call();
- }
-
- /** Uses the result of a form post to either create and publish a new Action to
- * go to the read-only-view of the specified form to indicate a successful form post
- * or stay within the edit-mode of the form and indicate errors or field validation messages
- * to the user on error case.
- *
- * @param postResult The form post result
- * @param action the action that was applied with the form post
- * @return the new Action that was used to stay on page or go the read-only-view of the form */
- public PageAction handleFormPost(final Result postResult, final PageAction action) {
- return postResult
- .map(result -> {
-
- PageAction resultAction = this.pageService.pageActionBuilder(action.pageContext())
- .newAction(action.definition)
- .create();
- if (resultAction.getEntityKey() == null) {
- resultAction = resultAction.withEntityKey(result.getEntityKey());
- }
-
- return resultAction;
- })
- .onError(this::handleError)
- .getOrThrow(error -> new FormPostException(error));
- }
-
- public boolean handleError(final Exception error) {
- if (error instanceof RestCallError) {
- ((RestCallError) error)
- .getErrorMessages()
- .stream()
- .filter(APIMessage.ErrorMessage.FIELD_VALIDATION::isOf)
- .map(FieldValidationError::new)
- .forEach(fve -> this.form.process(
- name -> name.equals(fve.fieldName),
- fieldAccessor -> showValidationError(fieldAccessor, fve)));
- return true;
- } else {
- log.error("Unexpected error while trying to post form: {}", error.getMessage());
- final EntityType resultType = this.post.getEntityType();
- if (resultType != null) {
- this.pageContext.notifySaveError(resultType, error);
- } else {
- this.pageContext.notifyError(
- new LocTextKey(PageContext.GENERIC_SAVE_ERROR_TEXT_KEY, Constants.EMPTY_NOTE),
- error);
- }
-
- return false;
- }
- }
-
- public boolean hasAnyError() {
- return this.form.hasAnyError();
- }
-
- private final void showValidationError(
- final FormFieldAccessor fieldAccessor,
- final FieldValidationError valError) {
-
- fieldAccessor.setError(this.i18nSupport.getText(new LocTextKey(
- FIELD_VALIDATION_LOCTEXT_PREFIX + valError.errorType,
- (Object[]) valError.getAttributes())));
- }
-
- public FormHandle process(
- final Predicate nameFilter,
- final Consumer processor) {
-
- this.form.process(nameFilter, processor);
- return this;
- }
-
-}
+/*
+ * Copyright (c) 2018 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.function.Consumer;
+import java.util.function.Predicate;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.APIMessage;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Entity;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+import ch.ethz.seb.sebserver.gui.form.Form.FormFieldAccessor;
+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.FieldValidationError;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.FormBinding;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
+
+public class FormHandle {
+
+ private static final Logger log = LoggerFactory.getLogger(FormHandle.class);
+
+ public static final String FIELD_VALIDATION_LOCTEXT_PREFIX = "sebserver.form.validation.fieldError.";
+
+ private final PageService pageService;
+ private final PageContext pageContext;
+ private final Form form;
+ private final RestCall post;
+ private final I18nSupport i18nSupport;
+
+ FormHandle(
+ final PageService pageService,
+ final PageContext pageContext,
+ final Form form,
+ final RestCall post) {
+
+ this.pageService = pageService;
+ this.pageContext = pageContext;
+ this.form = form;
+ this.post = post;
+ this.i18nSupport = pageService.getI18nSupport();
+ }
+
+ public PageContext getContext() {
+ return this.pageContext;
+ }
+
+ public FormBinding getFormBinding() {
+ return this.form;
+ }
+
+ public Form getForm() {
+ return this.form;
+ }
+
+ /** Process an API post request to send and save the form field values
+ * to the webservice and publishes a page event to return to read-only-view
+ * to indicate that the data was successfully saved or process an validation
+ * error indication if there are some validation errors.
+ *
+ * @param action the save action context
+ * @return the new Action context for read-only-view */
+ public final PageAction processFormSave(final PageAction action) {
+ return handleFormPost(doAPIPost(), action);
+ }
+
+ public final PageAction saveAndActivate(final PageAction action) {
+ final PageAction handleFormPost = handleFormPost(doAPIPost(), action);
+ final EntityType entityType = this.post.getEntityType();
+ this.pageService.getRestService().getBuilder(entityType, CallType.ACTIVATION_ACTIVATE)
+ .withURIVariable(API.PARAM_MODEL_ID, handleFormPost.getEntityKey().getModelId())
+ .call()
+ .getOrThrow();
+ return handleFormPost;
+ }
+
+ /** process a form post by first resetting all field validation errors (if there are some)
+ * then collecting all input data from the form by form-binding to a either a JSON string in
+ * HTTP PUT case or to an form-URL-encoded string on HTTP POST case. And PUT or POST the data
+ * to the webservice by using the defined RestCall and return the response result of the RestCall.
+ *
+ * @return the response result of the post (or put) RestCall */
+ public Result doAPIPost() {
+ // reset all errors that may still be displayed
+ this.form.process(
+ Utils.truePredicate(),
+ fieldAccessor -> fieldAccessor.resetError());
+
+ // post
+ return this.post
+ .newBuilder()
+ .withFormBinding(this.form)
+ .call();
+ }
+
+ /** Uses the result of a form post to either create and publish a new Action to
+ * go to the read-only-view of the specified form to indicate a successful form post
+ * or stay within the edit-mode of the form and indicate errors or field validation messages
+ * to the user on error case.
+ *
+ * @param postResult The form post result
+ * @param action the action that was applied with the form post
+ * @return the new Action that was used to stay on page or go the read-only-view of the form */
+ public PageAction handleFormPost(final Result postResult, final PageAction action) {
+ return postResult
+ .map(result -> {
+
+ PageAction resultAction = this.pageService.pageActionBuilder(action.pageContext())
+ .newAction(action.definition)
+ .create();
+ if (resultAction.getEntityKey() == null) {
+ resultAction = resultAction.withEntityKey(result.getEntityKey());
+ }
+
+ return resultAction;
+ })
+ .onError(this::handleError)
+ .getOrThrow(FormPostException::new);
+ }
+
+ public boolean handleError(final Exception error) {
+ if (error instanceof RestCallError) {
+ ((RestCallError) error)
+ .getErrorMessages()
+ .stream()
+ .filter(APIMessage.ErrorMessage.FIELD_VALIDATION::isOf)
+ .map(FieldValidationError::new)
+ .forEach(fve -> this.form.process(
+ name -> name.equals(fve.fieldName),
+ fieldAccessor -> showValidationError(fieldAccessor, fve)));
+ return true;
+ } else {
+ log.error("Unexpected error while trying to post form: {}", error.getMessage());
+ final EntityType resultType = this.post.getEntityType();
+ if (resultType != null) {
+ this.pageContext.notifySaveError(resultType, error);
+ } else {
+ this.pageContext.notifyError(
+ new LocTextKey(PageContext.GENERIC_SAVE_ERROR_TEXT_KEY, Constants.EMPTY_NOTE),
+ error);
+ }
+
+ return false;
+ }
+ }
+
+ public boolean hasAnyError() {
+ return this.form.hasAnyError();
+ }
+
+ private final void showValidationError(
+ final FormFieldAccessor fieldAccessor,
+ final FieldValidationError valError) {
+
+ fieldAccessor.setError(this.i18nSupport.getText(new LocTextKey(
+ FIELD_VALIDATION_LOCTEXT_PREFIX + valError.errorType,
+ (Object[]) valError.getAttributes())));
+ }
+
+ public FormHandle process(
+ final Predicate nameFilter,
+ final Consumer processor) {
+
+ this.form.process(nameFilter, processor);
+ return this;
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java
index 9b56d961..e6f4a2e0 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java
@@ -1,150 +1,150 @@
-/*
- * 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.function.Consumer;
-
-import org.apache.commons.lang3.StringUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.Browser;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.RGB;
-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 ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-
-public final class TextFieldBuilder extends FieldBuilder {
-
- private static final String HTML_TEXT_BLOCK_START =
- "";
- private static final String HTML_TEXT_BLOCK_END = "";
-
- boolean isPassword = false;
- boolean isNumber = false;
- Consumer numberCheck = null;
- boolean isArea = false;
- int areaMinHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
- boolean isColorbox = false;
- boolean isHTML = false;
-
- TextFieldBuilder(final String name, final LocTextKey label, final String value) {
- super(name, label, value);
- }
-
- public TextFieldBuilder asPasswordField() {
- this.isPassword = true;
- return this;
- }
-
- public TextFieldBuilder asNumber() {
- this.isNumber = true;
- return this;
- }
-
- public TextFieldBuilder asNumber(final Consumer numberCheck) {
- this.isNumber = true;
- this.numberCheck = numberCheck;
- return this;
- }
-
- public TextFieldBuilder asArea(final int minHeight) {
- this.areaMinHeight = minHeight;
- return asArea();
- }
-
- public TextFieldBuilder asArea() {
- this.isArea = true;
- this.titleValign = SWT.CENTER;
- return this;
- }
-
- public TextFieldBuilder asHTML() {
- this.isHTML = true;
- return this;
- }
-
- public FieldBuilder> asHTML(final boolean html) {
- this.isHTML = html;
- return this;
- }
-
- public TextFieldBuilder asColorbox() {
- this.isColorbox = true;
- return this;
- }
-
- @Override
- void build(final FormBuilder builder) {
- final boolean readonly = builder.readonly || this.readonly;
- final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
- final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
-
- if (readonly && this.isHTML) {
- final Browser browser = new Browser(fieldGrid, SWT.NONE);
- final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
- gridData.minimumHeight = this.areaMinHeight;
- browser.setBackground(new Color(builder.formParent.getDisplay(), new RGB(250, 250, 250)));
- browser.setLayoutData(gridData);
- if (StringUtils.isNoneBlank(this.value)) {
- browser.setText(createHTMLText(this.value));
- } else if (readonly) {
- browser.setText(Constants.EMPTY_NOTE);
- }
- builder.form.putReadonlyField(this.name, titleLabel, browser);
- return;
- }
-
- final Text textInput = (this.isNumber)
- ? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly)
- : (this.isArea)
- ? builder.widgetFactory.textAreaInput(fieldGrid, readonly)
- : builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly);
-
- final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
- if (this.isArea) {
- gridData.minimumHeight = this.areaMinHeight;
- } else if (this.isColorbox) {
- gridData.minimumHeight = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
- textInput.setData(RWT.CUSTOM_VARIANT, "colorbox");
- }
- textInput.setLayoutData(gridData);
- if (StringUtils.isNoneBlank(this.value)) {
- textInput.setText(this.value);
- } else if (readonly) {
- textInput.setText(Constants.EMPTY_NOTE);
- }
-
- if (readonly) {
- textInput.setEditable(false);
- builder.form.putReadonlyField(this.name, titleLabel, textInput);
- } else {
- final Label errorLabel = createErrorLabel(fieldGrid);
- builder.form.putField(this.name, titleLabel, textInput, errorLabel);
- builder.setFieldVisible(this.visible, this.name);
- }
-
- }
-
- private String createHTMLText(final String text) {
- return HTML_TEXT_BLOCK_START
- + text
- .replace(" {
+
+ private static final String HTML_TEXT_BLOCK_START =
+ "";
+ private static final String HTML_TEXT_BLOCK_END = "";
+
+ boolean isPassword = false;
+ boolean isNumber = false;
+ Consumer numberCheck = null;
+ boolean isArea = false;
+ int areaMinHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
+ boolean isColorBox = false;
+ boolean isHTML = false;
+
+ TextFieldBuilder(final String name, final LocTextKey label, final String value) {
+ super(name, label, value);
+ }
+
+ public TextFieldBuilder asPasswordField() {
+ this.isPassword = true;
+ return this;
+ }
+
+ public TextFieldBuilder asNumber() {
+ this.isNumber = true;
+ return this;
+ }
+
+ public TextFieldBuilder asNumber(final Consumer numberCheck) {
+ this.isNumber = true;
+ this.numberCheck = numberCheck;
+ return this;
+ }
+
+ public TextFieldBuilder asArea(final int minHeight) {
+ this.areaMinHeight = minHeight;
+ return asArea();
+ }
+
+ public TextFieldBuilder asArea() {
+ this.isArea = true;
+ this.titleValign = SWT.CENTER;
+ return this;
+ }
+
+ public TextFieldBuilder asHTML() {
+ this.isHTML = true;
+ return this;
+ }
+
+ public FieldBuilder> asHTML(final boolean html) {
+ this.isHTML = html;
+ return this;
+ }
+
+ public TextFieldBuilder asColorBox() {
+ this.isColorBox = true;
+ return this;
+ }
+
+ @Override
+ void build(final FormBuilder builder) {
+ final boolean readonly = builder.readonly || this.readonly;
+ final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
+ final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
+
+ if (readonly && this.isHTML) {
+ final Browser browser = new Browser(fieldGrid, SWT.NONE);
+ final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
+ gridData.minimumHeight = this.areaMinHeight;
+ browser.setBackground(new Color(builder.formParent.getDisplay(), new RGB(250, 250, 250)));
+ browser.setLayoutData(gridData);
+ if (StringUtils.isNoneBlank(this.value)) {
+ browser.setText(createHTMLText(this.value));
+ } else if (readonly) {
+ browser.setText(Constants.EMPTY_NOTE);
+ }
+ builder.form.putReadonlyField(this.name, titleLabel, browser);
+ return;
+ }
+
+ final Text textInput = (this.isNumber)
+ ? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly)
+ : (this.isArea)
+ ? builder.widgetFactory.textAreaInput(fieldGrid, readonly)
+ : builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly);
+
+ final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
+ if (this.isArea) {
+ gridData.minimumHeight = this.areaMinHeight;
+ } else if (this.isColorBox) {
+ gridData.minimumHeight = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
+ textInput.setData(RWT.CUSTOM_VARIANT, "colorbox");
+ }
+ textInput.setLayoutData(gridData);
+ if (StringUtils.isNoneBlank(this.value)) {
+ textInput.setText(this.value);
+ } else if (readonly) {
+ textInput.setText(Constants.EMPTY_NOTE);
+ }
+
+ if (readonly) {
+ textInput.setEditable(false);
+ builder.form.putReadonlyField(this.name, titleLabel, textInput);
+ } else {
+ final Label errorLabel = createErrorLabel(fieldGrid);
+ builder.form.putField(this.name, titleLabel, textInput, errorLabel);
+ builder.setFieldVisible(this.visible, this.name);
+ }
+
+ }
+
+ private String createHTMLText(final String text) {
+ return HTML_TEXT_BLOCK_START
+ + text
+ .replace("> lmsTypeResources() {
- return Arrays.asList(LmsType.values())
- .stream()
+ return Arrays.stream(LmsType.values())
.filter(lmsType -> lmsType != LmsType.MOCKUP || this.mock_lms_enabled)
.map(lmsType -> new Tuple<>(
lmsType.name(),
@@ -175,8 +174,7 @@ public class ResourceService {
}
public List> clientEventTypeResources() {
- return Arrays.asList(EventType.values())
- .stream()
+ return Arrays.stream(EventType.values())
.filter(eventType -> !CLIENT_EVENT_TYPE_EXCLUDE_MAP.contains(eventType))
.map(eventType -> new Tuple<>(
eventType.name(),
@@ -200,8 +198,7 @@ public class ResourceService {
}
public List> indicatorTypeResources() {
- return Arrays.asList(IndicatorType.values())
- .stream()
+ return Arrays.stream(IndicatorType.values())
.map(type -> new Tuple3<>(
type.name(),
this.i18nSupport.getText(EXAM_INDICATOR_TYPE_PREFIX + type.name(), type.name()),
@@ -295,8 +292,7 @@ public class ResourceService {
}
public List> entityTypeResources() {
- return Arrays.asList(EntityType.values())
- .stream()
+ return Arrays.stream(EntityType.values())
.filter(type -> !ENTITY_TYPE_EXCLUDE_MAP.contains(type))
.map(type -> new Tuple<>(type.name(), getEntityTypeName(type)))
.sorted(RESOURCE_COMPARATOR)
@@ -319,8 +315,7 @@ public class ResourceService {
}
public List> userActivityTypeResources() {
- return Arrays.asList(UserLogActivityType.values())
- .stream()
+ return Arrays.stream(UserLogActivityType.values())
.map(type -> new Tuple<>(type.name(), getUserActivityTypeName(type)))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
@@ -363,16 +358,13 @@ public class ResourceService {
}
public List> examTypeResources() {
- return Arrays.asList(ExamType.values())
- .stream()
- .filter(type -> type != ExamType.UNDEFINED)
+ return Arrays.stream(ExamType.values())
.map(type -> new Tuple3<>(
type.name(),
this.i18nSupport.getText(EXAM_TYPE_PREFIX + type.name()),
Utils.formatLineBreaks(this.i18nSupport.getText(
- this.i18nSupport.getText(EXAM_TYPE_PREFIX + type.name()) + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
- StringUtils.EMPTY))
- ))
+ EXAM_INDICATOR_TYPE_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
+ StringUtils.EMPTY))))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}
@@ -382,8 +374,7 @@ public class ResourceService {
}
public List> examConfigStatusResources(final boolean isAttachedToExam) {
- return Arrays.asList(ConfigurationStatus.values())
- .stream()
+ return Arrays.stream(ConfigurationStatus.values())
.filter(status -> {
if (isAttachedToExam) {
return status != ConfigurationStatus.READY_TO_USE;
@@ -579,8 +570,7 @@ public class ResourceService {
}
public List> getAttributeTypeResources() {
- return Arrays.asList(AttributeType.values())
- .stream()
+ return Arrays.stream(AttributeType.values())
.filter(type -> !ATTRIBUTE_TYPES_NOT_DISPLAYED.contains(type))
.map(type -> new Tuple<>(getAttributeTypeFilterName(type), getAttributeTypeName(type)))
.sorted(RESOURCE_COMPARATOR)
@@ -629,21 +619,25 @@ public class ResourceService {
}
public List> sebRestrictionWhiteListResources() {
- return Arrays.asList(WhiteListPath.values())
- .stream()
- .map(type -> new Tuple<>(
+ return Arrays.stream(WhiteListPath.values())
+ .map(type -> new Tuple3<>(
type.key,
- this.i18nSupport.getText(SEB_RESTRICTION_WHITE_LIST_PREFIX + type.name(), type.key)))
+ this.i18nSupport.getText(SEB_RESTRICTION_WHITE_LIST_PREFIX + type.name(), type.key),
+ Utils.formatLineBreaks(this.i18nSupport.getText(
+ SEB_RESTRICTION_WHITE_LIST_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
+ StringUtils.EMPTY))))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}
public List> sebRestrictionPermissionResources() {
- return Arrays.asList(PermissionComponent.values())
- .stream()
- .map(type -> new Tuple<>(
+ return Arrays.stream(PermissionComponent.values())
+ .map(type -> new Tuple3<>(
type.key,
- this.i18nSupport.getText(SEB_RESTRICTION_PERMISSIONS_PREFIX + type.name(), type.key)))
+ this.i18nSupport.getText(SEB_RESTRICTION_PERMISSIONS_PREFIX + type.name(), type.key),
+ Utils.formatLineBreaks(this.i18nSupport.getText(
+ SEB_RESTRICTION_PERMISSIONS_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
+ StringUtils.EMPTY))))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java
index 3d81e1d7..9570c79d 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java
@@ -1,542 +1,553 @@
-/*
- * 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.page;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.function.BooleanSupplier;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
-import ch.ethz.seb.sebserver.gbl.model.Activatable;
-import ch.ethz.seb.sebserver.gbl.model.Entity;
-import ch.ethz.seb.sebserver.gbl.model.EntityKey;
-import ch.ethz.seb.sebserver.gbl.model.Page;
-import ch.ethz.seb.sebserver.gbl.util.Result;
-import ch.ethz.seb.sebserver.gbl.util.Tuple;
-import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
-import ch.ethz.seb.sebserver.gui.form.FormBuilder;
-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.i18n.PolyglotPageService;
-import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
-import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEvent;
-import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageState;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
-import ch.ethz.seb.sebserver.gui.table.EntityTable;
-import ch.ethz.seb.sebserver.gui.table.TableBuilder;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-
-/** The main page service that provides functionality to build a page
- * with forms and tables as well as dealing with page actions */
-public interface PageService {
-
- Logger log = LoggerFactory.getLogger(PageService.class);
-
- /** Get the WidgetFactory service
- *
- * @return the WidgetFactory service */
- WidgetFactory getWidgetFactory();
-
- /** Get the polyglotPageService service
- *
- * @return the polyglotPageService service */
- PolyglotPageService getPolyglotPageService();
-
- /** Get the underling AuthorizationContextHolder for the current user session
- *
- * @return the underling AuthorizationContextHolder for the current user session */
- AuthorizationContextHolder getAuthorizationContextHolder();
-
- /** Get the I18nSupport (internationalization support) service
- *
- * @return the I18nSupport (internationalization support) service */
- I18nSupport getI18nSupport();
-
- /** Get the ResourceService
- *
- * @return the ResourceService */
- ResourceService getResourceService();
-
- /** get the JSONMapper for parse, read and write JSON
- *
- * @return the JSONMapper for parse, read and write JSON */
- JSONMapper getJSONMapper();
-
- /** Get the RestService bean
- *
- * @return the RestService bean */
- RestService getRestService();
-
- /** Use this to get the CurrentUser facade
- *
- * @return the CurrentUser facade */
- CurrentUser getCurrentUser();
-
- /** Get the PageState of the current user.
- *
- * @return PageState of the current user. */
- PageState getCurrentState();
-
- /** Get a PageAction function to go back to the current state.
- *
- * @return a PageAction function to go back to the current state. */
- default Function backToCurrentFunction() {
- final PageState currentState = this.getCurrentState();
- return action -> (currentState != null) ? currentState.gotoAction : action;
- }
-
- /** Get a page action execution function for switching the activity of currently selected
- * entities from a given entity-table.
- *
- * @param table the entity table
- * @param noSelectionText LocTextKey for missing selection message
- * @return page action execution function for switching the activity */
- Function activationToggleActionFunction(
- EntityTable table,
- LocTextKey noSelectionText);
-
- /** Get a message supplier to notify deactivation dependencies to the user for all given entities
- *
- * @param entities Set of entities to collect the dependencies for
- * @return a message supplier to notify deactivation dependencies to the user */
- Supplier confirmDeactivation(final Set extends T> entities);
-
- /** Get a message supplier to notify deactivation dependencies to the user for given entity
- *
- * @param entity the entity instance
- * @return a message supplier to notify deactivation dependencies to the user */
- default Supplier confirmDeactivation(final T entity) {
- return confirmDeactivation(new HashSet<>(Arrays.asList(entity)));
- }
-
- /** Get a message supplier to notify deactivation dependencies to the user for given entity table selection
- *
- * @param table the entity table
- * @return a message supplier to notify deactivation dependencies to the user */
- default Supplier confirmDeactivation(final EntityTable table) {
- return () -> {
- return confirmDeactivation(table
- .getSelectedROWData()
- .stream()
- .filter(e -> e.isActive()) // NOTE: Activatable::isActive leads to an error here!?
- .collect(Collectors.toSet()))
- .get();
- };
- }
-
- /** Use this to get an table selection action publisher that processes the action
- * activation on table selection.
- *
- * @param pageContext the current PageContext
- * @param actionDefinitions list of action definitions that activity should be toggled on table selection
- * @return the selection publisher that handles this defines action activation on table selection */
- default Consumer> getSelectionPublisher(
- final PageContext pageContext,
- final ActionDefinition... actionDefinitions) {
-
- return rows -> {
- firePageEvent(
- new ActionActivationEvent(!rows.isEmpty(), actionDefinitions),
- pageContext);
- };
- }
-
- /** Use this to get an table selection action publisher that processes the action
- * activation on table selection.
- *
- * This additional has the ability to define a entity activity action that is toggles in
- * case of the selected entity
- *
- * @param toggle the base entity activity action definition
- * @param activate the entity activation action definition
- * @param deactivate the entity deactivation action definition
- * @param pageContext the current PageContext
- * @param actionDefinitions list of action definitions that activity should be toggled on table selection
- * @return the selection publisher that handles this defines action activation on table selection */
- default Consumer> getSelectionPublisher(
- final ActionDefinition toggle,
- final ActionDefinition activate,
- final ActionDefinition deactivate,
- final PageContext pageContext,
- final ActionDefinition... actionDefinitions) {
-
- return rows -> {
-
- if (!rows.isEmpty()) {
- firePageEvent(
- new ActionActivationEvent(
- true,
- new Tuple<>(
- toggle,
- rows.iterator().next().isActive()
- ? deactivate
- : activate),
- actionDefinitions),
- pageContext);
- } else {
- firePageEvent(
- new ActionActivationEvent(false, actionDefinitions),
- pageContext);
- }
- };
- }
-
- /** Publishes a given PageEvent to the current page tree
- * This goes through the page-tree and collects all listeners the are listen to
- * the specified page event type.
- *
- * @param event the concrete PageEvent instance */
- void firePageEvent(T event, PageContext pageContext);
-
- /** Executes the given PageAction and if successful, propagate an ActionEvent to the current page.
- *
- * @param pageAction the PageAction to execute */
- default void executePageAction(final PageAction pageAction) {
- executePageAction(pageAction, result -> {
- });
- }
-
- /** Executes the given PageAction and if successful, propagate an ActionEvent to the current page.
- *
- * @param pageAction the PageAction to execute
- * @param callback a callback to react on PageAction execution. The Callback gets a Result referencing to
- * the executed PageAction or to an error if the PageAction has not been executed */
- void executePageAction(PageAction pageAction, Consumer> callback);
-
- /** Publishes a PageAction to the current page. This uses the firePageEvent form
- * PageContext of the given PageAction and fires a ActionPublishEvent for the given PageAction
- *
- * All ActionPublishEventListeners that are registered within the current page will
- * receive the ActionPublishEvent sent by this.
- *
- * @param pageAction the PageAction to publish
- * @param active indicates whether the action is active or not */
- void publishAction(final PageAction pageAction, boolean active);
-
- /** Get a new FormBuilder for the given PageContext
- * This FormBuilder uses the standard form grid which has 8 rows (2 title, 5 input and 1 right-space)
- *
- * @param pageContext the PageContext on that the FormBuilder should work
- * @return a FormBuilder instance for the given PageContext and with number of rows */
- default FormBuilder formBuilder(final PageContext pageContext) {
- return formBuilder(pageContext, 8);
- }
-
- /** Get a new FormBuilder for the given PageContext and with number of rows.
- *
- * @param pageContext the PageContext on that the FormBuilder should work
- * @param rows the number of rows of the from
- * @return a FormBuilder instance for the given PageContext and with number of rows */
- FormBuilder formBuilder(PageContext pageContext, int rows);
-
- /** Get an new TableBuilder for specified page based RestCall.
- *
- * @param apiCall the SEB Server API RestCall that feeds the table with data
- * @param the type of the Entity of the table
- * @return TableBuilder of specified type */
- default TableBuilder entityTableBuilder(final RestCall> apiCall) {
- return entityTableBuilder(apiCall.getClass().getSimpleName(), apiCall);
- }
-
- /** Get an new TableBuilder for specified page based RestCall.
- *
- * @param The name of the table to build
- * @param apiCall the SEB Server API RestCall that feeds the table with data
- * @param the type of the Entity of the table
- * @return TableBuilder of specified type */
- TableBuilder entityTableBuilder(
- String name,
- RestCall> apiCall);
-
- /** Get a new PageActionBuilder for a given PageContext.
- *
- * @param pageContext the PageContext that is used by the new PageActionBuilder
- * @return new PageActionBuilder to build PageAction */
- default PageActionBuilder pageActionBuilder(final PageContext pageContext) {
- return new PageActionBuilder(this, pageContext);
- }
-
- /** This triggers a logout on the current authorization context to logout the current user
- * and forward to the login page with showing a successful logout message to the user. */
- void logout(PageContext pageContext);
-
- default T logoutOnError(final Exception t, final PageContext pageContext) {
- log.error("Unexpected, Current User related error.Automatically logout and cleanup current user session. ", t);
- logout(pageContext);
- return null;
- }
-
- /** Clears the PageState of the current users page */
- void clearState();
-
- /** Key to store the ScrolledComposite update function within Control data map */
- static String SCROLLED_COMPOSITE_UPDATE = "SCROLLED_COMPOSITE_UPDATE";
-
- /** Creates a ScrolledComposite with content supplied the given content creation function.
- * The content creation function is used to create the content Composite as a child of the
- * newly created ScrolledComposite.
- * Also adds an update function within the ScrolledComposite Data mapping. If a child inside
- * the ScrolledComposite changes its dimensions the method updateScrolledComposite must be
- * called to update the ScrolledComposite scrolled content.
- *
- * @param parent the parent Composite of the ScrolledComposite
- * @param contentFunction the content creation function
- * @param showScrollbars indicates whether the scrollbar shall always be shown
- * @return the child composite that is scrolled by the newly created ScrolledComposite */
- static Composite createManagedVScrolledComposite(
- final Composite parent,
- final Function contentFunction,
- final boolean showScrollbars) {
-
- final ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.BORDER | SWT.V_SCROLL);
- scrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true));
-
- final Composite content = contentFunction.apply(scrolledComposite);
- scrolledComposite.setContent(content);
- scrolledComposite.setExpandHorizontal(true);
- scrolledComposite.setExpandVertical(true);
- final Point parentSize = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT);
- scrolledComposite.setSize(parentSize);
- if (showScrollbars) {
- scrolledComposite.setAlwaysShowScrollBars(true);
- }
-
- final Runnable update = () -> {
- final Point computeSize = content.computeSize(SWT.DEFAULT - 20, SWT.DEFAULT);
- scrolledComposite.setMinSize(computeSize);
-
- };
-
- scrolledComposite.addListener(SWT.Resize, event -> update.run());
- scrolledComposite.setData(SCROLLED_COMPOSITE_UPDATE, update);
-
- return content;
- }
-
- /** Used to update the crolledComposite when some if its content has dynamically changed
- * its dimensions.
- *
- * @param composite The Component that changed its dimensions */
- static void updateScrolledComposite(final Composite composite) {
- if (composite == null) {
- return;
- }
-
- Composite parent = composite.getParent();
- while (parent != null) {
- final Object update = parent.getData(SCROLLED_COMPOSITE_UPDATE);
- if (update != null) {
- ((Runnable) update).run();
- return;
- }
- parent = parent.getParent();
- }
- }
-
- static void clearComposite(final Composite parent) {
- if (parent == null) {
- return;
- }
-
- for (final Control control : parent.getChildren()) {
- control.dispose();
- }
- }
-
- public class PageActionBuilder {
- private final PageService pageService;
- private final PageContext originalPageContext;
-
- private PageContext pageContext;
- private ActionDefinition definition;
- private Supplier confirm;
- private LocTextKey successMessage;
- private Supplier> selectionSupplier;
- private LocTextKey noSelectionMessage;
- private Function exec;
- private boolean fireActionEvent = true;
- private boolean ignoreMoveAwayFromEdit = false;
- private PageAction switchAction;
-
- protected PageActionBuilder(final PageService pageService, final PageContext pageContext) {
- this.pageService = pageService;
- this.originalPageContext = pageContext;
- }
-
- public PageActionBuilder newAction(final ActionDefinition definition) {
- final PageActionBuilder newBuilder = new PageActionBuilder(this.pageService, this.originalPageContext);
- newBuilder.pageContext = originalPageContext.copy();
- newBuilder.definition = definition;
- newBuilder.confirm = null;
- newBuilder.successMessage = null;
- newBuilder.selectionSupplier = null;
- newBuilder.noSelectionMessage = null;
- newBuilder.exec = null;
- newBuilder.fireActionEvent = true;
- newBuilder.ignoreMoveAwayFromEdit = false;
- newBuilder.switchAction = null;
- return newBuilder;
- }
-
- public PageAction create() {
- return new PageAction(
- definition,
- confirm,
- successMessage,
- selectionSupplier,
- noSelectionMessage,
- pageContext,
- exec,
- fireActionEvent,
- ignoreMoveAwayFromEdit,
- switchAction);
- }
-
- public PageActionBuilder publish() {
- return publish(true);
- }
-
- public PageActionBuilder publish(final boolean active) {
- pageService.publishAction(create(), active);
- return this;
- }
-
- public PageActionBuilder publishIf(final BooleanSupplier condition) {
- return publishIf(condition, true);
- }
-
- public PageActionBuilder publishIf(final BooleanSupplier condition, final boolean active) {
- if (!condition.getAsBoolean()) {
- return this;
- }
-
- return this.publish(active);
- }
-
- public PageActionBuilder withSwitchAction(final PageAction switchAction) {
- this.switchAction = switchAction;
- return this;
- }
-
- public PageActionBuilder withExec(final Function exec) {
- this.exec = exec;
- return this;
- }
-
- public PageActionBuilder withSelectionSupplier(final Supplier> selectionSupplier) {
- this.selectionSupplier = selectionSupplier;
- return this;
- }
-
- public PageActionBuilder withSelect(
- final Supplier> selectionSupplier,
- final Function exec,
- final LocTextKey noSelectionMessage) {
-
- this.selectionSupplier = selectionSupplier;
- this.exec = exec;
- this.noSelectionMessage = noSelectionMessage;
- return this;
- }
-
- public PageActionBuilder withSimpleRestCall(
- final RestService restService,
- final Class extends RestCall> restCallType) {
-
- this.exec = action -> {
- final PageContext pageContext = action.pageContext();
- restService.getBuilder(restCallType)
- .withURIVariable(
- API.PARAM_MODEL_ID,
- action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
- .call()
- .onError(pageContext::notifyUnexpectedError);
- return action;
- };
-
- return this;
- }
-
- public PageActionBuilder withConfirm(final Supplier confirm) {
- this.confirm = confirm;
- return this;
- }
-
- public PageActionBuilder withSuccess(final String successMessageKey) {
- this.successMessage = new LocTextKey(successMessageKey);
- return this;
- }
-
- public PageActionBuilder noEventPropagation() {
- this.fireActionEvent = false;
- return this;
- }
-
- public PageActionBuilder withEntityKey(final EntityKey entityKey) {
- this.pageContext = this.pageContext.withEntityKey(entityKey);
- return this;
- }
-
- public PageActionBuilder ignoreMoveAwayFromEdit() {
- this.ignoreMoveAwayFromEdit = true;
- return this;
- }
-
- public PageActionBuilder withEntityKey(final Long modelId, final EntityType entityType) {
- if (modelId != null) {
- return withEntityKey(String.valueOf(modelId), entityType);
- }
-
- return this;
- }
-
- public PageActionBuilder withEntityKey(final String modelId, final EntityType entityType) {
- if (modelId == null || entityType == null) {
- return this;
- }
-
- this.pageContext = this.pageContext.withEntityKey(new EntityKey(modelId, entityType));
- return this;
- }
-
- public PageActionBuilder withParentEntityKey(final EntityKey entityKey) {
- this.pageContext = this.pageContext.withParentEntityKey(entityKey);
- return this;
- }
-
- public PageActionBuilder withAttribute(final String name, final String value) {
- this.pageContext = this.pageContext.withAttribute(name, value);
- return this;
- }
-
- }
-
-}
+/*
+ * 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.page;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
+import ch.ethz.seb.sebserver.gbl.model.Activatable;
+import ch.ethz.seb.sebserver.gbl.model.Entity;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+import ch.ethz.seb.sebserver.gbl.model.Page;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.gbl.util.Tuple;
+import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
+import ch.ethz.seb.sebserver.gui.form.FormBuilder;
+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.i18n.PolyglotPageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
+import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEvent;
+import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageState;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
+import ch.ethz.seb.sebserver.gui.table.TableBuilder;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+
+/** The main page service that provides functionality to build a page
+ * with forms and tables as well as dealing with page actions */
+public interface PageService {
+
+ Logger log = LoggerFactory.getLogger(PageService.class);
+
+ /** Get the WidgetFactory service
+ *
+ * @return the WidgetFactory service */
+ WidgetFactory getWidgetFactory();
+
+ /** Get the polyglotPageService service
+ *
+ * @return the polyglotPageService service */
+ PolyglotPageService getPolyglotPageService();
+
+ /** Get the underling AuthorizationContextHolder for the current user session
+ *
+ * @return the underling AuthorizationContextHolder for the current user session */
+ AuthorizationContextHolder getAuthorizationContextHolder();
+
+ /** Get the I18nSupport (internationalization support) service
+ *
+ * @return the I18nSupport (internationalization support) service */
+ I18nSupport getI18nSupport();
+
+ /** Get the ResourceService
+ *
+ * @return the ResourceService */
+ ResourceService getResourceService();
+
+ /** get the JSONMapper for parse, read and write JSON
+ *
+ * @return the JSONMapper for parse, read and write JSON */
+ JSONMapper getJSONMapper();
+
+ /** Get the RestService bean
+ *
+ * @return the RestService bean */
+ RestService getRestService();
+
+ /** Use this to get the CurrentUser facade
+ *
+ * @return the CurrentUser facade */
+ CurrentUser getCurrentUser();
+
+ /** Get the PageState of the current user.
+ *
+ * @return PageState of the current user. */
+ PageState getCurrentState();
+
+ /** Get a PageAction function to go back to the current state.
+ *
+ * @return a PageAction function to go back to the current state. */
+ default Function backToCurrentFunction() {
+ final PageState currentState = this.getCurrentState();
+ return action -> (currentState != null) ? currentState.gotoAction : action;
+ }
+
+ /** Get a page action execution function for switching the activity of currently selected
+ * entities from a given entity-table.
+ *
+ * @param table the entity table
+ * @param noSelectionText LocTextKey for missing selection message
+ * @param testBeforeActivation a function to test before activation. This function shall throw an error if test fails.
+ * My be null if no specific test is needed before activation
+ * @return page action execution function for switching the activity */
+ Function activationToggleActionFunction(
+ EntityTable table,
+ LocTextKey noSelectionText,
+ Function testBeforeActivation);
+
+ /** Get a page action execution function for switching the activity of currently selected
+ * entities from a given entity-table.
+ *
+ * @param table the entity table
+ * @param noSelectionText LocTextKey for missing selection message
+ * @return page action execution function for switching the activity */
+ default Function activationToggleActionFunction(
+ EntityTable table,
+ LocTextKey noSelectionText) {
+ return this.activationToggleActionFunction(table, noSelectionText, null);
+ }
+
+ /** Get a message supplier to notify deactivation dependencies to the user for all given entities
+ *
+ * @param entities Set of entities to collect the dependencies for
+ * @return a message supplier to notify deactivation dependencies to the user */
+ Supplier confirmDeactivation(final Set extends T> entities);
+
+ /** Get a message supplier to notify deactivation dependencies to the user for given entity
+ *
+ * @param entity the entity instance
+ * @return a message supplier to notify deactivation dependencies to the user */
+ default Supplier confirmDeactivation(final T entity) {
+ return confirmDeactivation(new HashSet<>(Arrays.asList(entity)));
+ }
+
+ /** Get a message supplier to notify deactivation dependencies to the user for given entity table selection
+ *
+ * @param table the entity table
+ * @return a message supplier to notify deactivation dependencies to the user */
+ default Supplier confirmDeactivation(final EntityTable table) {
+ return () -> confirmDeactivation(table
+ .getSelectedROWData()
+ .stream()
+ .filter(Activatable::isActive) // NOTE: Activatable::isActive leads to an error here!?
+ .collect(Collectors.toSet()))
+ .get();
+ }
+
+ /** Use this to get an table selection action publisher that processes the action
+ * activation on table selection.
+ *
+ * @param pageContext the current PageContext
+ * @param actionDefinitions list of action definitions that activity should be toggled on table selection
+ * @return the selection publisher that handles this defines action activation on table selection */
+ default Consumer> getSelectionPublisher(
+ final PageContext pageContext,
+ final ActionDefinition... actionDefinitions) {
+
+ return rows -> firePageEvent(
+ new ActionActivationEvent(!rows.isEmpty(), actionDefinitions),
+ pageContext);
+ }
+
+ /** Use this to get an table selection action publisher that processes the action
+ * activation on table selection.
+ *
+ * This additional has the ability to define a entity activity action that is toggles in
+ * case of the selected entity
+ *
+ * @param toggle the base entity activity action definition
+ * @param activate the entity activation action definition
+ * @param deactivate the entity deactivation action definition
+ * @param pageContext the current PageContext
+ * @param actionDefinitions list of action definitions that activity should be toggled on table selection
+ * @return the selection publisher that handles this defines action activation on table selection */
+ default Consumer> getSelectionPublisher(
+ final ActionDefinition toggle,
+ final ActionDefinition activate,
+ final ActionDefinition deactivate,
+ final PageContext pageContext,
+ final ActionDefinition... actionDefinitions) {
+
+ return rows -> {
+
+ if (!rows.isEmpty()) {
+ firePageEvent(
+ new ActionActivationEvent(
+ true,
+ new Tuple<>(
+ toggle,
+ rows.iterator().next().isActive()
+ ? deactivate
+ : activate),
+ actionDefinitions),
+ pageContext);
+ } else {
+ firePageEvent(
+ new ActionActivationEvent(false, actionDefinitions),
+ pageContext);
+ }
+ };
+ }
+
+ /** Publishes a given PageEvent to the current page tree
+ * This goes through the page-tree and collects all listeners the are listen to
+ * the specified page event type.
+ *
+ * @param event the concrete PageEvent instance */
+ void firePageEvent(T event, PageContext pageContext);
+
+ /** Executes the given PageAction and if successful, propagate an ActionEvent to the current page.
+ *
+ * @param pageAction the PageAction to execute */
+ default void executePageAction(final PageAction pageAction) {
+ executePageAction(pageAction, result -> {
+ });
+ }
+
+ /** Executes the given PageAction and if successful, propagate an ActionEvent to the current page.
+ *
+ * @param pageAction the PageAction to execute
+ * @param callback a callback to react on PageAction execution. The Callback gets a Result referencing to
+ * the executed PageAction or to an error if the PageAction has not been executed */
+ void executePageAction(PageAction pageAction, Consumer> callback);
+
+ /** Publishes a PageAction to the current page. This uses the firePageEvent form
+ * PageContext of the given PageAction and fires a ActionPublishEvent for the given PageAction
+ *
+ * All ActionPublishEventListeners that are registered within the current page will
+ * receive the ActionPublishEvent sent by this.
+ *
+ * @param pageAction the PageAction to publish
+ * @param active indicates whether the action is active or not */
+ void publishAction(final PageAction pageAction, boolean active);
+
+ /** Get a new FormBuilder for the given PageContext
+ * This FormBuilder uses the standard form grid which has 8 rows (2 title, 5 input and 1 right-space)
+ *
+ * @param pageContext the PageContext on that the FormBuilder should work
+ * @return a FormBuilder instance for the given PageContext and with number of rows */
+ default FormBuilder formBuilder(final PageContext pageContext) {
+ return formBuilder(pageContext, 8);
+ }
+
+ /** Get a new FormBuilder for the given PageContext and with number of rows.
+ *
+ * @param pageContext the PageContext on that the FormBuilder should work
+ * @param rows the number of rows of the from
+ * @return a FormBuilder instance for the given PageContext and with number of rows */
+ FormBuilder formBuilder(PageContext pageContext, int rows);
+
+ /** Get an new TableBuilder for specified page based RestCall.
+ *
+ * @param apiCall the SEB Server API RestCall that feeds the table with data
+ * @param the type of the Entity of the table
+ * @return TableBuilder of specified type */
+ default TableBuilder entityTableBuilder(final RestCall> apiCall) {
+ return entityTableBuilder(apiCall.getClass().getSimpleName(), apiCall);
+ }
+
+ /** Get an new TableBuilder for specified page based RestCall.
+ *
+ * @param name The name of the table to build
+ * @param apiCall the SEB Server API RestCall that feeds the table with data
+ * @param the type of the Entity of the table
+ * @return TableBuilder of specified type */
+ TableBuilder entityTableBuilder(
+ String name,
+ RestCall> apiCall);
+
+ /** Get a new PageActionBuilder for a given PageContext.
+ *
+ * @param pageContext the PageContext that is used by the new PageActionBuilder
+ * @return new PageActionBuilder to build PageAction */
+ default PageActionBuilder pageActionBuilder(final PageContext pageContext) {
+ return new PageActionBuilder(this, pageContext);
+ }
+
+ /** This triggers a logout on the current authorization context to logout the current user
+ * and forward to the login page with showing a successful logout message to the user. */
+ void logout(PageContext pageContext);
+
+ default T logoutOnError(final Exception t, final PageContext pageContext) {
+ log.error("Unexpected, Current User related error.Automatically logout and cleanup current user session. ", t);
+ logout(pageContext);
+ return null;
+ }
+
+ /** Clears the PageState of the current users page */
+ void clearState();
+
+ /** Key to store the ScrolledComposite update function within Control data map */
+ String SCROLLED_COMPOSITE_UPDATE = "SCROLLED_COMPOSITE_UPDATE";
+
+ /** Creates a ScrolledComposite with content supplied the given content creation function.
+ * The content creation function is used to create the content Composite as a child of the
+ * newly created ScrolledComposite.
+ * Also adds an update function within the ScrolledComposite Data mapping. If a child inside
+ * the ScrolledComposite changes its dimensions the method updateScrolledComposite must be
+ * called to update the ScrolledComposite scrolled content.
+ *
+ * @param parent the parent Composite of the ScrolledComposite
+ * @param contentFunction the content creation function
+ * @param showScrollbars indicates whether the scrollbar shall always be shown
+ * @return the child composite that is scrolled by the newly created ScrolledComposite */
+ static Composite createManagedVScrolledComposite(
+ final Composite parent,
+ final Function contentFunction,
+ final boolean showScrollbars) {
+
+ final ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.BORDER | SWT.V_SCROLL);
+ scrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true));
+
+ final Composite content = contentFunction.apply(scrolledComposite);
+ scrolledComposite.setContent(content);
+ scrolledComposite.setExpandHorizontal(true);
+ scrolledComposite.setExpandVertical(true);
+ final Point parentSize = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ scrolledComposite.setSize(parentSize);
+ if (showScrollbars) {
+ scrolledComposite.setAlwaysShowScrollBars(true);
+ }
+
+ final Runnable update = () -> {
+ final Point computeSize = content.computeSize(SWT.DEFAULT - 20, SWT.DEFAULT);
+ scrolledComposite.setMinSize(computeSize);
+
+ };
+
+ scrolledComposite.addListener(SWT.Resize, event -> update.run());
+ scrolledComposite.setData(SCROLLED_COMPOSITE_UPDATE, update);
+
+ return content;
+ }
+
+ /** Used to update the crolledComposite when some if its content has dynamically changed
+ * its dimensions.
+ *
+ * @param composite The Component that changed its dimensions */
+ static void updateScrolledComposite(final Composite composite) {
+ if (composite == null) {
+ return;
+ }
+
+ Composite parent = composite.getParent();
+ while (parent != null) {
+ final Object update = parent.getData(SCROLLED_COMPOSITE_UPDATE);
+ if (update != null) {
+ ((Runnable) update).run();
+ return;
+ }
+ parent = parent.getParent();
+ }
+ }
+
+ static void clearComposite(final Composite parent) {
+ if (parent == null) {
+ return;
+ }
+
+ for (final Control control : parent.getChildren()) {
+ control.dispose();
+ }
+ }
+
+ class PageActionBuilder {
+ private final PageService pageService;
+ private final PageContext originalPageContext;
+
+ private PageContext pageContext;
+ private ActionDefinition definition;
+ private Supplier confirm;
+ private LocTextKey successMessage;
+ private Supplier> selectionSupplier;
+ private LocTextKey noSelectionMessage;
+ private Function exec;
+ private boolean fireActionEvent = true;
+ private boolean ignoreMoveAwayFromEdit = false;
+ private PageAction switchAction;
+
+ protected PageActionBuilder(final PageService pageService, final PageContext pageContext) {
+ this.pageService = pageService;
+ this.originalPageContext = pageContext;
+ }
+
+ public PageActionBuilder newAction(final ActionDefinition definition) {
+ final PageActionBuilder newBuilder = new PageActionBuilder(this.pageService, this.originalPageContext);
+ newBuilder.pageContext = originalPageContext.copy();
+ newBuilder.definition = definition;
+ newBuilder.confirm = null;
+ newBuilder.successMessage = null;
+ newBuilder.selectionSupplier = null;
+ newBuilder.noSelectionMessage = null;
+ newBuilder.exec = null;
+ newBuilder.fireActionEvent = true;
+ newBuilder.ignoreMoveAwayFromEdit = false;
+ newBuilder.switchAction = null;
+ return newBuilder;
+ }
+
+ public PageAction create() {
+ return new PageAction(
+ definition,
+ confirm,
+ successMessage,
+ selectionSupplier,
+ noSelectionMessage,
+ pageContext,
+ exec,
+ fireActionEvent,
+ ignoreMoveAwayFromEdit,
+ switchAction);
+ }
+
+ public PageActionBuilder publish() {
+ return publish(true);
+ }
+
+ public PageActionBuilder publish(final boolean active) {
+ pageService.publishAction(create(), active);
+ return this;
+ }
+
+ public PageActionBuilder publishIf(final BooleanSupplier condition) {
+ return publishIf(condition, true);
+ }
+
+ public PageActionBuilder publishIf(final BooleanSupplier condition, final boolean active) {
+ if (!condition.getAsBoolean()) {
+ return this;
+ }
+
+ return this.publish(active);
+ }
+
+ public PageActionBuilder withSwitchAction(final PageAction switchAction) {
+ this.switchAction = switchAction;
+ return this;
+ }
+
+ public PageActionBuilder withExec(final Function exec) {
+ this.exec = exec;
+ return this;
+ }
+
+ public PageActionBuilder withSelectionSupplier(final Supplier> selectionSupplier) {
+ this.selectionSupplier = selectionSupplier;
+ return this;
+ }
+
+ public PageActionBuilder withSelect(
+ final Supplier> selectionSupplier,
+ final Function exec,
+ final LocTextKey noSelectionMessage) {
+
+ this.selectionSupplier = selectionSupplier;
+ this.exec = exec;
+ this.noSelectionMessage = noSelectionMessage;
+ return this;
+ }
+
+ public PageActionBuilder withSimpleRestCall(
+ final RestService restService,
+ final Class extends RestCall> restCallType) {
+
+ this.exec = action -> {
+ final PageContext pageContext = action.pageContext();
+ restService.getBuilder(restCallType)
+ .withURIVariable(
+ API.PARAM_MODEL_ID,
+ action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
+ .call()
+ .onError(pageContext::notifyUnexpectedError);
+ return action;
+ };
+
+ return this;
+ }
+
+ public PageActionBuilder withConfirm(final Supplier confirm) {
+ this.confirm = confirm;
+ return this;
+ }
+
+ public PageActionBuilder withSuccess(final String successMessageKey) {
+ this.successMessage = new LocTextKey(successMessageKey);
+ return this;
+ }
+
+ public PageActionBuilder noEventPropagation() {
+ this.fireActionEvent = false;
+ return this;
+ }
+
+ public PageActionBuilder withEntityKey(final EntityKey entityKey) {
+ this.pageContext = this.pageContext.withEntityKey(entityKey);
+ return this;
+ }
+
+ public PageActionBuilder ignoreMoveAwayFromEdit() {
+ this.ignoreMoveAwayFromEdit = true;
+ return this;
+ }
+
+ public PageActionBuilder withEntityKey(final Long modelId, final EntityType entityType) {
+ if (modelId != null) {
+ return withEntityKey(String.valueOf(modelId), entityType);
+ }
+
+ return this;
+ }
+
+ public PageActionBuilder withEntityKey(final String modelId, final EntityType entityType) {
+ if (modelId == null || entityType == null) {
+ return this;
+ }
+
+ this.pageContext = this.pageContext.withEntityKey(new EntityKey(modelId, entityType));
+ return this;
+ }
+
+ public PageActionBuilder withParentEntityKey(final EntityKey entityKey) {
+ this.pageContext = this.pageContext.withParentEntityKey(entityKey);
+ return this;
+ }
+
+ public PageActionBuilder withAttribute(final String name, final String value) {
+ this.pageContext = this.pageContext.withAttribute(name, value);
+ return this;
+ }
+
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java
index efe38947..dfc95b02 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java
@@ -1,384 +1,396 @@
-/*
- * 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.page.impl;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import javax.servlet.http.HttpSession;
-
-import org.eclipse.rap.rwt.RWT;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Service;
-
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
-import ch.ethz.seb.sebserver.gbl.model.Activatable;
-import ch.ethz.seb.sebserver.gbl.model.Entity;
-import ch.ethz.seb.sebserver.gbl.model.EntityKey;
-import ch.ethz.seb.sebserver.gbl.model.Page;
-import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
-import ch.ethz.seb.sebserver.gbl.util.Result;
-import ch.ethz.seb.sebserver.gui.form.FormBuilder;
-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.i18n.PolyglotPageService;
-import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
-import ch.ethz.seb.sebserver.gui.service.page.MultiPageMessageException;
-import ch.ethz.seb.sebserver.gui.service.page.PageContext;
-import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.PageStateDefinition.Type;
-import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
-import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent;
-import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent;
-import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
-import ch.ethz.seb.sebserver.gui.table.EntityTable;
-import ch.ethz.seb.sebserver.gui.table.TableBuilder;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-
-@Lazy
-@Service
-@GuiProfile
-public class PageServiceImpl implements PageService {
-
- private static final LocTextKey CONFIRM_DEACTIVATION_NO_DEP_KEY =
- new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies");
-
- private static final String CONFIRM_DEACTIVATION_KEY = "sebserver.dialog.confirm.deactivation";
-
- private static final Logger log = LoggerFactory.getLogger(PageServiceImpl.class);
-
- private static final LocTextKey MSG_GO_AWAY_FROM_EDIT =
- new LocTextKey("sebserver.overall.action.goAwayFromEditPageConfirm");
-
- private static final String ATTR_PAGE_STATE = "PAGE_STATE";
- private static final ListenerComparator LIST_COMPARATOR = new ListenerComparator();
-
- private final JSONMapper jsonMapper;
- private final WidgetFactory widgetFactory;
- private final PolyglotPageService polyglotPageService;
- private final ResourceService resourceService;
- private final CurrentUser currentUser;
-
- public PageServiceImpl(
- final JSONMapper jsonMapper,
- final WidgetFactory widgetFactory,
- final PolyglotPageService polyglotPageService,
- final ResourceService resourceService,
- final CurrentUser currentUser) {
-
- this.jsonMapper = jsonMapper;
- this.widgetFactory = widgetFactory;
- this.polyglotPageService = polyglotPageService;
- this.resourceService = resourceService;
- this.currentUser = currentUser;
- }
-
- @Override
- public WidgetFactory getWidgetFactory() {
- return this.widgetFactory;
- }
-
- @Override
- public PolyglotPageService getPolyglotPageService() {
- return this.polyglotPageService;
- }
-
- @Override
- public AuthorizationContextHolder getAuthorizationContextHolder() {
- return this.currentUser.getAuthorizationContextHolder();
- }
-
- @Override
- public I18nSupport getI18nSupport() {
- return this.widgetFactory.getI18nSupport();
- }
-
- @Override
- public ResourceService getResourceService() {
- return this.resourceService;
- }
-
- @Override
- public JSONMapper getJSONMapper() {
- return this.jsonMapper;
- }
-
- @Override
- public RestService getRestService() {
- if (this.resourceService == null) {
- return null;
- }
-
- return this.resourceService.getRestService();
- }
-
- @Override
- public CurrentUser getCurrentUser() {
- return this.currentUser;
- }
-
- @Override
- public PageState getCurrentState() {
- try {
-
- final HttpSession httpSession = RWT
- .getUISession()
- .getHttpSession();
-
- return (PageState) httpSession.getAttribute(ATTR_PAGE_STATE);
-
- } catch (final Exception e) {
- log.error("Failed to get current PageState: ", e);
- return null;
- }
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void firePageEvent(final T event, final PageContext pageContext) {
- final Class extends PageEvent> typeClass = event.getClass();
- final List> listeners = new ArrayList<>();
- ComposerService.traversePageTree(
- pageContext.getRoot(),
- c -> {
- final PageEventListener> listener =
- (PageEventListener>) c.getData(PageEventListener.LISTENER_ATTRIBUTE_KEY);
- return listener != null && listener.match(typeClass);
- },
- c -> listeners.add(((PageEventListener) c.getData(PageEventListener.LISTENER_ATTRIBUTE_KEY))));
-
- if (listeners.isEmpty()) {
- return;
- }
-
- listeners.stream()
- .sorted(LIST_COMPARATOR)
- .forEach(listener -> {
- try {
- listener.notify(event);
- } catch (final Exception e) {
- log.error("Unexpected error while notify PageEventListener: ", e);
- }
- });
- }
-
- @Override
- public void executePageAction(final PageAction pageAction, final Consumer> callback) {
- final PageState currentState = getCurrentState();
- if (!pageAction.ignoreMoveAwayFromEdit && currentState != null && currentState.type() == Type.FORM_EDIT) {
- pageAction.pageContext().applyConfirmDialog(
- MSG_GO_AWAY_FROM_EDIT,
- confirm -> {
- if (confirm) {
- exec(pageAction, callback);
- } else {
- callback.accept(Result.ofRuntimeError("Confirm denied"));
- }
- });
- } else {
- exec(pageAction, callback);
- }
- }
-
- @Override
- public Supplier confirmDeactivation(final Set extends T> entities) {
- final RestService restService = this.resourceService.getRestService();
- return () -> {
- if (entities == null || entities.isEmpty()) {
- return null;
- }
-
- try {
- final int dependencies = entities.stream()
- .flatMap(entity -> {
- final RestCall>.RestCallBuilder builder =
- restService.> getBuilder(
- entity.entityType(),
- CallType.GET_DEPENDENCIES);
-
- return builder
- .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(entity.getModelId()))
- .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.DEACTIVATE.name())
- .call()
- .getOrThrow().stream();
- })
- .collect(Collectors.toList())
- .size();
- if (dependencies > 0) {
- return new LocTextKey(CONFIRM_DEACTIVATION_KEY, String.valueOf(dependencies));
- } else {
- return CONFIRM_DEACTIVATION_NO_DEP_KEY;
- }
-
- } catch (final Exception e) {
- log.warn("Failed to get dependencyies. Error: {}", e.getMessage());
- return new LocTextKey(CONFIRM_DEACTIVATION_KEY, "");
- }
- };
- }
-
- @Override
- public Function activationToggleActionFunction(
- final EntityTable table,
- final LocTextKey noSelectionText) {
-
- return action -> {
- final Set selectedROWData = table.getSelectedROWData();
- if (selectedROWData == null || selectedROWData.isEmpty()) {
- throw new PageMessageException(noSelectionText);
- }
-
- final RestService restService = this.resourceService.getRestService();
- final EntityType entityType = table.getEntityType();
-
- final Collection errors = new ArrayList<>();
- for (final T entity : selectedROWData) {
- if (entity.isActive()) {
- restService.getBuilder(entityType, CallType.ACTIVATION_DEACTIVATE)
- .withURIVariable(API.PARAM_MODEL_ID, entity.getModelId())
- .call()
- .onError(errors::add);
- } else {
- restService.getBuilder(entityType, CallType.ACTIVATION_ACTIVATE)
- .withURIVariable(API.PARAM_MODEL_ID, entity.getModelId())
- .call()
- .onError(errors::add);
- }
- }
-
- if (!errors.isEmpty()) {
- final String entityTypeName = this.resourceService.getEntityTypeName(entityType);
- throw new MultiPageMessageException(
- new LocTextKey(PageContext.GENERIC_ACTIVATE_ERROR_TEXT_KEY, entityTypeName),
- errors);
- }
-
- return action;
- };
- }
-
- private void exec(final PageAction pageAction, final Consumer> callback) {
- pageAction.applyAction(result -> {
- if (!result.hasError()) {
-
- final PageAction action = result.get();
- if (pageAction.fireActionEvent) {
- firePageEvent(new ActionEvent(action), action.pageContext());
- }
-
- try {
- final HttpSession httpSession = RWT
- .getUISession()
- .getHttpSession();
-
- if (action != null &&
- action.fireActionEvent &&
- action.definition != null &&
- action.definition.targetState != null) {
-
- final PageState pageState = new PageState(action.definition.targetState, action);
- if (log.isDebugEnabled()) {
- log.debug("Set session PageState: {} : {}", pageState, httpSession.getId());
- }
- httpSession.setAttribute(ATTR_PAGE_STATE, pageState);
- }
- } catch (final Exception e) {
- log.error("Failed to set current PageState: ", e);
- }
- }
- callback.accept(result);
- });
- }
-
- @Override
- public void publishAction(final PageAction pageAction, final boolean active) {
- this.firePageEvent(new ActionPublishEvent(pageAction, active), pageAction.pageContext());
- }
-
- @Override
- public FormBuilder formBuilder(final PageContext pageContext, final int rows) {
- return new FormBuilder(this, pageContext, rows);
- }
-
- @Override
- public TableBuilder entityTableBuilder(
- final String name,
- final RestCall> apiCall) {
-
- return new TableBuilder<>(name, this, apiCall);
- }
-
- @Override
- public void logout(final PageContext pageContext) {
- this.clearState();
-
- try {
- final boolean logoutSuccessful = this.currentUser.logout();
-
- if (!logoutSuccessful) {
- log.warn("Failed to logout. See logfiles for more information");
- }
-
- } catch (final Exception e) {
- log.info("Cleanup logout failed: {}", e.getMessage());
- } finally {
- pageContext.forwardToLoginPage();
- }
-
- }
-
- @Override
- public void clearState() {
- try {
-
- final HttpSession httpSession = RWT
- .getUISession()
- .getHttpSession();
-
- log.debug("Clear session PageState: {}", httpSession.getId());
- httpSession.removeAttribute(ATTR_PAGE_STATE);
-
- } catch (final Exception e) {
- log.error("Failed to clear current PageState: ", e);
- }
- }
-
- private static final class ListenerComparator implements Comparator>, Serializable {
-
- private static final long serialVersionUID = 2571739214439340404L;
-
- @Override
- public int compare(final PageEventListener> o1, final PageEventListener> o2) {
- final int x = o1.priority();
- final int y = o2.priority();
- return (x < y) ? -1 : ((x == y) ? 0 : 1);
- }
- }
-
-}
+/*
+ * 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.page.impl;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.rap.rwt.RWT;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
+import ch.ethz.seb.sebserver.gbl.model.Activatable;
+import ch.ethz.seb.sebserver.gbl.model.Entity;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+import ch.ethz.seb.sebserver.gbl.model.Page;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.gui.form.FormBuilder;
+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.i18n.PolyglotPageService;
+import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
+import ch.ethz.seb.sebserver.gui.service.page.MultiPageMessageException;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageStateDefinition.Type;
+import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
+import ch.ethz.seb.sebserver.gui.service.page.event.ActionPublishEvent;
+import ch.ethz.seb.sebserver.gui.service.page.event.PageEvent;
+import ch.ethz.seb.sebserver.gui.service.page.event.PageEventListener;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
+import ch.ethz.seb.sebserver.gui.table.TableBuilder;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+
+@Lazy
+@Service
+@GuiProfile
+public class PageServiceImpl implements PageService {
+
+ private static final LocTextKey CONFIRM_DEACTIVATION_NO_DEP_KEY =
+ new LocTextKey("sebserver.dialog.confirm.deactivation.noDependencies");
+
+ private static final String CONFIRM_DEACTIVATION_KEY = "sebserver.dialog.confirm.deactivation";
+
+ private static final Logger log = LoggerFactory.getLogger(PageServiceImpl.class);
+
+ private static final LocTextKey MSG_GO_AWAY_FROM_EDIT =
+ new LocTextKey("sebserver.overall.action.goAwayFromEditPageConfirm");
+
+ private static final String ATTR_PAGE_STATE = "PAGE_STATE";
+ private static final ListenerComparator LIST_COMPARATOR = new ListenerComparator();
+
+ private final JSONMapper jsonMapper;
+ private final WidgetFactory widgetFactory;
+ private final PolyglotPageService polyglotPageService;
+ private final ResourceService resourceService;
+ private final CurrentUser currentUser;
+
+ public PageServiceImpl(
+ final JSONMapper jsonMapper,
+ final WidgetFactory widgetFactory,
+ final PolyglotPageService polyglotPageService,
+ final ResourceService resourceService,
+ final CurrentUser currentUser) {
+
+ this.jsonMapper = jsonMapper;
+ this.widgetFactory = widgetFactory;
+ this.polyglotPageService = polyglotPageService;
+ this.resourceService = resourceService;
+ this.currentUser = currentUser;
+ }
+
+ @Override
+ public WidgetFactory getWidgetFactory() {
+ return this.widgetFactory;
+ }
+
+ @Override
+ public PolyglotPageService getPolyglotPageService() {
+ return this.polyglotPageService;
+ }
+
+ @Override
+ public AuthorizationContextHolder getAuthorizationContextHolder() {
+ return this.currentUser.getAuthorizationContextHolder();
+ }
+
+ @Override
+ public I18nSupport getI18nSupport() {
+ return this.widgetFactory.getI18nSupport();
+ }
+
+ @Override
+ public ResourceService getResourceService() {
+ return this.resourceService;
+ }
+
+ @Override
+ public JSONMapper getJSONMapper() {
+ return this.jsonMapper;
+ }
+
+ @Override
+ public RestService getRestService() {
+ if (this.resourceService == null) {
+ return null;
+ }
+
+ return this.resourceService.getRestService();
+ }
+
+ @Override
+ public CurrentUser getCurrentUser() {
+ return this.currentUser;
+ }
+
+ @Override
+ public PageState getCurrentState() {
+ try {
+
+ final HttpSession httpSession = RWT
+ .getUISession()
+ .getHttpSession();
+
+ return (PageState) httpSession.getAttribute(ATTR_PAGE_STATE);
+
+ } catch (final Exception e) {
+ log.error("Failed to get current PageState: ", e);
+ return null;
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void firePageEvent(final T event, final PageContext pageContext) {
+ final Class extends PageEvent> typeClass = event.getClass();
+ final List> listeners = new ArrayList<>();
+ ComposerService.traversePageTree(
+ pageContext.getRoot(),
+ c -> {
+ final PageEventListener> listener =
+ (PageEventListener>) c.getData(PageEventListener.LISTENER_ATTRIBUTE_KEY);
+ return listener != null && listener.match(typeClass);
+ },
+ c -> listeners.add(((PageEventListener) c.getData(PageEventListener.LISTENER_ATTRIBUTE_KEY))));
+
+ if (listeners.isEmpty()) {
+ return;
+ }
+
+ listeners.stream()
+ .sorted(LIST_COMPARATOR)
+ .forEach(listener -> {
+ try {
+ listener.notify(event);
+ } catch (final Exception e) {
+ log.error("Unexpected error while notify PageEventListener: ", e);
+ }
+ });
+ }
+
+ @Override
+ public void executePageAction(final PageAction pageAction, final Consumer> callback) {
+ final PageState currentState = getCurrentState();
+ if (!pageAction.ignoreMoveAwayFromEdit && currentState != null && currentState.type() == Type.FORM_EDIT) {
+ pageAction.pageContext().applyConfirmDialog(
+ MSG_GO_AWAY_FROM_EDIT,
+ confirm -> {
+ if (confirm) {
+ exec(pageAction, callback);
+ } else {
+ callback.accept(Result.ofRuntimeError("Confirm denied"));
+ }
+ });
+ } else {
+ exec(pageAction, callback);
+ }
+ }
+
+ @Override
+ public Supplier confirmDeactivation(final Set extends T> entities) {
+ final RestService restService = this.resourceService.getRestService();
+ return () -> {
+ if (entities == null || entities.isEmpty()) {
+ return null;
+ }
+
+ try {
+ final int dependencies = (int) entities.stream()
+ .flatMap(entity -> {
+ final RestCall>.RestCallBuilder builder =
+ restService.>getBuilder(
+ entity.entityType(),
+ CallType.GET_DEPENDENCIES);
+
+ return builder
+ .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(entity.getModelId()))
+ .withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.DEACTIVATE.name())
+ .call()
+ .getOrThrow().stream();
+ }).count();
+ if (dependencies > 0) {
+ return new LocTextKey(CONFIRM_DEACTIVATION_KEY, String.valueOf(dependencies));
+ } else {
+ return CONFIRM_DEACTIVATION_NO_DEP_KEY;
+ }
+
+ } catch (final Exception e) {
+ log.warn("Failed to get dependencies. Error: {}", e.getMessage());
+ return new LocTextKey(CONFIRM_DEACTIVATION_KEY, "");
+ }
+ };
+ }
+
+ @Override
+ public Function activationToggleActionFunction(
+ final EntityTable table,
+ final LocTextKey noSelectionText,
+ Function testBeforeActivation) {
+
+ return action -> {
+ final Set selectedROWData = table.getSelectedROWData();
+ if (selectedROWData == null || selectedROWData.isEmpty()) {
+ throw new PageMessageException(noSelectionText);
+ }
+
+ final RestService restService = this.resourceService.getRestService();
+ final EntityType entityType = table.getEntityType();
+
+ final Collection errors = new ArrayList<>();
+ for (final T entity : selectedROWData) {
+
+ if (!entity.isActive()) {
+ RestCall.RestCallBuilder restCallBuilder = restService.getBuilder(
+ entityType,
+ CallType.ACTIVATION_ACTIVATE)
+ .withURIVariable(API.PARAM_MODEL_ID, entity.getModelId());
+ if (testBeforeActivation != null) {
+ try {
+ action.withEntityKey(entity.getEntityKey());
+ testBeforeActivation.apply(action);
+ restCallBuilder
+ .call()
+ .onError(errors::add);
+ } catch (Exception e) {
+ errors.add(e);
+ }
+ } else {
+ restCallBuilder
+ .call()
+ .onError(errors::add);
+ }
+ } else {
+ restService.getBuilder(entityType, CallType.ACTIVATION_DEACTIVATE)
+ .withURIVariable(API.PARAM_MODEL_ID, entity.getModelId())
+ .call()
+ .onError(errors::add);
+ }
+ }
+
+ if (!errors.isEmpty()) {
+ final String entityTypeName = this.resourceService.getEntityTypeName(entityType);
+ throw new MultiPageMessageException(
+ new LocTextKey(PageContext.GENERIC_ACTIVATE_ERROR_TEXT_KEY, entityTypeName),
+ errors);
+ }
+
+ return action;
+ };
+ }
+
+ private void exec(final PageAction pageAction, final Consumer> callback) {
+ pageAction.applyAction(result -> {
+ if (!result.hasError()) {
+
+ final PageAction action = result.get();
+ if (pageAction.fireActionEvent) {
+ firePageEvent(new ActionEvent(action), action.pageContext());
+ }
+
+ try {
+ final HttpSession httpSession = RWT
+ .getUISession()
+ .getHttpSession();
+
+ if (action != null &&
+ action.fireActionEvent &&
+ action.definition != null &&
+ action.definition.targetState != null) {
+
+ final PageState pageState = new PageState(action.definition.targetState, action);
+ if (log.isDebugEnabled()) {
+ log.debug("Set session PageState: {} : {}", pageState, httpSession.getId());
+ }
+ httpSession.setAttribute(ATTR_PAGE_STATE, pageState);
+ }
+ } catch (final Exception e) {
+ log.error("Failed to set current PageState: ", e);
+ }
+ }
+ callback.accept(result);
+ });
+ }
+
+ @Override
+ public void publishAction(final PageAction pageAction, final boolean active) {
+ this.firePageEvent(new ActionPublishEvent(pageAction, active), pageAction.pageContext());
+ }
+
+ @Override
+ public FormBuilder formBuilder(final PageContext pageContext, final int rows) {
+ return new FormBuilder(this, pageContext, rows);
+ }
+
+ @Override
+ public TableBuilder entityTableBuilder(
+ final String name,
+ final RestCall> apiCall) {
+
+ return new TableBuilder<>(name, this, apiCall);
+ }
+
+ @Override
+ public void logout(final PageContext pageContext) {
+ this.clearState();
+
+ try {
+ final boolean logoutSuccessful = this.currentUser.logout();
+
+ if (!logoutSuccessful) {
+ log.warn("Failed to logout. See logfiles for more information");
+ }
+
+ } catch (final Exception e) {
+ log.info("Cleanup logout failed: {}", e.getMessage());
+ } finally {
+ pageContext.forwardToLoginPage();
+ }
+
+ }
+
+ @Override
+ public void clearState() {
+ try {
+
+ final HttpSession httpSession = RWT
+ .getUISession()
+ .getHttpSession();
+
+ log.debug("Clear session PageState: {}", httpSession.getId());
+ httpSession.removeAttribute(ATTR_PAGE_STATE);
+
+ } catch (final Exception e) {
+ log.error("Failed to clear current PageState: ", e);
+ }
+ }
+
+ private static final class ListenerComparator implements Comparator>, Serializable {
+
+ private static final long serialVersionUID = 2571739214439340404L;
+
+ @Override
+ public int compare(final PageEventListener> o1, final PageEventListener> o2) {
+ return Integer.compare(o1.priority(), o2.priority());
+ }
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java
index 71568ca8..7e6403a6 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java
@@ -1,195 +1,191 @@
-/*
- * 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.session;
-
-import java.util.Collection;
-import java.util.EnumMap;
-
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.widgets.Display;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.model.Domain;
-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.exam.Indicator.IndicatorType;
-import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
-import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
-import ch.ethz.seb.sebserver.gui.form.Form;
-import ch.ethz.seb.sebserver.gui.form.FormBuilder;
-import ch.ethz.seb.sebserver.gui.form.FormHandle;
-import ch.ethz.seb.sebserver.gui.service.ResourceService;
-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.PageService;
-import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
-import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
-
-public class ClientConnectionDetails {
-
- private static final Logger log = LoggerFactory.getLogger(ClientConnectionDetails.class);
-
- private final static LocTextKey EXAM_NAME_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.connection.list.column.examname");
- private final static LocTextKey CONNECTION_ID_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.connection.list.column.id");
- private final static LocTextKey CONNECTION_ADDRESS_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.connection.list.column.address");
- private final static LocTextKey CONNECTION_STATUS_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.connection.list.column.status");
-
- private static final int NUMBER_OF_NONE_INDICATOR_ROWS = 3;
-
- private final PageService pageService;
- private final ResourceService resourceService;
- private final Exam exam;
- private final EnumMap indicatorMapping;
- private final RestCall.RestCallBuilder restCallBuilder;
- private final FormHandle> formhandle;
- private final ColorData colorData;
-
- private ClientConnectionData connectionData = null;
- private boolean statusChanged = true;
-
- public ClientConnectionDetails(
- final PageService pageService,
- final PageContext pageContext,
- final Exam exam,
- final RestCall.RestCallBuilder restCallBuilder,
- final Collection indicators) {
-
- final Display display = pageContext.getRoot().getDisplay();
-
- this.pageService = pageService;
- this.resourceService = pageService.getResourceService();
- this.exam = exam;
- this.restCallBuilder = restCallBuilder;
- this.colorData = new ColorData(display);
- this.indicatorMapping = IndicatorData.createFormIndicators(
- indicators,
- display,
- this.colorData,
- NUMBER_OF_NONE_INDICATOR_ROWS);
-
- final FormBuilder formBuilder = this.pageService.formBuilder(pageContext)
- .readonly(true)
- .addField(FormBuilder.text(
- QuizData.QUIZ_ATTR_NAME,
- EXAM_NAME_TEXT_KEY,
- this.exam.getName()))
- .addField(FormBuilder.text(
- Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
- CONNECTION_ID_TEXT_KEY,
- Constants.EMPTY_NOTE))
- .addField(FormBuilder.text(
- Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
- CONNECTION_ADDRESS_TEXT_KEY,
- Constants.EMPTY_NOTE))
- .withDefaultSpanInput(3)
- .addField(FormBuilder.text(
- Domain.CLIENT_CONNECTION.ATTR_STATUS,
- CONNECTION_STATUS_TEXT_KEY,
- Constants.EMPTY_NOTE)
- .asColorbox())
- .addEmptyCell();
-
- this.indicatorMapping
- .values()
- .stream()
- .forEach(indData -> {
- formBuilder.addField(FormBuilder.text(
- indData.indicator.name,
- new LocTextKey(indData.indicator.name),
- Constants.EMPTY_NOTE)
- .asColorbox()
- .withDefaultLabel(indData.indicator.name))
- .addEmptyCell();
- });
-
- this.formhandle = formBuilder.build();
- }
-
- public void updateData(final ServerPushContext context) {
- final ClientConnectionData connectionData = this.restCallBuilder
- .call()
- .get(error -> {
- log.error("Unexpected error while trying to get current client connection data: ", error);
- return null;
- });
-
- if (this.connectionData != null && connectionData != null) {
- this.statusChanged =
- this.connectionData.clientConnection.status != connectionData.clientConnection.status ||
- this.connectionData.missingPing != connectionData.missingPing;
- }
- this.connectionData = connectionData;
- }
-
- public void updateGUI(final ServerPushContext context) {
- if (this.connectionData == null) {
- return;
- }
-
- final Form form = this.formhandle.getForm();
- form.setFieldValue(
- Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
- this.connectionData.clientConnection.userSessionId);
-
- form.setFieldValue(
- Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
- this.connectionData.clientConnection.clientAddress);
-
- if (this.statusChanged) {
- // update status
- form.setFieldValue(
- Domain.CLIENT_CONNECTION.ATTR_STATUS,
- this.resourceService.localizedClientConnectionStatusName(this.connectionData));
- final Color statusColor = this.colorData.getStatusColor(this.connectionData);
- final Color statusTextColor = this.colorData.getStatusTextColor(statusColor);
- form.setFieldColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusColor);
- form.setFieldTextColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusTextColor);
- }
-
- // update indicators
- this.connectionData.getIndicatorValues()
- .stream()
- .forEach(indValue -> {
- final IndicatorData indData = this.indicatorMapping.get(indValue.getType());
- final double value = indValue.getValue();
- final String displayValue = IndicatorValue.getDisplayValue(indValue);
-
- if (!this.connectionData.clientConnection.status.establishedStatus) {
-
- form.setFieldValue(
- indData.indicator.name,
- (indData.indicator.type.showOnlyInActiveState)
- ? Constants.EMPTY_NOTE
- : displayValue);
- form.setFieldColor(indData.indicator.name, indData.defaultColor);
- form.setFieldTextColor(indData.indicator.name, indData.defaultTextColor);
- } else {
- form.setFieldValue(indData.indicator.name, displayValue);
- final int weight = IndicatorData.getWeight(indData, value);
- if (weight >= 0 && weight < indData.thresholdColor.length) {
- final ThresholdColor thresholdColor = indData.thresholdColor[weight];
- form.setFieldColor(indData.indicator.name, thresholdColor.color);
- form.setFieldTextColor(indData.indicator.name, thresholdColor.textColor);
- } else {
- form.setFieldColor(indData.indicator.name, indData.defaultColor);
- form.setFieldTextColor(indData.indicator.name, indData.defaultTextColor);
- }
- }
- });
- }
-
-}
+/*
+ * 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.session;
+
+import java.util.Collection;
+import java.util.EnumMap;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.widgets.Display;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+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.exam.Indicator.IndicatorType;
+import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
+import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
+import ch.ethz.seb.sebserver.gui.form.Form;
+import ch.ethz.seb.sebserver.gui.form.FormBuilder;
+import ch.ethz.seb.sebserver.gui.form.FormHandle;
+import ch.ethz.seb.sebserver.gui.service.ResourceService;
+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.PageService;
+import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
+
+public class ClientConnectionDetails {
+
+ private static final Logger log = LoggerFactory.getLogger(ClientConnectionDetails.class);
+
+ private final static LocTextKey EXAM_NAME_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.form.exam");
+ private final static LocTextKey CONNECTION_ID_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.form.id");
+ private final static LocTextKey CONNECTION_ADDRESS_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.form.address");
+ private final static LocTextKey CONNECTION_STATUS_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.form.status");
+
+ private static final int NUMBER_OF_NONE_INDICATOR_ROWS = 3;
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final Exam exam;
+ private final EnumMap indicatorMapping;
+ private final RestCall.RestCallBuilder restCallBuilder;
+ private final FormHandle> formhandle;
+ private final ColorData colorData;
+
+ private ClientConnectionData connectionData = null;
+ private boolean statusChanged = true;
+
+ public ClientConnectionDetails(
+ final PageService pageService,
+ final PageContext pageContext,
+ final Exam exam,
+ final RestCall.RestCallBuilder restCallBuilder,
+ final Collection indicators) {
+
+ final Display display = pageContext.getRoot().getDisplay();
+
+ this.pageService = pageService;
+ this.resourceService = pageService.getResourceService();
+ this.exam = exam;
+ this.restCallBuilder = restCallBuilder;
+ this.colorData = new ColorData(display);
+ this.indicatorMapping = IndicatorData.createFormIndicators(
+ indicators,
+ display,
+ this.colorData,
+ NUMBER_OF_NONE_INDICATOR_ROWS);
+
+ final FormBuilder formBuilder = this.pageService.formBuilder(pageContext)
+ .readonly(true)
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_NAME,
+ EXAM_NAME_TEXT_KEY,
+ this.exam.getName()))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
+ CONNECTION_ID_TEXT_KEY,
+ Constants.EMPTY_NOTE))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
+ CONNECTION_ADDRESS_TEXT_KEY,
+ Constants.EMPTY_NOTE))
+ .withDefaultSpanInput(3)
+ .addField(FormBuilder.text(
+ Domain.CLIENT_CONNECTION.ATTR_STATUS,
+ CONNECTION_STATUS_TEXT_KEY,
+ Constants.EMPTY_NOTE)
+ .asColorBox())
+ .addEmptyCell();
+
+ this.indicatorMapping
+ .values()
+ .forEach(indData -> formBuilder.addField(FormBuilder.text(
+ indData.indicator.name,
+ new LocTextKey(indData.indicator.name),
+ Constants.EMPTY_NOTE)
+ .asColorBox()
+ .withDefaultLabel(indData.indicator.name))
+ .addEmptyCell());
+
+ this.formhandle = formBuilder.build();
+ }
+
+ public void updateData() {
+ final ClientConnectionData connectionData = this.restCallBuilder
+ .call()
+ .get(error -> {
+ log.error("Unexpected error while trying to get current client connection data: ", error);
+ return null;
+ });
+
+ if (this.connectionData != null && connectionData != null) {
+ this.statusChanged =
+ this.connectionData.clientConnection.status != connectionData.clientConnection.status ||
+ this.connectionData.missingPing != connectionData.missingPing;
+ }
+ this.connectionData = connectionData;
+ }
+
+ public void updateGUI() {
+ if (this.connectionData == null) {
+ return;
+ }
+
+ final Form form = this.formhandle.getForm();
+ form.setFieldValue(
+ Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
+ this.connectionData.clientConnection.userSessionId);
+
+ form.setFieldValue(
+ Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
+ this.connectionData.clientConnection.clientAddress);
+
+ if (this.statusChanged) {
+ // update status
+ form.setFieldValue(
+ Domain.CLIENT_CONNECTION.ATTR_STATUS,
+ this.resourceService.localizedClientConnectionStatusName(this.connectionData));
+ final Color statusColor = this.colorData.getStatusColor(this.connectionData);
+ final Color statusTextColor = this.colorData.getStatusTextColor(statusColor);
+ form.setFieldColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusColor);
+ form.setFieldTextColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusTextColor);
+ }
+
+ // update indicators
+ this.connectionData.getIndicatorValues()
+ .forEach(indValue -> {
+ final IndicatorData indData = this.indicatorMapping.get(indValue.getType());
+ final double value = indValue.getValue();
+ final String displayValue = IndicatorValue.getDisplayValue(indValue);
+
+ if (!this.connectionData.clientConnection.status.establishedStatus) {
+
+ form.setFieldValue(
+ indData.indicator.name,
+ (indData.indicator.type.showOnlyInActiveState)
+ ? Constants.EMPTY_NOTE
+ : displayValue);
+ form.setFieldColor(indData.indicator.name, indData.defaultColor);
+ form.setFieldTextColor(indData.indicator.name, indData.defaultTextColor);
+ } else {
+ form.setFieldValue(indData.indicator.name, displayValue);
+ final int weight = IndicatorData.getWeight(indData, value);
+ if (weight >= 0 && weight < indData.thresholdColor.length) {
+ final ThresholdColor thresholdColor = indData.thresholdColor[weight];
+ form.setFieldColor(indData.indicator.name, thresholdColor.color);
+ form.setFieldTextColor(indData.indicator.name, thresholdColor.textColor);
+ } else {
+ form.setFieldColor(indData.indicator.name, indData.defaultColor);
+ form.setFieldTextColor(indData.indicator.name, indData.defaultTextColor);
+ }
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java
index 5f264aae..5b6076b6 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java
@@ -1,616 +1,644 @@
-/*
- * 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.session;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-import org.apache.commons.lang3.StringUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.TableItem;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.api.API;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-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.exam.Indicator.IndicatorType;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
-import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
-import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
-import ch.ethz.seb.sebserver.gbl.util.Tuple;
-import ch.ethz.seb.sebserver.gui.service.ResourceService;
-import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
-import ch.ethz.seb.sebserver.gui.service.page.PageService;
-import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
-import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
-import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
-
-public final class ClientConnectionTable {
-
- private static final Logger log = LoggerFactory.getLogger(ClientConnectionTable.class);
-
- private static final int BOTTOM_PADDING = 20;
- private static final int NUMBER_OF_NONE_INDICATOR_COLUMNS = 3;
- private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER_ATTRIBUTE";
-
- private static final String INDICATOR_NAME_TEXT_KEY_PREFIX =
- "sebserver.monitoring.connection.list.column.indicator.";
- private final static LocTextKey CONNECTION_ID_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.connection.list.column.id");
- private final static LocTextKey CONNECTION_ADDRESS_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.connection.list.column.address");
- private final static LocTextKey CONNECTION_STATUS_TEXT_KEY =
- new LocTextKey("sebserver.monitoring.connection.list.column.status");
-
- private final WidgetFactory widgetFactory;
- private final ResourceService resourceService;
- private final Exam exam;
- private final RestCall>.RestCallBuilder restCallBuilder;
- private final EnumMap indicatorMapping;
- private final Table table;
- private final ColorData colorData;
- private final EnumSet statusFilter;
- private String statusFilterParam = "";
- private boolean statusFilterChanged = false;
-
- private int tableWidth;
- private boolean needsSort = false;
- private LinkedHashMap tableMapping;
- private final Set toDelete = new HashSet<>();
- private final MultiValueMap sessionIds;
-
- private final Color darkFontColor;
- private final Color lightFontColor;
-
- public ClientConnectionTable(
- final PageService pageService,
- final Composite tableRoot,
- final Exam exam,
- final Collection indicators,
- final RestCall>.RestCallBuilder restCallBuilder) {
-
- this.widgetFactory = pageService.getWidgetFactory();
- this.resourceService = pageService.getResourceService();
- this.exam = exam;
- this.restCallBuilder = restCallBuilder;
-
- final Display display = tableRoot.getDisplay();
- this.colorData = new ColorData(display);
-
- this.darkFontColor = new Color(display, Constants.BLACK_RGB);
- this.lightFontColor = new Color(display, Constants.WHITE_RGB);
-
- this.indicatorMapping = IndicatorData.createFormIndicators(
- indicators,
- display,
- this.colorData,
- NUMBER_OF_NONE_INDICATOR_COLUMNS);
-
- this.statusFilter = EnumSet.noneOf(ConnectionStatus.class);
- loadStatusFilter();
-
- this.table = this.widgetFactory.tableLocalized(tableRoot, SWT.MULTI | SWT.V_SCROLL);
- final GridLayout gridLayout = new GridLayout(3 + indicators.size(), true);
- gridLayout.horizontalSpacing = 100;
- gridLayout.marginWidth = 100;
- gridLayout.marginRight = 100;
- this.table.setLayout(gridLayout);
- final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
- this.table.setLayoutData(gridData);
- this.table.setHeaderVisible(true);
- this.table.setLinesVisible(true);
-
- this.widgetFactory.tableColumnLocalized(
- this.table,
- CONNECTION_ID_TEXT_KEY);
- this.widgetFactory.tableColumnLocalized(
- this.table,
- CONNECTION_ADDRESS_TEXT_KEY);
- this.widgetFactory.tableColumnLocalized(
- this.table,
- CONNECTION_STATUS_TEXT_KEY);
- for (final Indicator indDef : indicators) {
- final TableColumn tc = new TableColumn(this.table, SWT.NONE);
- final String indicatorName = this.widgetFactory.getI18nSupport().getText(
- INDICATOR_NAME_TEXT_KEY_PREFIX + indDef.name,
- indDef.name);
- tc.setText(indicatorName);
- }
-
- this.tableMapping = new LinkedHashMap<>();
- this.sessionIds = new LinkedMultiValueMap<>();
- this.table.layout();
- }
-
- public WidgetFactory getWidgetFactory() {
- return this.widgetFactory;
- }
-
- public boolean isEmpty() {
- return this.tableMapping.isEmpty();
- }
-
- public Exam getExam() {
- return this.exam;
- }
-
- public boolean isStatusHidden(final ConnectionStatus status) {
- return this.statusFilter.contains(status);
- }
-
- public void hideStatus(final ConnectionStatus status) {
- this.statusFilter.add(status);
- saveStatusFilter();
- }
-
- public void showStatus(final ConnectionStatus status) {
- this.statusFilter.remove(status);
- saveStatusFilter();
- }
-
- public void withDefaultAction(final PageAction pageAction, final PageService pageService) {
- this.table.addListener(SWT.MouseDoubleClick, event -> {
- final Tuple selection = getSingleSelection();
- if (selection == null) {
- return;
- }
-
- final PageAction copyOfPageAction = PageAction.copyOf(pageAction);
- copyOfPageAction.withEntityKey(new EntityKey(
- selection._1,
- EntityType.CLIENT_CONNECTION));
- copyOfPageAction.withAttribute(
- Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
- selection._2);
- pageService.executePageAction(copyOfPageAction);
- });
-
- }
-
- public Set getConnectionTokens(
- final Predicate filter,
- final boolean selected) {
-
- if (selected) {
- final int[] selectionIndices = this.table.getSelectionIndices();
- if (selectionIndices == null || selectionIndices.length < 1) {
- return Collections.emptySet();
- }
-
- final Set result = new HashSet<>();
- for (int i = 0; i < selectionIndices.length; i++) {
- final UpdatableTableItem updatableTableItem =
- new ArrayList<>(this.tableMapping.values())
- .get(selectionIndices[i]);
- if (filter.test(updatableTableItem.connectionData.clientConnection)) {
- result.add(updatableTableItem.connectionData.clientConnection.connectionToken);
- }
- }
- return result;
- } else {
- return this.tableMapping
- .values()
- .stream()
- .map(item -> item.connectionData.clientConnection)
- .filter(filter)
- .map(ClientConnection::getConnectionToken)
- .collect(Collectors.toSet());
- }
- }
-
- public void removeSelection() {
- if (this.table != null) {
- this.table.deselectAll();
- }
- }
-
- public Set getSelection() {
- final int[] selectionIndices = this.table.getSelectionIndices();
- if (selectionIndices == null || selectionIndices.length < 1) {
- return Collections.emptySet();
- }
-
- final Set result = new HashSet<>();
- for (int i = 0; i < selectionIndices.length; i++) {
- final UpdatableTableItem updatableTableItem =
- new ArrayList<>(this.tableMapping.values()).get(selectionIndices[0]);
- result.add(new EntityKey(updatableTableItem.connectionId, EntityType.CLIENT_CONNECTION));
- }
- return result;
- }
-
- public Tuple getSingleSelection() {
- final int[] selectionIndices = this.table.getSelectionIndices();
- if (selectionIndices == null || selectionIndices.length < 1) {
- return null;
- }
-
- final UpdatableTableItem updatableTableItem =
- new ArrayList<>(this.tableMapping.values()).get(selectionIndices[0]);
- return new Tuple<>(
- (updatableTableItem.connectionId != null)
- ? String.valueOf(updatableTableItem.connectionId)
- : null,
- updatableTableItem.connectionData.clientConnection.connectionToken);
- }
-
- public void updateValues() {
- if (this.statusFilterChanged) {
- this.toDelete.clear();
- this.toDelete.addAll(this.tableMapping.keySet());
- }
- this.restCallBuilder
- .withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam)
- .call()
- .get(error -> {
- log.error("Error poll connection data: ", error);
- return Collections.emptyList();
- })
- .stream()
- .forEach(data -> {
- final UpdatableTableItem tableItem = this.tableMapping.computeIfAbsent(
- data.getConnectionId(),
- connectionId -> new UpdatableTableItem(connectionId));
- tableItem.push(data);
- if (this.statusFilterChanged) {
- this.toDelete.remove(data.getConnectionId());
- }
- });
-
- if (this.statusFilterChanged && !this.toDelete.isEmpty()) {
- this.toDelete.stream().forEach(id -> {
- final UpdatableTableItem item = this.tableMapping.remove(id);
- final List list = this.sessionIds.get(item.connectionData.clientConnection.userSessionId);
- if (list != null) {
- list.remove(id);
- }
- });
- this.statusFilterChanged = false;
- }
- }
-
- public void updateGUI() {
- fillTable();
- if (this.needsSort) {
- sortTable();
- }
-
- final TableItem[] items = this.table.getItems();
- final Iterator> iterator = this.tableMapping.entrySet().iterator();
- for (int i = 0; i < items.length; i++) {
- final UpdatableTableItem uti = iterator.next().getValue();
- uti.update(items[i], this.needsSort);
- }
-
- this.needsSort = false;
- adaptTableWidth();
- this.table.layout(true, true);
- }
-
- private void adaptTableWidth() {
- final Rectangle area = this.table.getParent().getClientArea();
- if (this.tableWidth != area.width) {
- final int columnWidth = area.width / this.table.getColumnCount() - 5;
- for (final TableColumn column : this.table.getColumns()) {
- column.setWidth(columnWidth);
- }
- this.tableWidth = area.width;
- }
-
- // update table height
- final GridData gridData = (GridData) this.table.getLayoutData();
- gridData.heightHint = area.height - BOTTOM_PADDING;
- }
-
- private void fillTable() {
- if (this.tableMapping.size() > this.table.getItemCount()) {
- while (this.tableMapping.size() > this.table.getItemCount()) {
- new TableItem(this.table, SWT.NONE);
- }
- } else if (this.tableMapping.size() < this.table.getItemCount()) {
- while (this.tableMapping.size() < this.table.getItemCount()) {
- this.table.getItem(0).dispose();
- }
- }
- }
-
- private void sortTable() {
- this.tableMapping = this.tableMapping.entrySet()
- .stream()
- .sorted((e1, e2) -> e1.getValue().compareTo(e2.getValue()))
- .collect(Collectors.toMap(
- Entry::getKey,
- Entry::getValue,
- (e1, e2) -> e1, LinkedHashMap::new));
- }
-
- private void saveStatusFilter() {
- try {
- this.resourceService
- .getCurrentUser()
- .putAttribute(
- USER_SESSION_STATUS_FILTER_ATTRIBUTE,
- StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR));
- } catch (final Exception e) {
- log.warn("Failed to save status filter to user session");
- } finally {
- this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR);
- this.statusFilterChanged = true;
- }
- }
-
- private void loadStatusFilter() {
- try {
- final String attribute = this.resourceService
- .getCurrentUser()
- .getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE);
- if (attribute != null) {
- this.statusFilter.clear();
- Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR))
- .forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name)));
-
- } else {
- this.statusFilter.clear();
- this.statusFilter.add(ConnectionStatus.DISABLED);
- }
- } catch (final Exception e) {
- log.warn("Failed to load status filter to user session");
- this.statusFilter.clear();
- this.statusFilter.add(ConnectionStatus.DISABLED);
- } finally {
- this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR);
- this.statusFilterChanged = true;
- }
- }
-
- private final class UpdatableTableItem implements Comparable {
-
- final Long connectionId;
- private boolean changed = false;
- private ClientConnectionData connectionData;
- private int thresholdsWeight;
- private int[] indicatorWeights = null;
- private boolean duplicateChecked = false;
-
- UpdatableTableItem(final Long connectionId) {
- this.connectionId = connectionId;
- }
-
- void update(final TableItem tableItem, final boolean force) {
- if (force || this.changed) {
- update(tableItem);
- }
- this.changed = false;
- }
-
- void update(final TableItem tableItem) {
- if (ClientConnectionTable.this.statusFilter.contains(this.connectionData.clientConnection.status)) {
- tableItem.dispose();
- return;
- }
- updateData(tableItem);
- if (this.connectionData != null) {
- updateConnectionStatusColor(tableItem);
- updateIndicatorValues(tableItem);
- updateDuplicateColor(tableItem);
-
- }
- }
-
- void updateData(final TableItem tableItem) {
- tableItem.setText(0, getConnectionIdentifer());
- tableItem.setText(1, getConnectionAddress());
- tableItem.setText(2, getStatusName());
- }
-
- void updateConnectionStatusColor(final TableItem tableItem) {
- final Color statusColor = ClientConnectionTable.this.colorData.getStatusColor(this.connectionData);
- final Color statusTextColor = ClientConnectionTable.this.colorData.getStatusTextColor(statusColor);
- tableItem.setBackground(2, statusColor);
- tableItem.setForeground(2, statusTextColor);
- }
-
- void updateDuplicateColor(final TableItem tableItem) {
- if (!this.duplicateChecked) {
- return;
- }
-
- if (this.connectionData != null
- && StringUtils.isNotBlank(this.connectionData.clientConnection.userSessionId)) {
- final List list =
- ClientConnectionTable.this.sessionIds.get(this.connectionData.clientConnection.userSessionId);
- if (list != null && list.size() > 1) {
- tableItem.setBackground(0, ClientConnectionTable.this.colorData.color3);
- tableItem.setForeground(0, ClientConnectionTable.this.lightFontColor);
- } else {
- tableItem.setBackground(0, null);
- tableItem.setForeground(0, ClientConnectionTable.this.darkFontColor);
- }
- } else {
- tableItem.setBackground(0, null);
- tableItem.setForeground(0, ClientConnectionTable.this.darkFontColor);
- }
- }
-
- void updateIndicatorValues(final TableItem tableItem) {
- if (this.connectionData == null || this.indicatorWeights == null) {
- return;
- }
-
- for (int i = 0; i < this.connectionData.indicatorValues.size(); i++) {
- final IndicatorValue indicatorValue = this.connectionData.indicatorValues.get(i);
- final IndicatorData indicatorData =
- ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType());
-
- if (!this.connectionData.clientConnection.status.establishedStatus) {
- final String value = (indicatorData.indicator.type.showOnlyInActiveState)
- ? Constants.EMPTY_NOTE
- : IndicatorValue.getDisplayValue(indicatorValue);
- tableItem.setText(indicatorData.tableIndex, value);
- tableItem.setBackground(indicatorData.tableIndex, indicatorData.defaultColor);
- tableItem.setForeground(indicatorData.tableIndex, indicatorData.defaultTextColor);
- } else {
- tableItem.setText(indicatorData.tableIndex, IndicatorValue.getDisplayValue(indicatorValue));
- final int weight = this.indicatorWeights[indicatorData.index];
- if (weight >= 0 && weight < indicatorData.thresholdColor.length) {
- final ThresholdColor thresholdColor = indicatorData.thresholdColor[weight];
- tableItem.setBackground(indicatorData.tableIndex, thresholdColor.color);
- tableItem.setForeground(indicatorData.tableIndex, thresholdColor.textColor);
- } else {
- tableItem.setBackground(indicatorData.tableIndex, indicatorData.defaultColor);
- tableItem.setForeground(indicatorData.tableIndex, indicatorData.defaultTextColor);
- }
- }
- }
- }
-
- @Override
- public int compareTo(final UpdatableTableItem other) {
- return Comparator.comparingInt(UpdatableTableItem::statusWeight)
- .thenComparingInt(UpdatableTableItem::thresholdsWeight)
- .thenComparing(UpdatableTableItem::getConnectionIdentifer)
- .compare(this, other);
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + getOuterType().hashCode();
- result = prime * result + ((this.connectionId == null) ? 0 : this.connectionId.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- final UpdatableTableItem other = (UpdatableTableItem) obj;
- if (!getOuterType().equals(other.getOuterType()))
- return false;
- return compareTo(other) == 0;
- }
-
- int statusWeight() {
- return ClientConnectionTable.this.colorData.statusWeight(this.connectionData);
- }
-
- int thresholdsWeight() {
- return -this.thresholdsWeight;
- }
-
- String getStatusName() {
- return ClientConnectionTable.this.resourceService
- .localizedClientConnectionStatusName(this.connectionData);
- }
-
- String getConnectionAddress() {
- if (this.connectionData != null && this.connectionData.clientConnection.clientAddress != null) {
- return this.connectionData.clientConnection.clientAddress;
- }
- return Constants.EMPTY_NOTE;
- }
-
- String getConnectionIdentifer() {
- if (this.connectionData != null && this.connectionData.clientConnection.userSessionId != null) {
- return this.connectionData.clientConnection.userSessionId;
- }
-
- return "--";
- }
-
- void push(final ClientConnectionData connectionData) {
- this.changed = this.connectionData == null ||
- !this.connectionData.dataEquals(connectionData);
-
- final boolean statusChanged = this.connectionData == null ||
- this.connectionData.clientConnection.status != connectionData.clientConnection.status;
-
- if (statusChanged) {
- ClientConnectionTable.this.needsSort = true;
- }
-
- if (this.indicatorWeights == null) {
- this.indicatorWeights = new int[ClientConnectionTable.this.indicatorMapping.size()];
- }
-
- for (int i = 0; i < connectionData.indicatorValues.size(); i++) {
- final IndicatorValue indicatorValue = connectionData.indicatorValues.get(i);
- final IndicatorData indicatorData =
- ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType());
-
- if (indicatorData == null) {
- log.error("No IndicatorData of type: {} found", indicatorValue.getType());
- } else {
-
- final double value = indicatorValue.getValue();
- final int indicatorWeight = IndicatorData.getWeight(indicatorData, value);
- if (this.indicatorWeights[indicatorData.index] != indicatorWeight) {
- ClientConnectionTable.this.needsSort = true;
- this.thresholdsWeight -= this.indicatorWeights[indicatorData.index];
- this.indicatorWeights[indicatorData.index] = indicatorWeight;
- this.thresholdsWeight += this.indicatorWeights[indicatorData.index];
- }
- }
- }
-
- this.connectionData = connectionData;
-
- if (!this.duplicateChecked &&
- this.connectionData.clientConnection.status != ConnectionStatus.DISABLED &&
- StringUtils.isNotBlank(connectionData.clientConnection.userSessionId)) {
-
- ClientConnectionTable.this.sessionIds.add(
- connectionData.clientConnection.userSessionId,
- this.connectionId);
- this.duplicateChecked = true;
- }
- }
-
- private ClientConnectionTable getOuterType() {
- return ClientConnectionTable.this;
- }
-
- }
-
-}
+/*
+ * 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.session;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tomcat.util.bcel.Const;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+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.exam.Indicator.IndicatorType;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
+import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
+import ch.ethz.seb.sebserver.gbl.util.Tuple;
+import ch.ethz.seb.sebserver.gui.service.ResourceService;
+import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+
+public final class ClientConnectionTable {
+
+ private static final Logger log = LoggerFactory.getLogger(ClientConnectionTable.class);
+
+ private static final int BOTTOM_PADDING = 20;
+ private static final int NUMBER_OF_NONE_INDICATOR_COLUMNS = 3;
+ private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER_ATTRIBUTE";
+
+ private static final String INDICATOR_NAME_TEXT_KEY_PREFIX =
+ "sebserver.exam.indicator.type.description.";
+ private final static LocTextKey CONNECTION_ID_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.list.column.id");
+ private final static LocTextKey CONNECTION_ID_TOOLTIP_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.list.column.id" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
+ private final static LocTextKey CONNECTION_ADDRESS_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.list.column.address");
+ private final static LocTextKey CONNECTION_ADDRESS_TOOLTIP_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.list.column.address" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
+ private final static LocTextKey CONNECTION_STATUS_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.list.column.status");
+ private final static LocTextKey CONNECTION_STATUS_TOOLTIP_TEXT_KEY =
+ new LocTextKey("sebserver.monitoring.connection.list.column.status" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
+
+ private final WidgetFactory widgetFactory;
+ private final ResourceService resourceService;
+ private final Exam exam;
+ private final RestCall>.RestCallBuilder restCallBuilder;
+ private final EnumMap indicatorMapping;
+ private final Table table;
+ private final ColorData colorData;
+ private final EnumSet statusFilter;
+ private String statusFilterParam = "";
+ private boolean statusFilterChanged = false;
+ private Consumer> selectionListener;
+
+ private int tableWidth;
+ private boolean needsSort = false;
+ private LinkedHashMap tableMapping;
+ private final Set toDelete = new HashSet<>();
+ private final MultiValueMap sessionIds;
+
+ private final Color darkFontColor;
+ private final Color lightFontColor;
+
+ public ClientConnectionTable(
+ final PageService pageService,
+ final Composite tableRoot,
+ final Exam exam,
+ final Collection indicators,
+ final RestCall>.RestCallBuilder restCallBuilder) {
+
+ this.widgetFactory = pageService.getWidgetFactory();
+ this.resourceService = pageService.getResourceService();
+ this.exam = exam;
+ this.restCallBuilder = restCallBuilder;
+
+ final Display display = tableRoot.getDisplay();
+ this.colorData = new ColorData(display);
+
+ this.darkFontColor = new Color(display, Constants.BLACK_RGB);
+ this.lightFontColor = new Color(display, Constants.WHITE_RGB);
+
+ this.indicatorMapping = IndicatorData.createFormIndicators(
+ indicators,
+ display,
+ this.colorData,
+ NUMBER_OF_NONE_INDICATOR_COLUMNS);
+
+ this.statusFilter = EnumSet.noneOf(ConnectionStatus.class);
+ loadStatusFilter();
+
+ this.table = this.widgetFactory.tableLocalized(tableRoot, SWT.MULTI | SWT.V_SCROLL);
+ final GridLayout gridLayout = new GridLayout(3 + indicators.size(), true);
+ gridLayout.horizontalSpacing = 100;
+ gridLayout.marginWidth = 100;
+ gridLayout.marginRight = 100;
+ this.table.setLayout(gridLayout);
+ final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
+ this.table.setLayoutData(gridData);
+ this.table.setHeaderVisible(true);
+ this.table.setLinesVisible(true);
+
+ this.table.addListener(SWT.Selection, event -> this.notifySelectionChange());
+
+ this.widgetFactory.tableColumnLocalized(
+ this.table,
+ CONNECTION_ID_TEXT_KEY,
+ CONNECTION_ID_TOOLTIP_TEXT_KEY);
+ this.widgetFactory.tableColumnLocalized(
+ this.table,
+ CONNECTION_ADDRESS_TEXT_KEY,
+ CONNECTION_ADDRESS_TOOLTIP_TEXT_KEY);
+ this.widgetFactory.tableColumnLocalized(
+ this.table,
+ CONNECTION_STATUS_TEXT_KEY,
+ CONNECTION_STATUS_TOOLTIP_TEXT_KEY);
+ for (final Indicator indDef : indicators) {
+ TableColumn tableColumn = widgetFactory.tableColumnLocalized(
+ this.table,
+ new LocTextKey(INDICATOR_NAME_TEXT_KEY_PREFIX + indDef.name),
+ new LocTextKey(INDICATOR_NAME_TEXT_KEY_PREFIX + indDef.type.name)
+ );
+ tableColumn.setText(indDef.name);
+ }
+
+ this.tableMapping = new LinkedHashMap<>();
+ this.sessionIds = new LinkedMultiValueMap<>();
+ this.table.layout();
+ }
+
+ public WidgetFactory getWidgetFactory() {
+ return this.widgetFactory;
+ }
+
+ public boolean isEmpty() {
+ return this.tableMapping.isEmpty();
+ }
+
+ public Exam getExam() {
+ return this.exam;
+ }
+
+ public boolean isStatusHidden(final ConnectionStatus status) {
+ return this.statusFilter.contains(status);
+ }
+
+ public void hideStatus(final ConnectionStatus status) {
+ this.statusFilter.add(status);
+ saveStatusFilter();
+ }
+
+ public void showStatus(final ConnectionStatus status) {
+ this.statusFilter.remove(status);
+ saveStatusFilter();
+ }
+
+ public ClientConnectionTable withDefaultAction(final PageAction pageAction, final PageService pageService) {
+ this.table.addListener(SWT.MouseDoubleClick, event -> {
+ final Tuple selection = getSingleSelection();
+ if (selection == null) {
+ return;
+ }
+
+ final PageAction copyOfPageAction = PageAction.copyOf(pageAction);
+ copyOfPageAction.withEntityKey(new EntityKey(
+ selection._1,
+ EntityType.CLIENT_CONNECTION));
+ copyOfPageAction.withAttribute(
+ Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
+ selection._2);
+ pageService.executePageAction(copyOfPageAction);
+ });
+ return this;
+ }
+
+ public Set getConnectionTokens(
+ final Predicate filter,
+ final boolean selected) {
+
+ if (selected) {
+ final int[] selectionIndices = this.table.getSelectionIndices();
+ if (selectionIndices == null || selectionIndices.length < 1) {
+ return Collections.emptySet();
+ }
+
+ final Set result = new HashSet<>();
+ for (int i = 0; i < selectionIndices.length; i++) {
+ final UpdatableTableItem updatableTableItem =
+ new ArrayList<>(this.tableMapping.values())
+ .get(selectionIndices[i]);
+ if (filter.test(updatableTableItem.connectionData.clientConnection)) {
+ result.add(updatableTableItem.connectionData.clientConnection.connectionToken);
+ }
+ }
+ return result;
+ } else {
+ return this.tableMapping
+ .values()
+ .stream()
+ .map(item -> item.connectionData.clientConnection)
+ .filter(filter)
+ .map(ClientConnection::getConnectionToken)
+ .collect(Collectors.toSet());
+ }
+ }
+
+ public void removeSelection() {
+ if (this.table != null) {
+ this.table.deselectAll();
+ this.notifySelectionChange();
+ }
+ }
+
+ public ClientConnectionTable withSelectionListener(Consumer> selectionListener) {
+ this.selectionListener = selectionListener;
+ return this;
+ }
+
+ public Set getSelection() {
+ final int[] selectionIndices = this.table.getSelectionIndices();
+ if (selectionIndices == null || selectionIndices.length < 1) {
+ return Collections.emptySet();
+ }
+
+ final Set result = new HashSet<>();
+ for (int i = 0; i < selectionIndices.length; i++) {
+ final UpdatableTableItem updatableTableItem =
+ new ArrayList<>(this.tableMapping.values()).get(selectionIndices[0]);
+ result.add(new EntityKey(updatableTableItem.connectionId, EntityType.CLIENT_CONNECTION));
+ }
+ return result;
+ }
+
+ public Tuple getSingleSelection() {
+ final int[] selectionIndices = this.table.getSelectionIndices();
+ if (selectionIndices == null || selectionIndices.length < 1) {
+ return null;
+ }
+
+ final UpdatableTableItem updatableTableItem =
+ new ArrayList<>(this.tableMapping.values()).get(selectionIndices[0]);
+ return new Tuple<>(
+ (updatableTableItem.connectionId != null)
+ ? String.valueOf(updatableTableItem.connectionId)
+ : null,
+ updatableTableItem.connectionData.clientConnection.connectionToken);
+ }
+
+ public void updateValues() {
+ if (this.statusFilterChanged) {
+ this.toDelete.clear();
+ this.toDelete.addAll(this.tableMapping.keySet());
+ }
+ this.restCallBuilder
+ .withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam)
+ .call()
+ .get(error -> {
+ log.error("Error poll connection data: ", error);
+ return Collections.emptyList();
+ })
+ .forEach(data -> {
+ final UpdatableTableItem tableItem = this.tableMapping.computeIfAbsent(
+ data.getConnectionId(),
+ UpdatableTableItem::new);
+ tableItem.push(data);
+ if (this.statusFilterChanged) {
+ this.toDelete.remove(data.getConnectionId());
+ }
+ });
+
+ if (this.statusFilterChanged && !this.toDelete.isEmpty()) {
+ this.toDelete.forEach(id -> {
+ final UpdatableTableItem item = this.tableMapping.remove(id);
+ final List list = this.sessionIds.get(item.connectionData.clientConnection.userSessionId);
+ if (list != null) {
+ list.remove(id);
+ }
+ });
+ this.statusFilterChanged = false;
+ }
+ }
+
+ public void updateGUI() {
+ fillTable();
+ if (this.needsSort) {
+ sortTable();
+ }
+
+ final TableItem[] items = this.table.getItems();
+ final Iterator> iterator = this.tableMapping.entrySet().iterator();
+ for (int i = 0; i < items.length; i++) {
+ final UpdatableTableItem uti = iterator.next().getValue();
+ uti.update(items[i], this.needsSort);
+ }
+
+ this.needsSort = false;
+ adaptTableWidth();
+ this.table.layout(true, true);
+ }
+
+ private void adaptTableWidth() {
+ final Rectangle area = this.table.getParent().getClientArea();
+ if (this.tableWidth != area.width) {
+ final int columnWidth = area.width / this.table.getColumnCount() - 5;
+ for (final TableColumn column : this.table.getColumns()) {
+ column.setWidth(columnWidth);
+ }
+ this.tableWidth = area.width;
+ }
+
+ // update table height
+ final GridData gridData = (GridData) this.table.getLayoutData();
+ gridData.heightHint = area.height - BOTTOM_PADDING;
+ }
+
+ private void fillTable() {
+ if (this.tableMapping.size() > this.table.getItemCount()) {
+ while (this.tableMapping.size() > this.table.getItemCount()) {
+ new TableItem(this.table, SWT.NONE);
+ }
+ } else if (this.tableMapping.size() < this.table.getItemCount()) {
+ while (this.tableMapping.size() < this.table.getItemCount()) {
+ this.table.getItem(0).dispose();
+ }
+ }
+ }
+
+ private void sortTable() {
+ this.tableMapping = this.tableMapping.entrySet()
+ .stream()
+ .sorted((e1, e2) -> e1.getValue().compareTo(e2.getValue()))
+ .collect(Collectors.toMap(
+ Entry::getKey,
+ Entry::getValue,
+ (e1, e2) -> e1, LinkedHashMap::new));
+ }
+
+ private void saveStatusFilter() {
+ try {
+ this.resourceService
+ .getCurrentUser()
+ .putAttribute(
+ USER_SESSION_STATUS_FILTER_ATTRIBUTE,
+ StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR));
+ } catch (final Exception e) {
+ log.warn("Failed to save status filter to user session");
+ } finally {
+ this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR);
+ this.statusFilterChanged = true;
+ }
+ }
+
+ private void loadStatusFilter() {
+ try {
+ final String attribute = this.resourceService
+ .getCurrentUser()
+ .getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE);
+ if (attribute != null) {
+ this.statusFilter.clear();
+ Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR))
+ .forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name)));
+
+ } else {
+ this.statusFilter.clear();
+ this.statusFilter.add(ConnectionStatus.DISABLED);
+ }
+ } catch (final Exception e) {
+ log.warn("Failed to load status filter to user session");
+ this.statusFilter.clear();
+ this.statusFilter.add(ConnectionStatus.DISABLED);
+ } finally {
+ this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR);
+ this.statusFilterChanged = true;
+ }
+ }
+
+ private void notifySelectionChange() {
+ if (this.selectionListener == null) {
+ return;
+ }
+
+ this.selectionListener.accept(this.getSelection());
+ }
+
+ private final class UpdatableTableItem implements Comparable {
+
+ final Long connectionId;
+ private boolean changed = false;
+ private ClientConnectionData connectionData;
+ private int thresholdsWeight;
+ private int[] indicatorWeights = null;
+ private boolean duplicateChecked = false;
+
+ UpdatableTableItem(final Long connectionId) {
+ this.connectionId = connectionId;
+ }
+
+ void update(final TableItem tableItem, final boolean force) {
+ if (force || this.changed) {
+ update(tableItem);
+ }
+ this.changed = false;
+ }
+
+ void update(final TableItem tableItem) {
+ if (ClientConnectionTable.this.statusFilter.contains(this.connectionData.clientConnection.status)) {
+ tableItem.dispose();
+ return;
+ }
+ updateData(tableItem);
+ if (this.connectionData != null) {
+ updateConnectionStatusColor(tableItem);
+ updateIndicatorValues(tableItem);
+ updateDuplicateColor(tableItem);
+
+ }
+ }
+
+ void updateData(final TableItem tableItem) {
+ tableItem.setText(0, getConnectionIdentifer());
+ tableItem.setText(1, getConnectionAddress());
+ tableItem.setText(2, getStatusName());
+ }
+
+ void updateConnectionStatusColor(final TableItem tableItem) {
+ final Color statusColor = ClientConnectionTable.this.colorData.getStatusColor(this.connectionData);
+ final Color statusTextColor = ClientConnectionTable.this.colorData.getStatusTextColor(statusColor);
+ tableItem.setBackground(2, statusColor);
+ tableItem.setForeground(2, statusTextColor);
+ }
+
+ void updateDuplicateColor(final TableItem tableItem) {
+ if (!this.duplicateChecked) {
+ return;
+ }
+
+ if (this.connectionData != null
+ && StringUtils.isNotBlank(this.connectionData.clientConnection.userSessionId)) {
+ final List list =
+ ClientConnectionTable.this.sessionIds.get(this.connectionData.clientConnection.userSessionId);
+ if (list != null && list.size() > 1) {
+ tableItem.setBackground(0, ClientConnectionTable.this.colorData.color3);
+ tableItem.setForeground(0, ClientConnectionTable.this.lightFontColor);
+ } else {
+ tableItem.setBackground(0, null);
+ tableItem.setForeground(0, ClientConnectionTable.this.darkFontColor);
+ }
+ } else {
+ tableItem.setBackground(0, null);
+ tableItem.setForeground(0, ClientConnectionTable.this.darkFontColor);
+ }
+ }
+
+ void updateIndicatorValues(final TableItem tableItem) {
+ if (this.connectionData == null || this.indicatorWeights == null) {
+ return;
+ }
+
+ for (int i = 0; i < this.connectionData.indicatorValues.size(); i++) {
+ final IndicatorValue indicatorValue = this.connectionData.indicatorValues.get(i);
+ final IndicatorData indicatorData =
+ ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType());
+
+ if (!this.connectionData.clientConnection.status.establishedStatus) {
+ final String value = (indicatorData.indicator.type.showOnlyInActiveState)
+ ? Constants.EMPTY_NOTE
+ : IndicatorValue.getDisplayValue(indicatorValue);
+ tableItem.setText(indicatorData.tableIndex, value);
+ tableItem.setBackground(indicatorData.tableIndex, indicatorData.defaultColor);
+ tableItem.setForeground(indicatorData.tableIndex, indicatorData.defaultTextColor);
+ } else {
+ tableItem.setText(indicatorData.tableIndex, IndicatorValue.getDisplayValue(indicatorValue));
+ final int weight = this.indicatorWeights[indicatorData.index];
+ if (weight >= 0 && weight < indicatorData.thresholdColor.length) {
+ final ThresholdColor thresholdColor = indicatorData.thresholdColor[weight];
+ tableItem.setBackground(indicatorData.tableIndex, thresholdColor.color);
+ tableItem.setForeground(indicatorData.tableIndex, thresholdColor.textColor);
+ } else {
+ tableItem.setBackground(indicatorData.tableIndex, indicatorData.defaultColor);
+ tableItem.setForeground(indicatorData.tableIndex, indicatorData.defaultTextColor);
+ }
+ }
+ }
+ }
+
+ @Override
+ public int compareTo(final UpdatableTableItem other) {
+ return Comparator.comparingInt(UpdatableTableItem::statusWeight)
+ .thenComparingInt(UpdatableTableItem::thresholdsWeight)
+ .thenComparing(UpdatableTableItem::getConnectionIdentifer)
+ .compare(this, other);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getOuterType().hashCode();
+ result = prime * result + ((this.connectionId == null) ? 0 : this.connectionId.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final UpdatableTableItem other = (UpdatableTableItem) obj;
+ if (!getOuterType().equals(other.getOuterType()))
+ return false;
+ return compareTo(other) == 0;
+ }
+
+ int statusWeight() {
+ return ClientConnectionTable.this.colorData.statusWeight(this.connectionData);
+ }
+
+ int thresholdsWeight() {
+ return -this.thresholdsWeight;
+ }
+
+ String getStatusName() {
+ return ClientConnectionTable.this.resourceService
+ .localizedClientConnectionStatusName(this.connectionData);
+ }
+
+ String getConnectionAddress() {
+ if (this.connectionData != null && this.connectionData.clientConnection.clientAddress != null) {
+ return this.connectionData.clientConnection.clientAddress;
+ }
+ return Constants.EMPTY_NOTE;
+ }
+
+ String getConnectionIdentifer() {
+ if (this.connectionData != null && this.connectionData.clientConnection.userSessionId != null) {
+ return this.connectionData.clientConnection.userSessionId;
+ }
+
+ return "--";
+ }
+
+ void push(final ClientConnectionData connectionData) {
+ this.changed = this.connectionData == null ||
+ !this.connectionData.dataEquals(connectionData);
+
+ final boolean statusChanged = this.connectionData == null ||
+ this.connectionData.clientConnection.status != connectionData.clientConnection.status;
+
+ if (statusChanged) {
+ ClientConnectionTable.this.needsSort = true;
+ }
+
+ if (this.indicatorWeights == null) {
+ this.indicatorWeights = new int[ClientConnectionTable.this.indicatorMapping.size()];
+ }
+
+ for (int i = 0; i < connectionData.indicatorValues.size(); i++) {
+ final IndicatorValue indicatorValue = connectionData.indicatorValues.get(i);
+ final IndicatorData indicatorData =
+ ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType());
+
+ if (indicatorData == null) {
+ log.error("No IndicatorData of type: {} found", indicatorValue.getType());
+ } else {
+
+ final double value = indicatorValue.getValue();
+ final int indicatorWeight = IndicatorData.getWeight(indicatorData, value);
+ if (this.indicatorWeights[indicatorData.index] != indicatorWeight) {
+ ClientConnectionTable.this.needsSort = true;
+ this.thresholdsWeight -= this.indicatorWeights[indicatorData.index];
+ this.indicatorWeights[indicatorData.index] = indicatorWeight;
+ this.thresholdsWeight += this.indicatorWeights[indicatorData.index];
+ }
+ }
+ }
+
+ this.connectionData = connectionData;
+
+ if (!this.duplicateChecked &&
+ this.connectionData.clientConnection.status != ConnectionStatus.DISABLED &&
+ StringUtils.isNotBlank(connectionData.clientConnection.userSessionId)) {
+
+ ClientConnectionTable.this.sessionIds.add(
+ connectionData.clientConnection.userSessionId,
+ this.connectionId);
+ this.duplicateChecked = true;
+ }
+ }
+
+ private ClientConnectionTable getOuterType() {
+ return ClientConnectionTable.this;
+ }
+
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCheckbox.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCheckbox.java
index c799af18..0a5e5105 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCheckbox.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCheckbox.java
@@ -104,7 +104,6 @@ public final class MultiSelectionCheckbox extends Composite implements Selection
}
Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR))
- .stream()
.forEach(key -> {
final Button button = this.checkboxes.get(key);
if (button != null) {
@@ -120,7 +119,7 @@ public final class MultiSelectionCheckbox extends Composite implements Selection
this.checkboxes
.values()
.stream()
- .filter(button -> button.getSelection())
+ .filter(Button::getSelection)
.map(button -> (String) button.getData(OPTION_VALUE))
.collect(Collectors.toList()).toArray());
}
@@ -129,7 +128,6 @@ public final class MultiSelectionCheckbox extends Composite implements Selection
public void clear() {
this.checkboxes
.values()
- .stream()
.forEach(button -> button.setSelection(false));
}
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
index 91c10933..39276d2d 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/ThresholdList.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/ThresholdList.java
@@ -1,242 +1,249 @@
-/*
- * 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.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.function.Supplier;
-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;
-import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
-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.service.page.PageService;
-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 final 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 String COLOR_SELECTION_TEXT_KEY = "sebserver.exam.indicator.thresholds.select.color";
- private static final LocTextKey VALUE_TEXT_KEY = new LocTextKey("sebserver.exam.indicator.thresholds.list.value");
- private static final LocTextKey COLOR_TEXT_KEY = new LocTextKey("sebserver.exam.indicator.thresholds.list.color");
- private static final LocTextKey ADD_TEXT_KEY = new LocTextKey("sebserver.exam.indicator.thresholds.list.add");
- private static final LocTextKey REMOVE_TEXT_KEY = new LocTextKey("sebserver.exam.indicator.thresholds.list.remove");
-
- private final WidgetFactory widgetFactory;
- private final Supplier indicatorTypeSupplier;
- private final List thresholds = new ArrayList<>();
-
- private final GridData valueCell;
- private final GridData colorCell;
- private final GridData actionCell;
- private final Composite updateAnchor;
-
- ThresholdList(
- final Composite parent,
- final Composite updateAnchor,
- final WidgetFactory widgetFactory,
- final Supplier indicatorTypeSupplier) {
-
- super(parent, SWT.NONE);
- this.indicatorTypeSupplier = indicatorTypeSupplier;
- this.updateAnchor = updateAnchor;
- this.widgetFactory = widgetFactory;
- super.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
- 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,
- VALUE_TEXT_KEY);
- this.valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
- valueTitle.setLayoutData(this.valueCell);
-
- final Label colorTitle = widgetFactory.labelLocalized(
- this,
- CustomVariant.TITLE_LABEL,
- COLOR_TEXT_KEY);
- this.colorCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
- colorTitle.setLayoutData(this.colorCell);
-
- final Label imageButton = widgetFactory.imageButton(
- ImageIcon.ADD_BOX,
- this,
- ADD_TEXT_KEY,
- this::addThreshold);
- this.actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
- imageButton.setLayoutData(this.actionCell);
- }
-
- public void setThresholds(final Collection thresholds) {
- clearList();
- if (thresholds != null) {
- thresholds
- .stream()
- .forEach(this::addThreshold);
- }
- }
-
- public Collection 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 -> {
- if (this.indicatorTypeSupplier.get().integerValue) {
- Integer.parseInt(s);
- } else {
- 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, null, null,
- COLOR_SELECTION_TEXT_KEY);
- 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,
- REMOVE_TEXT_KEY,
- null);
- final GridData actionCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
- imageButton.setLayoutData(actionCell);
-
- if (threshold != null) {
- if (threshold.value != null) {
- valueInput.setText(Indicator.getDisplayValue(
- this.indicatorTypeSupplier.get(),
- threshold.value));
- }
- if (threshold.color != null) {
- selector.select(threshold.color);
- }
- }
-
- final Entry entry = new Entry(valueInput, selector, imageButton);
- this.thresholds.add(entry);
-
- this.updateAnchor.layout();
- PageService.updateScrolledComposite(this);
- }
-
- private void removeThreshold(final Entry entry) {
- if (this.thresholds.remove(entry)) {
- entry.dispose();
- }
-
- this.updateAnchor.layout();
- PageService.updateScrolledComposite(this);
- }
-
- private void adaptColumnWidth(final Event event) {
- try {
- 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();
- }
- }
-
-}
+/*
+ * 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.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+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;
+import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
+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.service.page.PageService;
+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 final 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 String COLOR_SELECTION_TEXT_KEY = "sebserver.exam.indicator.thresholds.select.color";
+ private static final LocTextKey VALUE_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.thresholds.list.value");
+ private static final LocTextKey VALUE_TOOLTIP_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.thresholds.list.value" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
+ private static final LocTextKey COLOR_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.thresholds.list.color");
+ private static final LocTextKey COLOR_TOOLTIP_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.thresholds.list.color" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
+ private static final LocTextKey ADD_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.thresholds.list.add");
+ private static final LocTextKey REMOVE_TEXT_KEY =
+ new LocTextKey("sebserver.exam.indicator.thresholds.list.remove");
+
+ private final WidgetFactory widgetFactory;
+ private final Supplier indicatorTypeSupplier;
+ private final List thresholds = new ArrayList<>();
+
+ private final GridData valueCell;
+ private final GridData colorCell;
+ private final GridData actionCell;
+ private final Composite updateAnchor;
+
+ ThresholdList(
+ final Composite parent,
+ final Composite updateAnchor,
+ final WidgetFactory widgetFactory,
+ final Supplier indicatorTypeSupplier) {
+
+ super(parent, SWT.NONE);
+ this.indicatorTypeSupplier = indicatorTypeSupplier;
+ this.updateAnchor = updateAnchor;
+ this.widgetFactory = widgetFactory;
+ super.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+ 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,
+ VALUE_TEXT_KEY,
+ VALUE_TOOLTIP_TEXT_KEY);
+ this.valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
+ valueTitle.setLayoutData(this.valueCell);
+
+ final Label colorTitle = widgetFactory.labelLocalized(
+ this,
+ CustomVariant.TITLE_LABEL,
+ COLOR_TEXT_KEY,
+ COLOR_TOOLTIP_TEXT_KEY);
+ this.colorCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
+ colorTitle.setLayoutData(this.colorCell);
+
+ final Label imageButton = widgetFactory.imageButton(
+ ImageIcon.ADD_BOX,
+ this,
+ ADD_TEXT_KEY,
+ this::addThreshold);
+ this.actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false);
+ imageButton.setLayoutData(this.actionCell);
+ }
+
+ public void setThresholds(final Collection thresholds) {
+ clearList();
+ if (thresholds != null) {
+ thresholds.forEach(this::addThreshold);
+ }
+ }
+
+ public Collection 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())
+ .forEach(this::removeThreshold);
+ }
+
+ private void clearList() {
+ this.thresholds.forEach(Entry::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 -> {
+ if (this.indicatorTypeSupplier.get().integerValue) {
+ Integer.parseInt(s);
+ } else {
+ 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, null, null,
+ COLOR_SELECTION_TEXT_KEY);
+ 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,
+ REMOVE_TEXT_KEY,
+ null);
+ final GridData actionCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
+ imageButton.setLayoutData(actionCell);
+
+ if (threshold != null) {
+ if (threshold.value != null) {
+ valueInput.setText(Indicator.getDisplayValue(
+ this.indicatorTypeSupplier.get(),
+ threshold.value));
+ }
+ if (threshold.color != null) {
+ selector.select(threshold.color);
+ }
+ }
+
+ final Entry entry = new Entry(valueInput, selector, imageButton);
+ this.thresholds.add(entry);
+
+ this.updateAnchor.layout();
+ PageService.updateScrolledComposite(this);
+ }
+
+ private void removeThreshold(final Entry entry) {
+ if (this.thresholds.remove(entry)) {
+ entry.dispose();
+ }
+
+ this.updateAnchor.layout();
+ PageService.updateScrolledComposite(this);
+ }
+
+ private void adaptColumnWidth(final Event event) {
+ try {
+ 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();
+ }
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
index 3197b2cb..df699ca7 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
@@ -1,293 +1,290 @@
-/*
- * Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.LinkedMultiValueMap;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.core.type.TypeReference;
-
-import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
-import ch.ethz.seb.sebserver.gbl.async.AsyncService;
-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.LmsSetupTestResult;
-import ch.ethz.seb.sebserver.gbl.util.Result;
-import ch.ethz.seb.sebserver.gbl.util.Utils;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
-
-/** Implements the LmsAPITemplate for Open edX LMS Course API access.
- *
- * See also: https://docs.moodle.org/dev/Web_service_API_functions */
-public class MoodleCourseAccess extends CourseAccess {
-
- private static final Logger log = LoggerFactory.getLogger(MoodleCourseAccess.class);
-
- private static final String MOOLDE_QUIZ_START_URL_PATH = "/mod/quiz/view.php?id=";
- private static final String MOODLE_COURSE_API_FUNCTION_NAME = "core_course_get_courses";
- private static final String MOODLE_QUIZ_API_FUNCTION_NAME = "mod_quiz_get_quizzes_by_courses";
-
- private final JSONMapper jsonMapper;
- private final LmsSetup lmsSetup;
- private final MoodleRestTemplateFactory moodleRestTemplateFactory;
-
- private MoodleAPIRestTemplate restTemplate;
-
- protected MoodleCourseAccess(
- final JSONMapper jsonMapper,
- final LmsSetup lmsSetup,
- final MoodleRestTemplateFactory moodleRestTemplateFactory,
- final AsyncService asyncService) {
-
- super(asyncService);
- this.jsonMapper = jsonMapper;
- this.lmsSetup = lmsSetup;
- this.moodleRestTemplateFactory = moodleRestTemplateFactory;
- }
-
- LmsSetupTestResult initAPIAccess() {
-
- final LmsSetupTestResult attributesCheck = this.moodleRestTemplateFactory.test();
- if (!attributesCheck.isOk()) {
- return attributesCheck;
- }
-
- final Result restTemplateRequest = getRestTemplate();
- if (restTemplateRequest.hasError()) {
- final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " +
- this.moodleRestTemplateFactory.knownTokenAccessPaths;
- log.error(message, restTemplateRequest.getError().getMessage());
- return LmsSetupTestResult.ofTokenRequestError(message);
- }
-
- final MoodleAPIRestTemplate restTemplate = restTemplateRequest.get();
-
- try {
- restTemplate.testAPIConnection(
- MOODLE_COURSE_API_FUNCTION_NAME,
- MOODLE_QUIZ_API_FUNCTION_NAME);
- } catch (final RuntimeException e) {
- log.error("Failed to access Open edX course API: ", e);
- return LmsSetupTestResult.ofQuizAccessAPIError(e.getMessage());
- }
-
- return LmsSetupTestResult.ofOkay();
- }
-
- @Override
- protected Supplier> allQuizzesSupplier() {
- return () -> {
- return getRestTemplate()
- .map(this::collectAllQuizzes)
- .getOrThrow();
- };
- }
-
- private ArrayList collectAllQuizzes(final MoodleAPIRestTemplate restTemplate) {
- final String urlPrefix = this.lmsSetup.lmsApiUrl + MOOLDE_QUIZ_START_URL_PATH;
- return collectAllCourses(
- restTemplate)
- .stream()
- .reduce(
- new ArrayList(),
- (list, courseData) -> {
- list.addAll(quizDataOf(
- this.lmsSetup,
- courseData,
- urlPrefix));
- return list;
- },
- (list1, list2) -> {
- list1.addAll(list2);
- return list1;
- });
- }
-
- private List collectAllCourses(final MoodleAPIRestTemplate restTemplate) {
-
- try {
-
- // first get courses form Moodle...
- final String coursesJSON = restTemplate.callMoodleAPIFunction(MOODLE_COURSE_API_FUNCTION_NAME);
- final Map courseData = this.jsonMapper.> readValue(
- coursesJSON,
- new TypeReference>() {
- })
- .stream()
- .collect(Collectors.toMap(d -> d.id, Function.identity()));
-
- // then get all quizzes of courses and filter
- final LinkedMultiValueMap attributes = new LinkedMultiValueMap<>();
- attributes.put("courseids", new ArrayList<>(courseData.keySet()));
-
- final String quizzesJSON = restTemplate.callMoodleAPIFunction(
- MOODLE_QUIZ_API_FUNCTION_NAME,
- attributes);
-
- final CourseQuizData courseQuizData = this.jsonMapper.readValue(
- quizzesJSON,
- CourseQuizData.class);
-
- courseQuizData.quizzes
- .stream()
- .forEach(quiz -> {
- final CourseData course = courseData.get(quiz.course);
- if (course != null) {
- course.quizzes.add(quiz);
- }
- });
-
- return courseData.values()
- .stream()
- .filter(c -> !c.quizzes.isEmpty())
- .collect(Collectors.toList());
- } catch (final Exception e) {
- throw new RuntimeException("Unexpected exception while trying to get course data: ", e);
- }
- }
-
- private static List quizDataOf(
- final LmsSetup lmsSetup,
- final CourseData courseData,
- final String uriPrefix) {
-
- final Map additionalAttrs = new HashMap<>();
- additionalAttrs.clear();
- additionalAttrs.put("timecreated", String.valueOf(courseData.timecreated));
- additionalAttrs.put("course_shortname", courseData.shortname);
- additionalAttrs.put("course_fullname", courseData.fullname);
- additionalAttrs.put("course_displayname", courseData.displayname);
- additionalAttrs.put("course_summary", courseData.summary);
-
- return courseData.quizzes
- .stream()
- .map(courseQuizData -> {
- final String startURI = uriPrefix + courseData.id;
- additionalAttrs.put("timelimit", String.valueOf(courseQuizData.timelimit));
- return new QuizData(
- courseQuizData.id,
- lmsSetup.getInstitutionId(),
- lmsSetup.id,
- lmsSetup.getLmsType(),
- courseQuizData.name,
- courseQuizData.intro,
- Utils.toDateTimeUTCUnix(courseData.startdate),
- Utils.toDateTimeUTCUnix(courseData.enddate),
- startURI,
- additionalAttrs);
- })
- .collect(Collectors.toList());
-
- }
-
- private Result getRestTemplate() {
- if (this.restTemplate == null) {
- final Result templateRequest = this.moodleRestTemplateFactory
- .createRestTemplate();
- if (templateRequest.hasError()) {
- return templateRequest;
- } else {
- this.restTemplate = templateRequest.get();
- }
- }
-
- return Result.of(this.restTemplate);
- }
-
- /** Maps the Moodle course API course data */
- @JsonIgnoreProperties(ignoreUnknown = true)
- static final class CourseData {
- final String id;
- final String shortname;
- final String fullname;
- final String displayname;
- final String summary;
- final Long startdate; // unixtime milliseconds UTC
- final Long enddate; // unixtime milliseconds UTC
- final Long timecreated; // unixtime milliseconds UTC
- final Collection quizzes = new ArrayList<>();
-
- @JsonCreator
- protected CourseData(
- @JsonProperty(value = "id") final String id,
- @JsonProperty(value = "shortname") final String shortname,
- @JsonProperty(value = "fullname") final String fullname,
- @JsonProperty(value = "displayname") final String displayname,
- @JsonProperty(value = "summary") final String summary,
- @JsonProperty(value = "startdate") final Long startdate,
- @JsonProperty(value = "enddate") final Long enddate,
- @JsonProperty(value = "timecreated") final Long timecreated) {
-
- this.id = id;
- this.shortname = shortname;
- this.fullname = fullname;
- this.displayname = displayname;
- this.summary = summary;
- this.startdate = startdate;
- this.enddate = enddate;
- this.timecreated = timecreated;
- }
-
- }
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- static final class CourseQuizData {
- final Collection quizzes;
-
- @JsonCreator
- protected CourseQuizData(
- @JsonProperty(value = "quizzes") final Collection quizzes) {
-
- this.quizzes = quizzes;
- }
- }
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- static final class CourseQuiz {
- final String id;
- final String course;
- final String coursemodule;
- final String name;
- final String intro; // HTML
- final Long timelimit; // unixtime milliseconds UTC
-
- @JsonCreator
- protected CourseQuiz(
- @JsonProperty(value = "id") final String id,
- @JsonProperty(value = "course") final String course,
- @JsonProperty(value = "coursemodule") final String coursemodule,
- @JsonProperty(value = "name") final String name,
- @JsonProperty(value = "intro") final String intro,
- @JsonProperty(value = "timelimit") final Long timelimit) {
-
- this.id = id;
- this.course = course;
- this.coursemodule = coursemodule;
- this.name = name;
- this.intro = intro;
- this.timelimit = timelimit;
- }
-
- }
-
-}
+/*
+ * Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.LinkedMultiValueMap;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
+import ch.ethz.seb.sebserver.gbl.async.AsyncService;
+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.LmsSetupTestResult;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
+
+/** Implements the LmsAPITemplate for Open edX LMS Course API access.
+ *
+ * See also: https://docs.moodle.org/dev/Web_service_API_functions */
+public class MoodleCourseAccess extends CourseAccess {
+
+ private static final Logger log = LoggerFactory.getLogger(MoodleCourseAccess.class);
+
+ private static final String MOODLE_QUIZ_START_URL_PATH = "/mod/quiz/view.php?id=";
+ private static final String MOODLE_COURSE_API_FUNCTION_NAME = "core_course_get_courses";
+ private static final String MOODLE_QUIZ_API_FUNCTION_NAME = "mod_quiz_get_quizzes_by_courses";
+ private static final String MOODLE_COURSE_API_COURSE_IDS = "courseids";
+
+ private final JSONMapper jsonMapper;
+ private final LmsSetup lmsSetup;
+ private final MoodleRestTemplateFactory moodleRestTemplateFactory;
+
+ private MoodleAPIRestTemplate restTemplate;
+
+ protected MoodleCourseAccess(
+ final JSONMapper jsonMapper,
+ final LmsSetup lmsSetup,
+ final MoodleRestTemplateFactory moodleRestTemplateFactory,
+ final AsyncService asyncService) {
+
+ super(asyncService);
+ this.jsonMapper = jsonMapper;
+ this.lmsSetup = lmsSetup;
+ this.moodleRestTemplateFactory = moodleRestTemplateFactory;
+ }
+
+ LmsSetupTestResult initAPIAccess() {
+
+ final LmsSetupTestResult attributesCheck = this.moodleRestTemplateFactory.test();
+ if (!attributesCheck.isOk()) {
+ return attributesCheck;
+ }
+
+ final Result restTemplateRequest = getRestTemplate();
+ if (restTemplateRequest.hasError()) {
+ final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " +
+ this.moodleRestTemplateFactory.knownTokenAccessPaths;
+ log.error(message, restTemplateRequest.getError().getMessage());
+ return LmsSetupTestResult.ofTokenRequestError(message);
+ }
+
+ final MoodleAPIRestTemplate restTemplate = restTemplateRequest.get();
+
+ try {
+ restTemplate.testAPIConnection(
+ MOODLE_COURSE_API_FUNCTION_NAME,
+ MOODLE_QUIZ_API_FUNCTION_NAME);
+ } catch (final RuntimeException e) {
+ log.error("Failed to access Open edX course API: ", e);
+ return LmsSetupTestResult.ofQuizAccessAPIError(e.getMessage());
+ }
+
+ return LmsSetupTestResult.ofOkay();
+ }
+
+ @Override
+ protected Supplier> allQuizzesSupplier() {
+ return () -> getRestTemplate()
+ .map(this::collectAllQuizzes)
+ .getOrThrow();
+ }
+
+ private ArrayList collectAllQuizzes(final MoodleAPIRestTemplate restTemplate) {
+ final String urlPrefix = this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH;
+ return collectAllCourses(
+ restTemplate)
+ .stream()
+ .reduce(
+ new ArrayList<>(),
+ (list, courseData) -> {
+ list.addAll(quizDataOf(
+ this.lmsSetup,
+ courseData,
+ urlPrefix));
+ return list;
+ },
+ (list1, list2) -> {
+ list1.addAll(list2);
+ return list1;
+ });
+ }
+
+ private List collectAllCourses(final MoodleAPIRestTemplate restTemplate) {
+
+ try {
+
+ // first get courses form Moodle...
+ final String coursesJSON = restTemplate.callMoodleAPIFunction(MOODLE_COURSE_API_FUNCTION_NAME);
+ final Map courseData = this.jsonMapper.> readValue(
+ coursesJSON,
+ new TypeReference>() {
+ })
+ .stream()
+ .collect(Collectors.toMap(d -> d.id, Function.identity()));
+
+ // then get all quizzes of courses and filter
+ final LinkedMultiValueMap attributes = new LinkedMultiValueMap<>();
+ attributes.put(MOODLE_COURSE_API_COURSE_IDS, new ArrayList<>(courseData.keySet()));
+
+ final String quizzesJSON = restTemplate.callMoodleAPIFunction(
+ MOODLE_QUIZ_API_FUNCTION_NAME,
+ attributes);
+
+ final CourseQuizData courseQuizData = this.jsonMapper.readValue(
+ quizzesJSON,
+ CourseQuizData.class);
+
+ courseQuizData.quizzes
+ .forEach(quiz -> {
+ final CourseData course = courseData.get(quiz.course);
+ if (course != null) {
+ course.quizzes.add(quiz);
+ }
+ });
+
+ return courseData.values()
+ .stream()
+ .filter(c -> !c.quizzes.isEmpty())
+ .collect(Collectors.toList());
+ } catch (final Exception e) {
+ throw new RuntimeException("Unexpected exception while trying to get course data: ", e);
+ }
+ }
+
+ static Map additionalAttrs = new HashMap<>();
+ private static List quizDataOf(
+ final LmsSetup lmsSetup,
+ final CourseData courseData,
+ final String uriPrefix) {
+
+ additionalAttrs.clear();
+ additionalAttrs.put(QuizData.ATTR_ADDITIONAL_CREATION_TIME, String.valueOf(courseData.time_created));
+ additionalAttrs.put(QuizData.ATTR_ADDITIONAL_SHORT_NAME, courseData.short_name);
+ additionalAttrs.put(QuizData.ATTR_ADDITIONAL_FULL_NAME, courseData.full_name);
+ additionalAttrs.put(QuizData.ATTR_ADDITIONAL_DISPLAY_NAME, courseData.display_name);
+ additionalAttrs.put(QuizData.ATTR_ADDITIONAL_SUMMARY, courseData.summary);
+
+ return courseData.quizzes
+ .stream()
+ .map(courseQuizData -> {
+ final String startURI = uriPrefix + courseData.id;
+ additionalAttrs.put(QuizData.ATTR_ADDITIONAL_TIME_LIMIT, String.valueOf(courseQuizData.time_limit));
+ return new QuizData(
+ courseQuizData.id,
+ lmsSetup.getInstitutionId(),
+ lmsSetup.id,
+ lmsSetup.getLmsType(),
+ courseQuizData.name,
+ courseQuizData.intro,
+ Utils.toDateTimeUTCUnix(courseData.start_date),
+ Utils.toDateTimeUTCUnix(courseData.end_date),
+ startURI,
+ additionalAttrs);
+ })
+ .collect(Collectors.toList());
+ }
+
+ private Result getRestTemplate() {
+ if (this.restTemplate == null) {
+ final Result templateRequest = this.moodleRestTemplateFactory
+ .createRestTemplate();
+ if (templateRequest.hasError()) {
+ return templateRequest;
+ } else {
+ this.restTemplate = templateRequest.get();
+ }
+ }
+
+ return Result.of(this.restTemplate);
+ }
+
+ /** Maps the Moodle course API course data */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ static final class CourseData {
+ final String id;
+ final String short_name;
+ final String full_name;
+ final String display_name;
+ final String summary;
+ final Long start_date; // unix-time milliseconds UTC
+ final Long end_date; // unix-time milliseconds UTC
+ final Long time_created; // unix-time milliseconds UTC
+ final Collection quizzes = new ArrayList<>();
+
+ @JsonCreator
+ protected CourseData(
+ @JsonProperty(value = "id") final String id,
+ @JsonProperty(value = "shortname") final String short_name,
+ @JsonProperty(value = "fullname") final String full_name,
+ @JsonProperty(value = "displayname") final String display_name,
+ @JsonProperty(value = "summary") final String summary,
+ @JsonProperty(value = "startdate") final Long start_date,
+ @JsonProperty(value = "enddate") final Long end_date,
+ @JsonProperty(value = "timecreated") final Long time_created) {
+
+ this.id = id;
+ this.short_name = short_name;
+ this.full_name = full_name;
+ this.display_name = display_name;
+ this.summary = summary;
+ this.start_date = start_date;
+ this.end_date = end_date;
+ this.time_created = time_created;
+ }
+
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ static final class CourseQuizData {
+ final Collection quizzes;
+
+ @JsonCreator
+ protected CourseQuizData(
+ @JsonProperty(value = "quizzes") final Collection quizzes) {
+
+ this.quizzes = quizzes;
+ }
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ static final class CourseQuiz {
+ final String id;
+ final String course;
+ final String course_module;
+ final String name;
+ final String intro; // HTML
+ final Long time_limit; // unix-time milliseconds UTC
+
+ @JsonCreator
+ protected CourseQuiz(
+ @JsonProperty(value = "id") final String id,
+ @JsonProperty(value = "course") final String course,
+ @JsonProperty(value = "coursemodule") final String course_module,
+ @JsonProperty(value = "name") final String name,
+ @JsonProperty(value = "intro") final String intro,
+ @JsonProperty(value = "timelimit") final Long time_limit) {
+
+ this.id = id;
+ this.course = course;
+ this.course_module = course_module;
+ this.name = name;
+ this.intro = intro;
+ this.time_limit = time_limit;
+ }
+
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java
index 860868d9..07ca4178 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java
@@ -1,320 +1,320 @@
-/*
- * 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.servicelayer.session.impl;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Objects;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-import org.apache.commons.lang3.BooleanUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Service;
-
-import ch.ethz.seb.sebserver.gbl.api.APIMessage;
-import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
-import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
-import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
-import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
-import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
-import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
-import ch.ethz.seb.sebserver.gbl.util.Result;
-import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
-import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
-import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
-import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
-
-@Lazy
-@Service
-@WebServiceProfile
-public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
-
- private static final Logger log = LoggerFactory.getLogger(ExamConfigUpdateServiceImpl.class);
-
- private final ExamDAO examDAO;
- private final ConfigurationDAO configurationDAO;
- private final ExamConfigurationMapDAO examConfigurationMapDAO;
- private final ExamSessionService examSessionService;
- private final ExamUpdateHandler examUpdateHandler;
-
- protected ExamConfigUpdateServiceImpl(
- final ExamDAO examDAO,
- final ConfigurationDAO configurationDAO,
- final ExamConfigurationMapDAO examConfigurationMapDAO,
- final ExamSessionService examSessionService,
- final ExamUpdateHandler examUpdateHandler) {
-
- this.examDAO = examDAO;
- this.configurationDAO = configurationDAO;
- this.examConfigurationMapDAO = examConfigurationMapDAO;
- this.examSessionService = examSessionService;
- this.examUpdateHandler = examUpdateHandler;
- }
-
- // processing:
- // check running exam integrity (No running exam with active SEB client-connection available)
- // if OK, create an update-id and for each exam, create an update-lock on DB (This also prevents new SEB client connection attempts during update)
- // check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
- // store the new configuration values (into history) so that they take effect
- // generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
- // evict each Exam from cache and release the update-lock on DB
- @Override
- public Result> processExamConfigurationChange(final Long configurationNodeId) {
-
- final String updateId = this.examUpdateHandler.createUpdateId();
-
- if (log.isDebugEnabled()) {
- log.debug("Process SEB Exam Configuration update for: {} with update-id {}",
- configurationNodeId,
- updateId);
- }
-
- return Result.tryCatch(() -> {
-
- // check running exam integrity (No running exam with active SEB client-connection available)
- final Collection examIdsFirstCheck = checkRunningExamIntegrity(configurationNodeId)
- .getOrThrow();
-
- if (log.isDebugEnabled()) {
- log.debug("Involved exams on fist integrity check: {}", examIdsFirstCheck);
- }
-
- // if OK, create an update-lock on DB (This also prevents new SEB client connection attempts during update)
- final Collection exams = lockForUpdate(examIdsFirstCheck, updateId)
- .stream()
- .map(Result::getOrThrow)
- .collect(Collectors.toList());
-
- final Collection examsIds = exams
- .stream()
- .map(Exam::getId)
- .collect(Collectors.toList());
-
- if (log.isDebugEnabled()) {
- log.debug("Update-Lock successfully placed for all involved exams: {}", examsIds);
- }
-
- // check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
- final Collection examIdsSecondCheck = checkRunningExamIntegrity(configurationNodeId)
- .getOrThrow();
-
- checkIntegrityDoubleCheck(
- examIdsFirstCheck,
- examIdsSecondCheck);
-
- if (log.isDebugEnabled()) {
- log.debug("Involved exams on second integrity check: {}", examIdsSecondCheck);
- }
-
- // store the new configuration values (into history) so that they take effect
- final Configuration configuration = this.configurationDAO
- .saveToHistory(configurationNodeId)
- .getOrThrow();
-
- if (log.isDebugEnabled()) {
- log.debug("Successfully save SEB Exam Configuration: {}", configuration);
- }
-
- // generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
- for (final Exam exam : exams) {
- if (exam.getStatus() == ExamStatus.RUNNING && BooleanUtils.isTrue(exam.lmsSebRestriction)) {
- this.examUpdateHandler
- .getSebRestrictionService()
- .applySebClientRestriction(exam)
- .onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t));
- }
- }
-
- // evict each Exam from cache and release the update-lock on DB
- for (final Exam exam : exams) {
- this.examSessionService
- .flushCache(exam)
- .onError(t -> log.error("Failed to flush Exam from cache: {}", exam, t));
- }
-
- // release the update-locks on involved exams
- for (final Long examId : examIdsFirstCheck) {
- this.examDAO.releaseLock(examId, updateId)
- .onError(t -> log.error("Failed to release lock for Exam: {}", examId, t));
- }
-
- return examIdsFirstCheck;
- })
- .onError(t -> this.examDAO.forceUnlockAll(updateId));
- }
-
- @Override
- public Result processExamConfigurationMappingChange(
- final ExamConfigurationMap mapping,
- final Function> changeAction) {
-
- return this.examDAO.byPK(mapping.examId)
- .map(exam -> {
-
- // if the exam is not currently running just apply the action
- if (exam.status != ExamStatus.RUNNING) {
- return changeAction
- .apply(mapping)
- .getOrThrow();
- }
-
- // if the exam is running...
- final String updateId = this.examUpdateHandler.createUpdateId();
- if (log.isDebugEnabled()) {
- log.debug(
- "Process SEB Exam Configuration mapping update for: {} with update-id {}",
- mapping,
- updateId);
- }
-
- // check if there are no active client connections for this exam
- checkActiveClientConnections(exam);
-
- // lock the exam
- this.examDAO
- .placeLock(exam.id, updateId)
- .getOrThrow();
-
- // check again if there are no new active client connections in the meantime
- checkActiveClientConnections(exam);
-
- // apply the referenced change action. On error the change is rolled back and
- // this processing returns immediately with the error
- final T result = changeAction
- .apply(mapping)
- .onError(t -> log.error("Fauled to save exam configuration: {}",
- mapping.configurationNodeId))
- .getOrThrow();
-
- // update seb client restriction if the feature is activated for the exam
- if (exam.lmsSebRestriction) {
- this.examUpdateHandler
- .getSebRestrictionService()
- .applySebClientRestriction(exam)
- .onError(t -> log.error(
- "Failed to update SEB Client restriction for Exam: {}",
- exam,
- t));
- }
-
- // flush the exam cache. If there was an error during flush, it is logged but this process goes on
- // and the saved changes are not rolled back
- this.examSessionService
- .flushCache(exam)
- .onError(t -> log.error("Failed to flush cache for exam: {}", exam));
-
- // release the exam lock
- this.examDAO
- .releaseLock(exam.id, updateId)
- .onError(t -> log.error("Failed to release lock for exam: {}", exam));
-
- return result;
- })
- .onError(t -> this.examDAO.forceUnlock(mapping.examId));
-
- }
-
- private void checkActiveClientConnections(final Exam exam) {
- if (this.examSessionService.hasActiveSebClientConnections(exam.id)) {
- throw new APIMessage.APIMessageException(
- ErrorMessage.INTEGRITY_VALIDATION,
- "Integrity violation: There are currently active SEB Client connection.");
- }
- }
-
- @Override
- public void forceReleaseUpdateLocks(final Long configurationId) {
-
- log.warn(" **** Force release of update-locks for all exams that are related to configuration: {}",
- configurationId);
-
- try {
- final Configuration config = this.configurationDAO
- .byPK(configurationId)
- .getOrThrow();
-
- final Collection involvedExams = this.examConfigurationMapDAO
- .getExamIdsForConfigNodeId(config.configurationNodeId)
- .getOrThrow();
-
- final Collection examsIds = forceReleaseUpdateLocks(involvedExams)
- .stream()
- .map(Result::getOrThrow)
- .collect(Collectors.toList());
-
- log.info("Successfully released update-locks for exams: {}", examsIds);
-
- } catch (final Exception e) {
- log.error("Failed to release update-locks for exam(s)", e);
- }
-
- }
-
- @Override
- public Collection> forceReleaseUpdateLocks(final Collection examIds) {
- return examIds
- .stream()
- .map(this.examDAO::forceUnlock)
- .collect(Collectors.toList());
- }
-
- @Override
- public Result> checkRunningExamIntegrity(final Long configurationNodeId) {
- final Collection involvedExams = this.examConfigurationMapDAO
- .getExamIdsForConfigNodeId(configurationNodeId)
- .getOrThrow();
-
- if (involvedExams == null || involvedExams.isEmpty()) {
- return Result.of(Collections.emptyList());
- }
-
- // check if the configuration is attached to a running exams with active client connections
- final long activeConnections = involvedExams
- .stream()
- .flatMap(examId -> {
- return this.examSessionService.getConnectionData(examId, Objects::nonNull)
- .getOrThrow()
- .stream();
- })
- .filter(ExamSessionService::isActiveConnection)
- .count();
-
- // if we have active SEB client connection on any running exam that
- // is involved within the specified configuration change, the change is denied
- if (activeConnections > 0) {
- return Result.ofError(new APIMessage.APIMessageException(
- ErrorMessage.INTEGRITY_VALIDATION,
- "Integrity violation: There are currently active SEB Client connection."));
- } else {
- // otherwise we return the involved identifiers exams to further processing
- return Result.of(involvedExams);
- }
- }
-
- private void checkIntegrityDoubleCheck(
- final Collection examIdsFirstCheck,
- final Collection examIdsSecondCheck) {
-
- if (examIdsFirstCheck.size() != examIdsSecondCheck.size()) {
- throw new IllegalStateException("Running Exam integrity check missmatch. examIdsFirstCheck: "
- + examIdsFirstCheck + " examIdsSecondCheck: " + examIdsSecondCheck);
- }
- }
-
- private Collection> lockForUpdate(final Collection examIds, final String update) {
- return examIds.stream()
- .map(id -> this.examDAO.placeLock(id, update))
- .collect(Collectors.toList());
- }
-
-}
+/*
+ * 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.servicelayer.session.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.BooleanUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+
+import ch.ethz.seb.sebserver.gbl.api.APIMessage;
+import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
+import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
+import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
+import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
+import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
+import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
+
+@Lazy
+@Service
+@WebServiceProfile
+public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
+
+ private static final Logger log = LoggerFactory.getLogger(ExamConfigUpdateServiceImpl.class);
+
+ private final ExamDAO examDAO;
+ private final ConfigurationDAO configurationDAO;
+ private final ExamConfigurationMapDAO examConfigurationMapDAO;
+ private final ExamSessionService examSessionService;
+ private final ExamUpdateHandler examUpdateHandler;
+
+ protected ExamConfigUpdateServiceImpl(
+ final ExamDAO examDAO,
+ final ConfigurationDAO configurationDAO,
+ final ExamConfigurationMapDAO examConfigurationMapDAO,
+ final ExamSessionService examSessionService,
+ final ExamUpdateHandler examUpdateHandler) {
+
+ this.examDAO = examDAO;
+ this.configurationDAO = configurationDAO;
+ this.examConfigurationMapDAO = examConfigurationMapDAO;
+ this.examSessionService = examSessionService;
+ this.examUpdateHandler = examUpdateHandler;
+ }
+
+ // processing:
+ // check running exam integrity (No running exam with active SEB client-connection available)
+ // if OK, create an update-id and for each exam, create an update-lock on DB (This also prevents new SEB client connection attempts during update)
+ // check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
+ // store the new configuration values (into history) so that they take effect
+ // generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
+ // evict each Exam from cache and release the update-lock on DB
+ @Override
+ public Result> processExamConfigurationChange(final Long configurationNodeId) {
+
+ final String updateId = this.examUpdateHandler.createUpdateId();
+
+ if (log.isDebugEnabled()) {
+ log.debug("Process SEB Exam Configuration update for: {} with update-id {}",
+ configurationNodeId,
+ updateId);
+ }
+
+ return Result.tryCatch(() -> {
+
+ // check running exam integrity (No running exam with active SEB client-connection available)
+ final Collection examIdsFirstCheck = checkRunningExamIntegrity(configurationNodeId)
+ .getOrThrow();
+
+ if (log.isDebugEnabled()) {
+ log.debug("Involved exams on fist integrity check: {}", examIdsFirstCheck);
+ }
+
+ // if OK, create an update-lock on DB (This also prevents new SEB client connection attempts during update)
+ final Collection exams = lockForUpdate(examIdsFirstCheck, updateId)
+ .stream()
+ .map(Result::getOrThrow)
+ .collect(Collectors.toList());
+
+ final Collection examsIds = exams
+ .stream()
+ .map(Exam::getId)
+ .collect(Collectors.toList());
+
+ if (log.isDebugEnabled()) {
+ log.debug("Update-Lock successfully placed for all involved exams: {}", examsIds);
+ }
+
+ // check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
+ final Collection examIdsSecondCheck = checkRunningExamIntegrity(configurationNodeId)
+ .getOrThrow();
+
+ checkIntegrityDoubleCheck(
+ examIdsFirstCheck,
+ examIdsSecondCheck);
+
+ if (log.isDebugEnabled()) {
+ log.debug("Involved exams on second integrity check: {}", examIdsSecondCheck);
+ }
+
+ // store the new configuration values (into history) so that they take effect
+ final Configuration configuration = this.configurationDAO
+ .saveToHistory(configurationNodeId)
+ .getOrThrow();
+
+ if (log.isDebugEnabled()) {
+ log.debug("Successfully save SEB Exam Configuration: {}", configuration);
+ }
+
+ // generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
+ for (final Exam exam : exams) {
+ if (exam.getStatus() == ExamStatus.RUNNING && BooleanUtils.isTrue(exam.lmsSebRestriction)) {
+ this.examUpdateHandler
+ .getSebRestrictionService()
+ .applySebClientRestriction(exam)
+ .onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t));
+ }
+ }
+
+ // evict each Exam from cache and release the update-lock on DB
+ for (final Exam exam : exams) {
+ this.examSessionService
+ .flushCache(exam)
+ .onError(t -> log.error("Failed to flush Exam from cache: {}", exam, t));
+ }
+
+ // release the update-locks on involved exams
+ for (final Long examId : examIdsFirstCheck) {
+ this.examDAO.releaseLock(examId, updateId)
+ .onError(t -> log.error("Failed to release lock for Exam: {}", examId, t));
+ }
+
+ return examIdsFirstCheck;
+ })
+ .onError(t -> this.examDAO.forceUnlockAll(updateId));
+ }
+
+ @Override
+ public Result processExamConfigurationMappingChange(
+ final ExamConfigurationMap mapping,
+ final Function> changeAction) {
+
+ return this.examDAO.byPK(mapping.examId)
+ .map(exam -> {
+
+ // if the exam is not currently running just apply the action
+ if (exam.status != ExamStatus.RUNNING) {
+ return changeAction
+ .apply(mapping)
+ .getOrThrow();
+ }
+
+ // if the exam is running...
+ final String updateId = this.examUpdateHandler.createUpdateId();
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "Process SEB Exam Configuration mapping update for: {} with update-id {}",
+ mapping,
+ updateId);
+ }
+
+ // check if there are no active client connections for this exam
+ checkActiveClientConnections(exam);
+
+ // lock the exam
+ this.examDAO
+ .placeLock(exam.id, updateId)
+ .getOrThrow();
+
+ // check again if there are no new active client connections in the meantime
+ checkActiveClientConnections(exam);
+
+ // apply the referenced change action. On error the change is rolled back and
+ // this processing returns immediately with the error
+ final T result = changeAction
+ .apply(mapping)
+ .onError(t -> log.error("Failed to save exam configuration: {}",
+ mapping.configurationNodeId))
+ .getOrThrow();
+
+ // update seb client restriction if the feature is activated for the exam
+ if (exam.lmsSebRestriction) {
+ this.examUpdateHandler
+ .getSebRestrictionService()
+ .applySebClientRestriction(exam)
+ .onError(t -> log.error(
+ "Failed to update SEB Client restriction for Exam: {}",
+ exam,
+ t));
+ }
+
+ // flush the exam cache. If there was an error during flush, it is logged but this process goes on
+ // and the saved changes are not rolled back
+ this.examSessionService
+ .flushCache(exam)
+ .onError(t -> log.error("Failed to flush cache for exam: {}", exam));
+
+ // release the exam lock
+ this.examDAO
+ .releaseLock(exam.id, updateId)
+ .onError(t -> log.error("Failed to release lock for exam: {}", exam));
+
+ return result;
+ })
+ .onError(t -> this.examDAO.forceUnlock(mapping.examId));
+
+ }
+
+ private void checkActiveClientConnections(final Exam exam) {
+ if (this.examSessionService.hasActiveSebClientConnections(exam.id)) {
+ throw new APIMessage.APIMessageException(
+ ErrorMessage.INTEGRITY_VALIDATION,
+ "Integrity violation: There are currently active SEB Client connection.");
+ }
+ }
+
+ @Override
+ public void forceReleaseUpdateLocks(final Long configurationId) {
+
+ log.warn(" **** Force release of update-locks for all exams that are related to configuration: {}",
+ configurationId);
+
+ try {
+ final Configuration config = this.configurationDAO
+ .byPK(configurationId)
+ .getOrThrow();
+
+ final Collection involvedExams = this.examConfigurationMapDAO
+ .getExamIdsForConfigNodeId(config.configurationNodeId)
+ .getOrThrow();
+
+ final Collection examsIds = forceReleaseUpdateLocks(involvedExams)
+ .stream()
+ .map(Result::getOrThrow)
+ .collect(Collectors.toList());
+
+ log.info("Successfully released update-locks for exams: {}", examsIds);
+
+ } catch (final Exception e) {
+ log.error("Failed to release update-locks for exam(s)", e);
+ }
+
+ }
+
+ @Override
+ public Collection> forceReleaseUpdateLocks(final Collection examIds) {
+ return examIds
+ .stream()
+ .map(this.examDAO::forceUnlock)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Result> checkRunningExamIntegrity(final Long configurationNodeId) {
+ final Collection involvedExams = this.examConfigurationMapDAO
+ .getExamIdsForConfigNodeId(configurationNodeId)
+ .getOrThrow();
+
+ if (involvedExams == null || involvedExams.isEmpty()) {
+ return Result.of(Collections.emptyList());
+ }
+
+ // check if the configuration is attached to a running exams with active client connections
+ final long activeConnections = involvedExams
+ .stream()
+ .flatMap(examId -> {
+ return this.examSessionService.getConnectionData(examId, Objects::nonNull)
+ .getOrThrow()
+ .stream();
+ })
+ .filter(ExamSessionService::isActiveConnection)
+ .count();
+
+ // if we have active SEB client connection on any running exam that
+ // is involved within the specified configuration change, the change is denied
+ if (activeConnections > 0) {
+ return Result.ofError(new APIMessage.APIMessageException(
+ ErrorMessage.INTEGRITY_VALIDATION,
+ "Integrity violation: There are currently active SEB Client connection."));
+ } else {
+ // otherwise we return the involved identifiers exams to further processing
+ return Result.of(involvedExams);
+ }
+ }
+
+ private void checkIntegrityDoubleCheck(
+ final Collection examIdsFirstCheck,
+ final Collection examIdsSecondCheck) {
+
+ if (examIdsFirstCheck.size() != examIdsSecondCheck.size()) {
+ throw new IllegalStateException("Running Exam integrity check missmatch. examIdsFirstCheck: "
+ + examIdsFirstCheck + " examIdsSecondCheck: " + examIdsSecondCheck);
+ }
+ }
+
+ private Collection> lockForUpdate(final Collection examIds, final String update) {
+ return examIds.stream()
+ .map(id -> this.examDAO.placeLock(id, update))
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamConfigurationMappingController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamConfigurationMappingController.java
index bfc46c6d..06631281 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamConfigurationMappingController.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamConfigurationMappingController.java
@@ -1,244 +1,245 @@
-/*
- * 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 org.mybatis.dynamic.sql.SqlTable;
-import org.springframework.http.MediaType;
-import org.springframework.util.MultiValueMap;
-import org.springframework.validation.FieldError;
-import org.springframework.web.bind.annotation.PathVariable;
-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.api.APIMessage;
-import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
-import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
-import ch.ethz.seb.sebserver.gbl.api.EntityType;
-import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
-import ch.ethz.seb.sebserver.gbl.model.Domain;
-import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
-import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
-import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
-import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
-import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
-import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
-import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
-import ch.ethz.seb.sebserver.gbl.util.Pair;
-import ch.ethz.seb.sebserver.gbl.util.Result;
-import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport;
-import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
-import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
-import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
-import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
-import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
-
-@WebServiceProfile
-@RestController
-@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_CONFIGURATION_MAP_ENDPOINT)
-public class ExamConfigurationMappingController extends EntityController {
-
- private final ExamDAO examDao;
- private final ConfigurationNodeDAO configurationNodeDAO;
- private final ExamConfigUpdateService examConfigUpdateService;
- private final ExamSessionService examSessionService;
-
- protected ExamConfigurationMappingController(
- final AuthorizationService authorization,
- final BulkActionService bulkActionService,
- final EntityDAO entityDAO,
- final UserActivityLogDAO userActivityLogDAO,
- final PaginationService paginationService,
- final BeanValidationService beanValidationService,
- final ExamDAO examDao,
- final ConfigurationNodeDAO configurationNodeDAO,
- final ExamConfigUpdateService examConfigUpdateService,
- final ExamSessionService examSessionService) {
-
- super(
- authorization,
- bulkActionService,
- entityDAO,
- userActivityLogDAO,
- paginationService,
- beanValidationService);
-
- this.examDao = examDao;
- this.configurationNodeDAO = configurationNodeDAO;
- this.examConfigUpdateService = examConfigUpdateService;
- this.examSessionService = examSessionService;
- }
-
- @Override
- protected ExamConfigurationMap createNew(final POSTMapper postParams) {
- final Long institutionId = postParams.getLong(API.PARAM_INSTITUTION_ID);
- return new ExamConfigurationMap(institutionId, postParams);
- }
-
- @Override
- protected SqlTable getSQLTableOfEntity() {
- return ExamConfigurationMapRecordDynamicSqlSupport.examConfigurationMapRecord;
- }
-
- @Override
- protected EntityType getGrantEntityType() {
- return EntityType.EXAM;
- }
-
- @Override
- protected GrantEntity toGrantEntity(final ExamConfigurationMap entity) {
- if (entity == null) {
- return null;
- }
-
- return this.examDao
- .byPK(entity.examId)
- .getOrThrow();
- }
-
- @Override
- protected Result checkCreateAccess(final ExamConfigurationMap entity) {
- final GrantEntity grantEntity = toGrantEntity(entity);
- this.authorization.checkWrite(grantEntity);
- return Result.of(entity);
- }
-
- @Override
- protected Result validForCreate(final ExamConfigurationMap entity) {
- return super.validForCreate(entity)
- .map(this::checkConfigurationState)
- .map(this::checkPasswordMatch)
- .map(this::checkNoActiveClientConnections);
- }
-
- @Override
- @RequestMapping(
- method = RequestMethod.POST,
- consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
- produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
- public ExamConfigurationMap create(
- @RequestParam final MultiValueMap allRequestParams,
- @RequestParam(
- name = API.PARAM_INSTITUTION_ID,
- required = true,
- defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
-
- // check modify privilege for requested institution and concrete entityType
- this.checkModifyPrivilege(institutionId);
- final POSTMapper postMap = new POSTMapper(allRequestParams)
- .putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
-
- final ExamConfigurationMap requestModel = this.createNew(postMap);
- return this.checkCreateAccess(requestModel)
- .map(this::checkPasswordMatch)
- .flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
- entity,
- this.entityDAO::createNew))
- .flatMap(this::logCreate)
- .flatMap(this::notifyCreated)
- .getOrThrow();
- }
-
- @Override
- @RequestMapping(
- path = API.MODEL_ID_VAR_PATH_SEGMENT,
- method = RequestMethod.DELETE,
- produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
- public EntityProcessingReport hardDelete(@PathVariable final String modelId) {
-
- return this.entityDAO.byModelId(modelId)
- .flatMap(this::checkWriteAccess)
- .flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
- entity,
- this::bulkDelete))
- .flatMap(this::notifyDeleted)
- .flatMap(pair -> this.logBulkAction(pair.b))
- .getOrThrow();
- }
-
- @Override
- protected Result notifyCreated(final ExamConfigurationMap entity) {
- // update the attached configurations state to "In Use"
- return this.configurationNodeDAO.save(new ConfigurationNode(
- entity.configurationNodeId,
- null,
- null,
- null,
- null,
- null,
- null,
- ConfigurationStatus.IN_USE))
- .map(id -> entity);
-
- }
-
- @Override
- protected Result> notifyDeleted(
- final Pair pair) {
- // update the attached configurations state to "Ready"
- return this.configurationNodeDAO.save(new ConfigurationNode(
- pair.a.configurationNodeId,
- null,
- null,
- null,
- null,
- null,
- null,
- ConfigurationStatus.READY_TO_USE))
- .map(id -> pair);
- }
-
- private ExamConfigurationMap checkPasswordMatch(final ExamConfigurationMap entity) {
- if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
- throw new APIMessageException(APIMessage.fieldValidationError(
- new FieldError(
- Domain.EXAM_CONFIGURATION_MAP.TYPE_NAME,
- PasswordChange.ATTR_NAME_PASSWORD,
- "examConfigMapping:confirm_encrypt_secret:password.mismatch")));
- }
-
- return entity;
- }
-
- private ExamConfigurationMap checkConfigurationState(final ExamConfigurationMap entity) {
- final ConfigurationStatus status;
- if (entity.getConfigStatus() != null) {
- status = entity.getConfigStatus();
- } else {
- status = this.configurationNodeDAO.byPK(entity.configurationNodeId)
- .getOrThrow()
- .getStatus();
- }
-
- if (status != ConfigurationStatus.READY_TO_USE) {
- throw new APIMessageException(ErrorMessage.INTEGRITY_VALIDATION.of(
- "Illegal SEB Exam Configuration state"));
- }
-
- return entity;
- }
-
- private ExamConfigurationMap checkNoActiveClientConnections(final ExamConfigurationMap entity) {
- if (this.examSessionService.hasActiveSebClientConnections(entity.examId)) {
- throw new APIMessageException(ErrorMessage.INTEGRITY_VALIDATION.of(
- "The Exam is currently running and has active SEB Client connections"));
- }
-
- return entity;
- }
-
-}
+/*
+ * 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 org.mybatis.dynamic.sql.SqlTable;
+import org.springframework.http.MediaType;
+import org.springframework.util.MultiValueMap;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.annotation.PathVariable;
+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.api.APIMessage;
+import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
+import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
+import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
+import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
+import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
+import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
+import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
+import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
+import ch.ethz.seb.sebserver.gbl.util.Pair;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport;
+import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
+import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
+import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
+import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
+
+@WebServiceProfile
+@RestController
+@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_CONFIGURATION_MAP_ENDPOINT)
+public class ExamConfigurationMappingController extends EntityController {
+
+ private final ExamDAO examDao;
+ private final ConfigurationNodeDAO configurationNodeDAO;
+ private final ExamConfigUpdateService examConfigUpdateService;
+ private final ExamSessionService examSessionService;
+
+ protected ExamConfigurationMappingController(
+ final AuthorizationService authorization,
+ final BulkActionService bulkActionService,
+ final EntityDAO entityDAO,
+ final UserActivityLogDAO userActivityLogDAO,
+ final PaginationService paginationService,
+ final BeanValidationService beanValidationService,
+ final ExamDAO examDao,
+ final ConfigurationNodeDAO configurationNodeDAO,
+ final ExamConfigUpdateService examConfigUpdateService,
+ final ExamSessionService examSessionService) {
+
+ super(
+ authorization,
+ bulkActionService,
+ entityDAO,
+ userActivityLogDAO,
+ paginationService,
+ beanValidationService);
+
+ this.examDao = examDao;
+ this.configurationNodeDAO = configurationNodeDAO;
+ this.examConfigUpdateService = examConfigUpdateService;
+ this.examSessionService = examSessionService;
+ }
+
+ @Override
+ protected ExamConfigurationMap createNew(final POSTMapper postParams) {
+ final Long institutionId = postParams.getLong(API.PARAM_INSTITUTION_ID);
+ return new ExamConfigurationMap(institutionId, postParams);
+ }
+
+ @Override
+ protected SqlTable getSQLTableOfEntity() {
+ return ExamConfigurationMapRecordDynamicSqlSupport.examConfigurationMapRecord;
+ }
+
+ @Override
+ protected EntityType getGrantEntityType() {
+ return EntityType.EXAM;
+ }
+
+ @Override
+ protected GrantEntity toGrantEntity(final ExamConfigurationMap entity) {
+ if (entity == null) {
+ return null;
+ }
+
+ return this.examDao
+ .byPK(entity.examId)
+ .getOrThrow();
+ }
+
+ @Override
+ protected Result checkCreateAccess(final ExamConfigurationMap entity) {
+ final GrantEntity grantEntity = toGrantEntity(entity);
+ this.authorization.checkWrite(grantEntity);
+ return Result.of(entity);
+ }
+
+ @Override
+ protected Result validForCreate(final ExamConfigurationMap entity) {
+ return super.validForCreate(entity)
+ .map(this::checkConfigurationState)
+ .map(this::checkPasswordMatch)
+ .map(this::checkNoActiveClientConnections);
+ }
+
+ @Override
+ @RequestMapping(
+ method = RequestMethod.POST,
+ consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
+ produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+ public ExamConfigurationMap create(
+ @RequestParam final MultiValueMap allRequestParams,
+ @RequestParam(
+ name = API.PARAM_INSTITUTION_ID,
+ required = true,
+ defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
+
+ // check modify privilege for requested institution and concrete entityType
+ this.checkModifyPrivilege(institutionId);
+ final POSTMapper postMap = new POSTMapper(allRequestParams)
+ .putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
+
+ final ExamConfigurationMap requestModel = this.createNew(postMap);
+ return this.checkCreateAccess(requestModel)
+ .flatMap(this::validForCreate)
+ .map(this::checkPasswordMatch)
+ .flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
+ entity,
+ this.entityDAO::createNew))
+ .flatMap(this::logCreate)
+ .flatMap(this::notifyCreated)
+ .getOrThrow();
+ }
+
+ @Override
+ @RequestMapping(
+ path = API.MODEL_ID_VAR_PATH_SEGMENT,
+ method = RequestMethod.DELETE,
+ produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+ public EntityProcessingReport hardDelete(@PathVariable final String modelId) {
+
+ return this.entityDAO.byModelId(modelId)
+ .flatMap(this::checkWriteAccess)
+ .flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
+ entity,
+ this::bulkDelete))
+ .flatMap(this::notifyDeleted)
+ .flatMap(pair -> this.logBulkAction(pair.b))
+ .getOrThrow();
+ }
+
+ @Override
+ protected Result notifyCreated(final ExamConfigurationMap entity) {
+ // update the attached configurations state to "In Use"
+ return this.configurationNodeDAO.save(new ConfigurationNode(
+ entity.configurationNodeId,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ ConfigurationStatus.IN_USE))
+ .map(id -> entity);
+
+ }
+
+ @Override
+ protected Result> notifyDeleted(
+ final Pair pair) {
+ // update the attached configurations state to "Ready"
+ return this.configurationNodeDAO.save(new ConfigurationNode(
+ pair.a.configurationNodeId,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ ConfigurationStatus.READY_TO_USE))
+ .map(id -> pair);
+ }
+
+ private ExamConfigurationMap checkPasswordMatch(final ExamConfigurationMap entity) {
+ if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
+ throw new APIMessageException(APIMessage.fieldValidationError(
+ new FieldError(
+ Domain.EXAM_CONFIGURATION_MAP.TYPE_NAME,
+ PasswordChange.ATTR_NAME_PASSWORD,
+ "examConfigMapping:confirm_encrypt_secret:password.mismatch")));
+ }
+
+ return entity;
+ }
+
+ private ExamConfigurationMap checkConfigurationState(final ExamConfigurationMap entity) {
+ final ConfigurationStatus status;
+ if (entity.getConfigStatus() != null) {
+ status = entity.getConfigStatus();
+ } else {
+ status = this.configurationNodeDAO.byPK(entity.configurationNodeId)
+ .getOrThrow()
+ .getStatus();
+ }
+
+ if (status != ConfigurationStatus.READY_TO_USE) {
+ throw new APIMessageException(ErrorMessage.INTEGRITY_VALIDATION.of(
+ "Illegal SEB Exam Configuration state"));
+ }
+
+ return entity;
+ }
+
+ private ExamConfigurationMap checkNoActiveClientConnections(final ExamConfigurationMap entity) {
+ if (this.examSessionService.hasActiveSebClientConnections(entity.examId)) {
+ throw new APIMessageException(ErrorMessage.INTEGRITY_VALIDATION.of(
+ "The Exam is currently running and has active SEB Client connections"));
+ }
+
+ return entity;
+ }
+
+}
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index 79a87572..ba78cba0 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -9,7 +9,7 @@ sebserver.overall.about.markup=Please sign in again
+sebserver.logout.invalid-session.message=You have been signed out because of a user session invalidation.
Please sign in again
sebserver.login.password.change=Information
sebserver.login.password.change.success=The password was successfully changed. Please sign in with your new password
@@ -139,11 +140,11 @@ sebserver.institution.list.actions=
sebserver.institution.list.empty=No institution has been found. Please adapt the filter or create a new institution
sebserver.institution.list.title=Institutions
sebserver.institution.list.column.name=Name
-sebserver.institution.list.column.name.tooltip=The name of the institution.Use the filter above to narrow down a specific name.{0}
+sebserver.institution.list.column.name.tooltip=The name of the institution.
Use the filter above to narrow down a specific name.
{0}
sebserver.institution.list.column.urlSuffix=URL Suffix
-sebserver.institution.list.column.urlSuffix.tooltip=The URL suffix to the institutional login page.Use the filter above to narrow down a specific URL suffix.{0}
+sebserver.institution.list.column.urlSuffix.tooltip=The URL suffix to the institutional login page.
Use the filter above to narrow down a specific URL suffix.
{0}
sebserver.institution.list.column.active=Status
-sebserver.institution.list.column.active.tooltip=The activity of the institution.Use the filter above to specify the activity.{0}
+sebserver.institution.list.column.active.tooltip=The activity of the institution.
Use the filter above to specify the activity.
{0}
sebserver.institution.action.list=Institution
sebserver.institution.action.form=Institution
@@ -164,9 +165,9 @@ sebserver.institution.form.title=Institution
sebserver.institution.form.name=Name
sebserver.institution.form.name.tooltip=The name of the institution
sebserver.institution.form.urlSuffix=URL Suffix
-sebserver.institution.form.urlSuffix.tooltip=The URL suffix to the institutional login page. Institutional URL is: http(s):///
+sebserver.institution.form.urlSuffix.tooltip=The URL suffix to the institutional login page.
Institutional URL is: http(s):///
sebserver.institution.form.logoImage=Logo Image
-sebserver.institution.form.logoImage.tooltip=The Image that is shown as a logo in the specified institutional login page.In edit mode, use the arrow sign to open a upload dialog.
+sebserver.institution.form.logoImage.tooltip=The Image that is shown as a logo in the specified institutional login page.
In edit mode, use the arrow sign to open a upload dialog.
sebserver.institution.form.logoImage.unsupportedFileType=The selected file is not supported. Supported are: PNG and JPG
@@ -176,27 +177,27 @@ sebserver.institution.form.logoImage.unsupportedFileType=The selected file is no
sebserver.useraccount.list.actions=
sebserver.useraccount.role.SEB_SERVER_ADMIN=SEB Server Administrator
-sebserver.useraccount.role.SEB_SERVER_ADMIN.tooltip=A SEB server administrator has overall read privilegesand is able to create new institutions and user accounts
+sebserver.useraccount.role.SEB_SERVER_ADMIN.tooltip=A SEB server administrator has overall read privileges
and is able to create new institutions and user accounts
sebserver.useraccount.role.INSTITUTIONAL_ADMIN=Institutional Administrator
-sebserver.useraccount.role.INSTITUTIONAL_ADMIN.tooltip=An institutional administrator has overall read privileges on the assigned institutionand is able to edit the institution and create new user accounts for the institution.Furthermore an institutional administrator is able to create new LMS bindings and SEB client configurations for the institution.
+sebserver.useraccount.role.INSTITUTIONAL_ADMIN.tooltip=An institutional administrator has overall read privileges on the assigned institution
and is able to edit the institution and create new user accounts for the institution.
Furthermore an institutional administrator is able to create new LMS bindings and SEB client configurations for the institution.
sebserver.useraccount.role.EXAM_ADMIN=Exam Administrator
-sebserver.useraccount.role.EXAM_ADMIN.tooltip=An exam administrator has overall read privileges for the institution but cannot see or manage other user accounts.An exam administrator is able to create new SEB configurations and import and setup exams.
+sebserver.useraccount.role.EXAM_ADMIN.tooltip=An exam administrator has overall read privileges for the institution but cannot see or manage other user accounts.
An exam administrator is able to create new SEB configurations and import and setup exams.
sebserver.useraccount.role.EXAM_SUPPORTER=Exam Supporter
-sebserver.useraccount.role.EXAM_SUPPORTER.tooltip=An exam supporter can only see and edit the own user account and monitor exams for that he/she was attached by an exam administrator.
+sebserver.useraccount.role.EXAM_SUPPORTER.tooltip=An exam supporter can only see and edit the own user account
and monitor exams for that he/she was attached by an exam administrator.
sebserver.useraccount.list.empty=No user account has been found. Please adapt the filter or create a new user account
sebserver.useraccount.list.title=User Accounts
sebserver.useraccount.list.column.institution=Institution
-sebserver.useraccount.list.column.institution.tooltip=The institution of the user account.Use the filter above to specify the institution.{0}
+sebserver.useraccount.list.column.institution.tooltip=The institution of the user account.
Use the filter above to specify the institution.
{0}
sebserver.useraccount.list.column.name=First Name
-sebserver.useraccount.list.column.name.tooltip=The first name of the user.Use the filter above to narrow down a specific first name.{0}
+sebserver.useraccount.list.column.name.tooltip=The first name of the user.
Use the filter above to narrow down a specific first name.
{0}
sebserver.useraccount.list.column.username=User Name
-sebserver.useraccount.list.column.username.tooltip=The internal user name of the user.Use the filter above to narrow down a specific user name.{0}
+sebserver.useraccount.list.column.username.tooltip=The internal user name of the user.
Use the filter above to narrow down a specific user name.
{0}
sebserver.useraccount.list.column.email=Mail
-sebserver.useraccount.list.column.email.tooltip=The e-mail address of the user.Use the filter above to narrow down a specific e-mail address.{0}
+sebserver.useraccount.list.column.email.tooltip=The e-mail address of the user.
Use the filter above to narrow down a specific e-mail address.
{0}
sebserver.useraccount.list.column.language=Language
sebserver.useraccount.list.column.active=Active
-sebserver.useraccount.list.column.active.tooltip=The activity of the user.Use the filter above to specify the activity.{0}
+sebserver.useraccount.list.column.active.tooltip=The activity of the user.
Use the filter above to specify the activity.
{0}
sebserver.useraccount.action.list=User Account
sebserver.useraccount.action.form=User Account of {0}
@@ -231,9 +232,9 @@ sebserver.useraccount.form.mail.tooltip=The e-mail address of the user.
sebserver.useraccount.form.language=Language
sebserver.useraccount.form.language.tooltip=The users language.
sebserver.useraccount.form.timezone=Time Zone
-sebserver.useraccount.form.timezone.tooltip=The time-zone of the user.Note that this also defines the exact time and date that is displayed to the user.
+sebserver.useraccount.form.timezone.tooltip=The time-zone of the user.
Note that this also defines the exact time and date that is displayed to the user.
sebserver.useraccount.form.roles=User Roles
-sebserver.useraccount.form.roles.tooltip=Select the roles for the user.A user can have more then one role but must have at least one.Please use the tooltip on the role name for more information about a specific role.
+sebserver.useraccount.form.roles.tooltip=Select the roles for the user.
A user can have more then one role but must have at least one.
Please use the tooltip on the role name for more information about a specific role.
sebserver.useraccount.form.password=Password
sebserver.useraccount.form.password.tooltip=The password of the user account
sebserver.useraccount.form.password.confirm=Confirm Password
@@ -257,9 +258,13 @@ sebserver.lmssetup.list.action.no.modify.privilege=No Access: A LMS Setup from o
sebserver.lmssetup.list.empty=No LMS Setup has been found. Please adapt the filter or create a new LMS Setup
sebserver.lmssetup.list.title=Learning Management System Setups
sebserver.lmssetup.list.column.institution=Institution
+sebserver.lmssetup.list.column.institution.tooltip=The institution of the LMS setup.
Use the filter above to specify the institution.
{0}
sebserver.lmssetup.list.column.name=Name
+sebserver.lmssetup.list.column.name.tooltip=The name of the LMS setup.
Use the filter above to narrow down a specific LMS by name.
{0}
sebserver.lmssetup.list.column.type=LMS Type
+sebserver.lmssetup.list.column.type.tooltip=The type of the LMS.
Use the filter above to specify the LMS type.
{0}
sebserver.lmssetup.list.column.active=Active
+sebserver.lmssetup.list.column.active.tooltip=The activity of the LMS Setup.
Use the filter above to specify the activity.
{0}
sebserver.lmssetup.action.list=LMS Connection Settings
sebserver.lmssetup.action.form=LMS Setup
@@ -270,7 +275,7 @@ sebserver.lmssetup.action.modify=Edit
sebserver.lmssetup.action.savetest=Test And Save
sebserver.lmssetup.action.testsave=Test And Save
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.tokenRequestError=The API access was denied: {0}
Please check the LMS connection details.
sebserver.lmssetup.action.test.quizRequestError=Unable to request courses or quizzes from the course API of the LMS. {0}
sebserver.lmssetup.action.test.quizRestrictionError=Unable to access course restriction API of the LMS. {0}
sebserver.lmssetup.action.test.missingParameter=There is one or more missing connection parameter.
Please check the connection parameter for this LMS Setup
@@ -285,17 +290,25 @@ sebserver.lmssetup.info.pleaseSelect=Please select first a LMS Setup from the li
sebserver.lmssetup.form.title=Learning Management System Setup
sebserver.lmssetup.form.title.new=Add Learning Management System Setup
sebserver.lmssetup.form.institution=Institution
+sebserver.lmssetup.form.institution.tooltip=The institution where the LMS setup belongs to
sebserver.lmssetup.form.name=Name
+sebserver.lmssetup.form.name.tooltip=The name of the LMS setup
sebserver.lmssetup.form.type=Type
-sebserver.lmssetup.form.clientname.seb=SEB Auth. Name
-sebserver.lmssetup.form.secret.seb=SEB Auth. Password
+sebserver.lmssetup.form.type.tooltip=The type of the LMS Setup.
sebserver.lmssetup.form.url=LMS Server Address
+sebserver.lmssetup.form.url.tooltip=The server URL to the specific LMS server.
This should point to the main- or root-address of the LMS server
sebserver.lmssetup.form.clientname.lms=LMS Server Username
+sebserver.lmssetup.form.clientname.lms.tooltip=The client name of the API client access to the LMS.
This is usually provided by an LMS administrator that has created a API access account for SEB Server binding within the LMS server.
sebserver.lmssetup.form.secret.lms=LMS Server Password
+sebserver.lmssetup.form.secret.lms.tooltip=The secret or password of the API client access to the LMS.
This is usually provided by an LMS administrator that has created a API access account for SEB Server binding within the LMS server.
sebserver.lmssetup.form.proxy=Proxy
+sebserver.lmssetup.form.proxy.tooltip=The proxy details of a proxy is needed to connect to a LMS server
sebserver.lmssetup.form.proxy.host=Proxy Host
+sebserver.lmssetup.form.proxy.host.tooltip=The server name of the proxy host to connect to
sebserver.lmssetup.form.proxy.port=Proxy Port
+sebserver.lmssetup.form.proxy.port.tooltip=The proxy server port to connect to
sebserver.lmssetup.form.proxy.auth-credentials=Proxy Name/Password
+sebserver.lmssetup.form.proxy.auth-credentials.tooltip=The proxy authentication credentials (name and password)
to authenticate the connection within the proxy server
################################
# Quiz Discovery
@@ -306,10 +319,15 @@ sebserver.quizdiscovery.list.actions=
sebserver.quizdiscovery.list.title=Quizzes
sebserver.quizdiscovery.list.empty=No Quiz has been found. Please adapt the filter or create a new LMS Setup
sebserver.quizdiscovery.list.column.institution=Institution
+sebserver.quizdiscovery.list.column.institution.tooltip=The institution of the LMS setup that defines LMS of the quiz.
Use the filter above to specify the institution.
{0}
sebserver.quizdiscovery.list.column.lmssetup=LMS
+sebserver.quizdiscovery.list.column.lmssetup.tooltip=The LMS setup that defines the LMS of the quiz
Use the filter above to specify the LMS setup.
{0}
sebserver.quizdiscovery.list.column.name=Name
+sebserver.quizdiscovery.list.column.name.tooltip=The name of the quiz.
Use the filter above to narrow down a specific quiz name.
{0}
sebserver.quizdiscovery.list.column.starttime=Start Time {0}
+sebserver.quizdiscovery.list.column.starttime.tooltip=The start time of the quiz.
Use the filter above to set a specific from date.
{0}
sebserver.quizdiscovery.list.column.endtime=End Time {0}
+sebserver.quizdiscovery.list.column.endtime.tooltip=The end time of the quiz.
{0}
sebserver.quizdiscovery.info.pleaseSelect=Please select first a Quiz from the list
sebserver.quizdiscovery.action.list=LMS Exam Discovery
@@ -318,16 +336,32 @@ sebserver.quizdiscovery.quiz.import.out.dated=The Selected Quiz is is already fi
sebserver.quizdiscovery.action.details=Show Details
sebserver.quizdiscovery.quiz.details.title=Quiz Details
+sebserver.quizdiscovery.quiz.details.institution=Institution
+sebserver.quizdiscovery.quiz.details.institution.tooltip=The institution of the LMS setup that defines the LMS of the quiz.
+sebserver.quizdiscovery.quiz.details.lmssetup=LMS Setup
+sebserver.quizdiscovery.quiz.details.lmssetup.tooltip=The LMS setup that defines the LMS of the quiz.
+sebserver.quizdiscovery.quiz.details.name=Name
+sebserver.quizdiscovery.quiz.details.name.tooltip=The name of the quiz.
This name is defined on the corresponding LMS
sebserver.quizdiscovery.quiz.details.description=Description
+sebserver.quizdiscovery.quiz.details.description.tooltip=The description of the quiz.
This description is defined on the corresponding LMS
sebserver.quizdiscovery.quiz.details.starttime=Start Time
+sebserver.quizdiscovery.quiz.details.starttime.tooltip=The start time of the quiz.
This time is set on the corresponding LMS
sebserver.quizdiscovery.quiz.details.endtime=End Time
+sebserver.quizdiscovery.quiz.details.endtime.tooltip=The end time of the quiz.
This time is set on the corresponding LMS
sebserver.quizdiscovery.quiz.details.url=Start URL
+sebserver.quizdiscovery.quiz.details.url.tooltip=The start URL on the LMS for the quiz.
This is defined by the LMS setup and the quiz URL
sebserver.quizdiscovery.quiz.details.additional.timecreated=Creation Time
+sebserver.quizdiscovery.quiz.details.additional.timecreated.tooltip=The time when the quiz was first created
This time is defined by the corresponding LMS
sebserver.quizdiscovery.quiz.details.additional.course_shortname=Short Name
+sebserver.quizdiscovery.quiz.details.additional.course_shortname.tooltip=The short name of the quiz
This is defined on the corresponding LMS
sebserver.quizdiscovery.quiz.details.additional.course_fullname=Full Name
+sebserver.quizdiscovery.quiz.details.additional.course_fullname.tooltip=The full name of the quiz
This is defined on the corresponding LMS
sebserver.quizdiscovery.quiz.details.additional.course_displayname=Display Name
+sebserver.quizdiscovery.quiz.details.additional.course_displayname.tooltip=The display name of the quiz
This is defined on the corresponding LMS
sebserver.quizdiscovery.quiz.details.additional.course_summary=Summary
+sebserver.quizdiscovery.quiz.details.additional.course_summary.tooltip=The summary of the quiz
This is defined on the corresponding LMS
sebserver.quizdiscovery.quiz.details.additional.timelimit=Time Limit
+sebserver.quizdiscovery.quiz.details.additional.timelimit.toolitp=The time limit of the quiz
This is defined on the corresponding LMS
################################
# Exam
@@ -336,10 +370,15 @@ sebserver.quizdiscovery.quiz.details.additional.timelimit=Time Limit
sebserver.exam.list.actions=
sebserver.exam.list.title=Exams
sebserver.exam.list.column.institution=Institution
+sebserver.exam.list.column.institution.tooltip=The institution of the LMS setup that defines LMS of the exam.
Use the filter above to specify the institution.
{0}
sebserver.exam.list.column.lmssetup=LMS
+sebserver.exam.list.column.lmssetup.tooltip=The LMS setup that defines the LMS of the exam
Use the filter above to specify the LMS setup.
{0}
sebserver.exam.list.column.name=Name
+sebserver.exam.list.column.name.tooltip=The name of the exam.
Use the filter above to narrow down a specific exam name.
{0}
sebserver.exam.list.column.starttime=Start Time {0}
+sebserver.exam.list.column.starttime.tooltip=The start time of the exam.
Use the filter above to set a specific from date.
{0}
sebserver.exam.list.column.type=Type
+sebserver.exam.list.column.type.tooltip=The type of the exam.
Use the filter above to set a specific exam type.
{0}
sebserver.exam.list.empty=No Exam has been found. Please adapt the filter or import one from Quiz
sebserver.exam.list.modify.out.dated=Finished exams cannot be modified.
@@ -369,43 +408,73 @@ sebserver.exam.info.pleaseSelect=Please select first an Exam from the list
sebserver.exam.form.title.import=Import Exam
sebserver.exam.form.title=Exam
sebserver.exam.form.lmssetup=LMS Setup
+sebserver.exam.form.lmssetup.tooltip=The LMS setup that defines the LMS of the exam.
sebserver.exam.form.quizid=Quiz Identifier
+sebserver.exam.form.quizid.tooltip=The identifier that identifies the quiz of the exam on the corresponding LMS
sebserver.exam.form.quizurl=Quiz URL
+sebserver.exam.form.quizurl.tooltip=The direct URL link to the quiz/exam on the LMS
sebserver.exam.form.name=Name
+sebserver.exam.form.name.tooltip=The name of the exam.
This name is defined on the corresponding LMS
sebserver.exam.form.description=Description
+sebserver.exam.form.description.tooltip=The description of the exam.
This description is defined on the corresponding LMS
sebserver.exam.form.starttime=Start Time
+sebserver.exam.form.starttime.tooltip=The start time of the exam.
This time is set on the corresponding LMS
sebserver.exam.form.endtime=End Time
+sebserver.exam.form.endtime.tooltip=The end time of the exam.
This time is set on the corresponding LMS
sebserver.exam.form.status=Status
+sebserver.exam.form.status.tooltip=The current status for the exam.
Either "Up Coming" for an exam that has not yet been started,
"Running" for an exam that is currently running
or "Finished" for an exam that has already been finished yet
sebserver.exam.form.type=Exam Type
+sebserver.exam.form.type.tooltip=The type of the exam.
This has only descriptive character for now and can be used to categorise exams within a type
sebserver.exam.form.supporter=Exam Supporter
-sebserver.exam.form.supporter.action.add=Add as supporter for this exam
-sebserver.exam.form.supporter.action.remove=Remove supporter
+sebserver.exam.form.supporter.tooltip=A list of users that are allowed to support this exam.
To add a user in edit mode click into the field on right-hand and start typing the first letters of the username.
A filtered choice will drop down. Click on a specific username on the drop-down to add the user to the list.
To remove a user from the list, just double-click the username on the list.
sebserver.exam.form.sebrestriction.title=SEB Restriction Details
+sebserver.exam.form.sebrestriction.info=Info
+sebserver.exam.form.sebrestriction.info-text=A detailed description of the SEB restriction can be found within this page:
https://seb-openedx.readthedocs.io/en/latest/usage.html
sebserver.exam.form.sebrestriction.configKeys=Config Keys
+sebserver.exam.form.sebrestriction.configKeys.tooltip=A comma-separated list of SEB Config Keys that are automatically generated from the attached SEB exam configurations
and are checked by the LMS for the restricted SEB access for every request
sebserver.exam.form.sebrestriction.browserExamKeys=Browser Exam Keys
+sebserver.exam.form.sebrestriction.browserExamKeys.tooltip=A comma-separated list of SEB Browser Exam Keys
that are checked by the LMS for the restricted SEB access for every request
sebserver.exam.form.sebrestriction.WHITELIST_PATHS=Component White-List
+sebserver.exam.form.sebrestriction.WHITELIST_PATHS.tooltip=Grant no-restriction to each of the given Open edX path components by select them for white-list.
sebserver.exam.form.sebrestriction.BLACKLIST_CHAPTERS=Chapters Black-List
+sebserver.exam.form.sebrestriction.BLACKLIST_CHAPTERS.tooltip=Explicitly restrict a course chapter by adding the course-chapter-identifier to this comma-separated list
sebserver.exam.form.sebrestriction.PERMISSION_COMPONENTS=Permissions
+sebserver.exam.form.sebrestriction.PERMISSION_COMPONENTS.tooltip=Define the additional SEB restriction permissions
sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED=User Banning
+sebserver.exam.form.sebrestriction.USER_BANNING_ENABLED.tooltip=Indicates whether the user of a restricted access shall be banned on authentication failure or not.
sebserver.exam.form.sebrestriction.whiteListPaths.ABOUT=About
+sebserver.exam.form.sebrestriction.whiteListPaths.ABOUT.tooltip=The "About" section of the Open edX course
sebserver.exam.form.sebrestriction.whiteListPaths.COURSE_OUTLINE=Course Outline
+sebserver.exam.form.sebrestriction.whiteListPaths.COURSE_OUTLINE.tooltip=The outline section of the Open edX course
sebserver.exam.form.sebrestriction.whiteListPaths.COURSE_WARE=Course Ware
+sebserver.exam.form.sebrestriction.whiteListPaths.COURSE_WARE.tooltip=The actual course and course content
sebserver.exam.form.sebrestriction.whiteListPaths.DISCUSSION=Discussion
+sebserver.exam.form.sebrestriction.whiteListPaths.DISCUSSION.tooltip=The discussion section of the Open edX course
sebserver.exam.form.sebrestriction.whiteListPaths.PROGRESS=Progress
+sebserver.exam.form.sebrestriction.whiteListPaths.PROGRESS.tooltip=The progress overview of the Open edX course
sebserver.exam.form.sebrestriction.whiteListPaths.WIKI=Description (Wiki)
+sebserver.exam.form.sebrestriction.whiteListPaths.WIKI.tooltip=The wikipedia section of the Open edX course
sebserver.exam.form.sebrestriction.permissions.ALWAYS_ALLOW_STUFF=Stuff Role Always Allowed
+sebserver.exam.form.sebrestriction.permissions.ALWAYS_ALLOW_STUFF.tooltip=Set this to always allow none-restricted access for a user that has "stuff" privileges.
sebserver.exam.form.sebrestriction.permissions.CHECK_BROWSER_EXAM_KEY=Check Browser-Exam-Key
+sebserver.exam.form.sebrestriction.permissions.CHECK_BROWSER_EXAM_KEY.tooltip=Always check received SEB Browser Exam Key with the defined ones for every request.
sebserver.exam.form.sebrestriction.permissions.CHECK_CONFIG_KEY=Check Config-Key
+sebserver.exam.form.sebrestriction.permissions.CHECK_CONFIG_KEY.tooltip=Always check received SEB Config Key with the defined ones for every request.
sebserver.exam.form.sebrestriction.permissions.CHECK_BROWSER_EXAM_OR_CONFIG_KEY=Check Browser-Exam- Or Config-Key
+sebserver.exam.form.sebrestriction.permissions.CHECK_BROWSER_EXAM_OR_CONFIG_KEY.tooltip=Always check either SEB Browser Exam Key or SEB Config Key with the defined ones for every request.
sebserver.exam.type.UNDEFINED=Not Defined
+sebserver.exam.type.UNDEFINED.tooltip=No exam type specified
sebserver.exam.type.MANAGED=Managed Devices
+sebserver.exam.type.MANAGED.tooltip=Exam type specified for managed devices
sebserver.exam.type.BYOD=Bring Your Own Device
+sebserver.exam.type.BYOD.tooltip=Exam type specified for bring your own devices
sebserver.exam.type.VDI=VDI (Virtual Desktop Infrastructure)
+sebserver.exam.type.VDI.tooltip=Exam type specified for Virtual Desktop Infrastructure
sebserver.exam.status.UP_COMING=Up Coming
sebserver.exam.status.RUNNING=Running
@@ -413,9 +482,13 @@ sebserver.exam.status.FINISHED=Finished
sebserver.exam.configuration.list.actions=
sebserver.exam.configuration.list.title=SEB Exam Configuration
+sebserver.exam.configuration.list.title.tooltip=A list of all attached SEB exam configuration for this exam.
sebserver.exam.configuration.list.column.name=Name
+sebserver.exam.configuration.list.column.name.tooltip=The name of the attached SEB exam configuration.
sebserver.exam.configuration.list.column.description=Description
+sebserver.exam.configuration.list.column.description.tooltip=The description of the attached SEB exam configuration.
sebserver.exam.configuration.list.column.status=Status
+sebserver.exam.configuration.list.column.status.tooltip=The current status of the attached SEB exam configuration.
sebserver.exam.configuration.list.empty=There is currently no SEB Configuration defined for this Exam. Please add one
sebserver.exam.configuration.list.pleaseSelect=Please select first a SEB Configuration from the list
sebserver.exam.configuration.action.noconfig.message=There is currently no SEB exam configuration to select.
Please create one in SEB Configuration / Exam Configuration
@@ -431,25 +504,34 @@ sebserver.exam.configuration.action.get-config-key=Export Config-Key
sebserver.exam.configuration.form.title.new=Add SEB Configuration Mapping
sebserver.exam.configuration.form.title=SEB Configuration Mapping
sebserver.exam.configuration.form.name=SEB Configuration
+sebserver.exam.configuration.form.name.tooltip=Please select a SEB exam configuration to attach to the exam
sebserver.exam.configuration.form.encryptSecret=Encryption Password
+sebserver.exam.configuration.form.encryptSecret.tooltip=Define a encryption password if the SEB exam configuration should be encrypted by password
sebserver.exam.configuration.form.description=Description
+sebserver.exam.configuration.form.description.tooltip=The description of the selected SEB exam configuration
sebserver.exam.configuration.form.status=Status
+sebserver.exam.configuration.form.status.tooltip=The current status of the selected SEB exam configuration
sebserver.exam.configuration.form.encryptSecret.confirm=Confirm Password
+sebserver.exam.configuration.form.encryptSecret.confirm.tooltip=Please confirm the encryption password if there is one
sebserver.exam.indicator.list.actions=
sebserver.exam.indicator.list.title=Indicators
+sebserver.exam.indicator.list.title.tooltip=A list of indicators that are shown on the exam monitoring view
sebserver.exam.indicator.list.column.type=Type
+sebserver.exam.indicator.list.column.type.tooltip=The type of indicator
sebserver.exam.indicator.list.column.name=Name
+sebserver.exam.indicator.list.column.name.tooltip=The name of the indicator
sebserver.exam.indicator.list.column.thresholds=Thresholds
+sebserver.exam.indicator.list.column.thresholds.tooltip=The thresholds of the indicator
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 first an indicator from the list
sebserver.exam.indicator.type.LAST_PING=Last Ping Time
sebserver.exam.indicator.type.ERROR_COUNT=Errors
sebserver.exam.indicator.type.WARN_COUNT=Warnings
-sebserver.exam.indicator.type.description.LAST_PING=This indicator shows the time in milliseconds since the last ping has been received from a SEB Client.This indicator can be used to track a SEB Client connection and indicate connection losse.Thresholds are defined in milliseconds.
-sebserver.exam.indicator.type.description.ERROR_COUNT=This indicator shows the number of error log messages that has been received from a SEB Client.This indicator can be used to track errors of connected SEB ClientsThresholds are defined by natural numbers.
-sebserver.exam.indicator.type.description.WARN_COUNT=This indicator shows the number of warn log messages that has been received from a SEB Client.This indicator can be used to track warnings of connected SEB ClientsThresholds are defined by natural numbers.
+sebserver.exam.indicator.type.description.LAST_PING=This indicator shows the time in milliseconds since
the last ping has been received from a SEB Client.
This indicator can be used to track a SEB Client connection and indicate connection loss.
The value is in milliseconds.
+sebserver.exam.indicator.type.description.ERROR_COUNT=This indicator shows the number of error log messages that
has been received from a SEB Client.
This indicator can be used to track errors of connected SEB Clients
The value is natural numbers.
+sebserver.exam.indicator.type.description.WARN_COUNT=This indicator shows the number of warn log messages that
has been received from a SEB Client.
This indicator can be used to track warnings of connected SEB Clients
The value is natural numbers.
sebserver.exam.indicator.info.pleaseSelect=Please select first an indicator from the list
@@ -462,19 +544,27 @@ sebserver.exam.indicator.action.save=Save
sebserver.exam.indicator.form.title=Indicator
sebserver.exam.indicator.form.title.new=Add Indicator
sebserver.exam.indicator.form.exam=Exam
+sebserver.exam.indicator.form.exam.tooltip=The exam this indicator belongs to.
sebserver.exam.indicator.form.name=Name
+sebserver.exam.indicator.form.name.tooltip=The name of the indicator.
This name is also displayed as the column title of the indicator on the exam monitoring view
sebserver.exam.indicator.form.type=Type
+sebserver.exam.indicator.form.type.tooltip=The type of the indicator.
There are only a set of defined indicators to choose from.
Choose one to see a detailed description for each indicator below.
sebserver.exam.indicator.form.description=Type Description
+sebserver.exam.indicator.form.description.tooltip=A detailed description of the selected indicator.
sebserver.exam.indicator.form.color=Default Color
+sebserver.exam.indicator.form.color.tooltip=The default color that is displayed on the exam monitoring for this indicator.
sebserver.exam.indicator.form.color.action=Please select a color
sebserver.exam.indicator.form.thresholds=Thresholds
+sebserver.exam.indicator.form.thresholds.tooltip=A list of value / color pairs that defines the thresholds of the indicator.
On the exam monitoring view a cell of the indicator is displayed in the specified color when the defined threshold value is reached
sebserver.exam.indicator.thresholds.select.color=Please select a color
sebserver.exam.indicator.thresholds.list.title=Thresholds
sebserver.exam.indicator.thresholds.list.value=Value
+sebserver.exam.indicator.thresholds.list.value.tooltip=The threshold value.
sebserver.exam.indicator.thresholds.list.color=Color
-sebserver.exam.indicator.thresholds.list.add=Add Threshold
-sebserver.exam.indicator.thresholds.list.remove=Delete Threshold
+sebserver.exam.indicator.thresholds.list.color.tooltip=The color that is displayed on the exam monitoring view when indicator value has reached the defined threshold value.
+sebserver.exam.indicator.thresholds.list.add=Add a new threshold
+sebserver.exam.indicator.thresholds.list.remove=Delete this threshold
################################
# SEB Client Configuration
@@ -489,28 +579,40 @@ sebserver.clientconfig.list.empty=There is currently no SEB-Client configuration
sebserver.clientconfig.list.title=SEB Client Configurations
sebserver.clientconfig.list.actions=
sebserver.clientconfig.list.column.institution=Institution
-sebserver.clientconfig.list.column.institution.tooltip=The institution of the SEB client configuration.Use the filter above to specify the institution.{0}
+sebserver.clientconfig.list.column.institution.tooltip=The institution of the SEB client configuration.
Use the filter above to specify the institution.
{0}
sebserver.clientconfig.list.column.name=Name
-sebserver.clientconfig.list.column.name.tooltip=The name of the SEB client configuration.Use the filter above to narrow down a specific name.{0}
+sebserver.clientconfig.list.column.name.tooltip=The name of the SEB client configuration.
Use the filter above to narrow down a specific name.
{0}
sebserver.clientconfig.list.column.date=Creation Date {0}
-sebserver.clientconfig.list.column.date.tooltip=The date when the SEB client configuration was first created.Use the filter above to specify a from-date.{0}
+sebserver.clientconfig.list.column.date.tooltip=The date when the SEB client configuration was first created.
Use the filter above to specify a from-date.
{0}
sebserver.clientconfig.list.column.active=Active
-sebserver.clientconfig.list.column.active.tooltip=The activity of SEB client configuration.Use the filter above to specify the activity.{0}
+sebserver.clientconfig.list.column.active.tooltip=The activity of SEB client configuration.
Use the filter above to specify the activity.
{0}
sebserver.clientconfig.info.pleaseSelect=Please select first a Client Configuration from the list
sebserver.clientconfig.list.action.no.modify.privilege=No Access: A SEB Client Configuration from other institution cannot be modified.
sebserver.clientconfig.form.title.new=Add Client Configuration
sebserver.clientconfig.form.title=SEB Client Configuration
sebserver.clientconfig.form.name=Name
-sebserver.clientconfig.form.name.tooltip=The name of the SEB Client Configuration.Can be any name that not already exists for another SEB Client Configuration
+sebserver.clientconfig.form.name.tooltip=The name of the SEB Client Configuration.
Can be any name that not already exists for another SEB Client Configuration
+sebserver.clientconfig.form.fallback=With Fallback
+sebserver.clientconfig.form.fallback.tooltip=Indicates whether this SEB Client Configuration has a fallback definition or not
sebserver.clientconfig.form.fallback-url=Fallback Start URL
sebserver.clientconfig.form.fallback-url.tooltip=A fallback URL that tells the SEB where to go when the SEB Server service is unavailable.
+sebserver.clientconfig.form.sebServerFallbackTimeout=Fallback Timeout
+sebserver.clientconfig.form.sebServerFallbackTimeout.tooltip=Defines the fallback timeout for the SEB Client in milli-seconds.
+sebserver.clientconfig.form.sebServerFallbackAttempts=Fallback Attempts
+sebserver.clientconfig.form.sebServerFallbackAttempts.tooltip=The number of connection attempts a SEB Client is trying before switching to fallback case.
+sebserver.clientconfig.form.sebServerFallbackAttemptInterval=Attempt Interval
+sebserver.clientconfig.form.sebServerFallbackAttemptInterval.tooltip=The interval (in milli-seconds) between connection attempts a SEB Client shall use.
+sebserver.clientconfig.form.sebServerFallbackPasswordHash=Fallback Password
+sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip=A password if set, a SEB Client user must give before the SEB Client starts the fallback procedure.
sebserver.clientconfig.form.date=Creation Date
sebserver.clientconfig.form.date.tooltip=The date when the SEB client configuration was first created.
sebserver.clientconfig.form.encryptSecret=Configuration Password
sebserver.clientconfig.form.encryptSecret.tooltip=Define a password if the SEB client configuration shall be password-encrypted
sebserver.clientconfig.form.encryptSecret.confirm=Confirm Password
-sebserver.clientconfig.form.encryptSecret.confirm.tooltip=Please retype the given password for configrmation
+sebserver.clientconfig.form.encryptSecret.confirm.tooltip=Please retype the given password for confirmation
+sebserver.clientconfig.form.sebConfigPurpose=Configuration Purpose
+sebserver.clientconfig.form.sebConfigPurpose.tooltip=This indicates whether this client configuration shall be used to configure the SEB Client or to start an exam
sebserver.clientconfig.action.list.new=Add Configuration
sebserver.clientconfig.action.list.view=View Configuration
@@ -527,13 +629,13 @@ sebserver.clientconfig.action.deactivate=Deactivate Configuration
sebserver.examconfig.action.list=Exam Configuration
sebserver.examconfig.list.title=Exam Configurations
sebserver.examconfig.list.column.institution=Institution
-sebserver.examconfig.list.column.institution.tooltip=The institution of the SEB exam configuration.Use the filter above to specify the institution.{0}
+sebserver.examconfig.list.column.institution.tooltip=The institution of the SEB exam configuration.
Use the filter above to specify the institution.
{0}
sebserver.examconfig.list.column.name=Name
-sebserver.examconfig.list.column.name.tooltip=The name of the SEB exam configuration.Use the filter above to narrow down a specific name.{0}
+sebserver.examconfig.list.column.name.tooltip=The name of the SEB exam configuration.
Use the filter above to narrow down a specific name.
{0}
sebserver.examconfig.list.column.description=Description
-sebserver.examconfig.list.column.description.tooltip=The description of the SEB exam configuration.Use the filter above to find configurations that contains specific words or phrases within the description.{0}
+sebserver.examconfig.list.column.description.tooltip=The description of the SEB exam configuration.
Use the filter above to find configurations that contains specific words or phrases within the description.
{0}
sebserver.examconfig.list.column.status=Status
-sebserver.examconfig.list.column.status.tooltip=The status of the SEB exam configuration.Use the filter above to specify a status.{0}
+sebserver.examconfig.list.column.status.tooltip=The status of the SEB exam configuration.
Use the filter above to specify a status.
{0}
sebserver.examconfig.list.actions=
@@ -577,10 +679,10 @@ sebserver.examconfig.form.with-history=With History
sebserver.examconfig.form.template=Template
sebserver.examconfig.form.template.tooltip=The template this SEB exam configuration depends on.
sebserver.examconfig.form.status=Status
-sebserver.examconfig.form.status.tooltip=The status of this SEB exam configuration.Under Construction marks a SEB exam configuration to not be able to attach to an exam so far.Ready to use marks an SEB exam configuration to be able to attach to an exam.In Use marks a SEB exam configuration is already been used from one or more exam(s)
+sebserver.examconfig.form.status.tooltip=The status of this SEB exam configuration.
Under Construction marks a SEB exam configuration to not be able to attach to an exam so far.
Ready to use marks an SEB exam configuration to be able to attach to an exam.
In Use marks a SEB exam configuration is already been used from one or more exam(s)
sebserver.examconfig.form.config-key.title=Config Key
sebserver.examconfig.form.attached-to=Attached To Exam
-sebserver.examconfig.form.attached-to.tooltip=This SEB exam configuration is currently attached to the following exams.Select an exam from the list and use the "View Exam" or Double-Click on the list to go to a specific exam.
+sebserver.examconfig.form.attached-to.tooltip=This SEB exam configuration is currently attached to the following exams.
Select an exam from the list and use the "View Exam" or Double-Click on the list to go to a specific exam.
sebserver.examconfig.status.CONSTRUCTION=Under Construction
sebserver.examconfig.status.READY_TO_USE=Ready To Use
@@ -603,9 +705,9 @@ sebserver.examconfig.props.form.views.hooked_keys=Hooked Keys
sebserver.examconfig.props.label.hashedAdminPassword=Administrator password
sebserver.examconfig.props.label.hashedAdminPassword.confirm=Confirm password
sebserver.examconfig.props.label.allowQuit=Allow user to quit SEB
-sebserver.examconfig.props.label.allowQuit.tooltip=Users can quit SEB with Control-Q, window close or quit button.Otherwise use a quit link in your exam system or shutdown/restart the computer.
+sebserver.examconfig.props.label.allowQuit.tooltip=Users can quit SEB with Control-Q, window close or quit button.
Otherwise use a quit link in your exam system or shutdown/restart the computer.
sebserver.examconfig.props.label.ignoreExitKeys=Ignore exit keys
-sebserver.examconfig.props.label.ignoreExitKeys.tooltip=SEB ignores the exit keys and can only be quit manually by entering the quit password.(click Quit button in SEB taskbar, press Ctrl-Q or click the main browser window close button)
+sebserver.examconfig.props.label.ignoreExitKeys.tooltip=SEB ignores the exit keys and can only be quit manually by entering the quit password.
(click Quit button in SEB taskbar, press Ctrl-Q or click the main browser window close button)
sebserver.examconfig.props.label.hashedQuitPassword=Quit/unlock password
sebserver.examconfig.props.label.hashedQuitPassword.confirm=Confirm password
sebserver.examconfig.props.group.exitSequence=Exit Sequence
@@ -645,15 +747,15 @@ sebserver.examconfig.props.label.mainBrowserWindowPositioning.2=Right
sebserver.examconfig.props.group.wintoolbar=Browser Window Toolbar
sebserver.examconfig.props.label.enableBrowserWindowToolbar=Enable browser window toolbar
-sebserver.examconfig.props.label.enableBrowserWindowToolbar.tooltip=Displays a toolbar on top of the browser windowwhich can also be hidden by the user.
+sebserver.examconfig.props.label.enableBrowserWindowToolbar.tooltip=Displays a toolbar on top of the browser window
which can also be hidden by the user.
sebserver.examconfig.props.label.hideBrowserWindowToolbar=Hide toolbar as default (Mac)
-sebserver.examconfig.props.label.hideBrowserWindowToolbar.tooltip=Hide browser window toolbar by default.It can be shown again by using the View menu or Alt-Command-T.
+sebserver.examconfig.props.label.hideBrowserWindowToolbar.tooltip=Hide browser window toolbar by default.
It can be shown again by using the View menu or Alt-Command-T.
sebserver.examconfig.props.label.showMenuBar=Show menu bar (Mac)
sebserver.examconfig.props.label.showMenuBar.tooltip=Show the OS X menu bar to allow to access settings like Wi-Fi.
sebserver.examconfig.props.group.taskbar=SEB Taskbar/Dock
sebserver.examconfig.props.label.showTaskBar=Show SEB taskbar
-sebserver.examconfig.props.label.showTaskBar.tooltip=The SEB task bar shows and switches between open browser windows, allowed resources and applications and displays additional controls
+sebserver.examconfig.props.label.showTaskBar.tooltip=The SEB task bar shows and switches between open browser windows,
allowed resources and applications and displays additional controls
sebserver.examconfig.props.label.taskBarHeight=Taskbar/dock height
sebserver.examconfig.props.label.taskBarHeight.tooltip=Height of SEB dock/task bar in points/pixels
sebserver.examconfig.props.label.showReloadButton=Show reload button
@@ -665,9 +767,9 @@ sebserver.examconfig.props.label.showInputLanguage.tooltip=Shows current keyboar
sebserver.examconfig.props.group.zoom=Enable Zoom (Win/Mac)
sebserver.examconfig.props.label.enableZoomPage=Enable page zoom
-sebserver.examconfig.props.label.enableZoomPage.tooltip=Pages can be zoomed with ctrl - cmd +/- or the commands in the view menu and browser window toolbar (Mac)
+sebserver.examconfig.props.label.enableZoomPage.tooltip=Pages can be zoomed with ctrl - cmd +/-
or the commands in the view menu and browser window toolbar (Mac)
sebserver.examconfig.props.label.enableZoomText=Enable text zoom
-sebserver.examconfig.props.label.enableZoomText.tooltip=Text in browser windows can be zoomed with cmd +/- or the commands in the view menu and browser window toolbar (Mac)
+sebserver.examconfig.props.label.enableZoomText.tooltip=Text in browser windows can be zoomed with cmd +/-
or the commands in the view menu and browser window toolbar (Mac)
sebserver.examconfig.props.group.zoomMode=Zoom Mode Win (Ctrl-Mousewheel)
sebserver.examconfig.props.label.zoomMode.0=Use page zoom
sebserver.examconfig.props.label.zoomMode.0.tooltip=Zoom whole web pages using Ctrl-Mousewheel (Win)"
@@ -687,7 +789,7 @@ sebserver.examconfig.props.label.allowSpellCheck=Allow spell checking
sebserver.examconfig.props.label.allowSpellCheck.tooltip=Allow to use "Check spelling" in the SEB browser
sebserver.examconfig.props.label.allowDictionaryLookup=Allow dictionary lookup (Mac)
sebserver.examconfig.props.label.allowDictionaryLookup.tooltip=Allow to use the OS X dictionary lookup using a 3 finger tap
-sebserver.examconfig.props.label.allowSpellCheckDictionary=The list below shows all dictionaries currently available for spell checking.SEB comes with a list of standard dictionaries that can be activated/deactivated here.
+sebserver.examconfig.props.label.allowSpellCheckDictionary=The list below shows all dictionaries currently available for spell checking.
SEB comes with a list of standard dictionaries that can be activated/deactivated here.
sebserver.examconfig.props.label.allowSpellCheckDictionary.da-DK=Danish (Denmark) (da-DK)
sebserver.examconfig.props.label.allowSpellCheckDictionary.en-AU=English (Australia) (en-AU)
sebserver.examconfig.props.label.allowSpellCheckDictionary.en-GB=English (United Kingdom) (en-GB)
@@ -702,8 +804,8 @@ sebserver.examconfig.props.group.newBrowserWindow=Links requesting to be opened
sebserver.examconfig.props.label.newBrowserWindowByLinkPolicy.0=get generally blocked
sebserver.examconfig.props.label.newBrowserWindowByLinkPolicy.1=open in same window
sebserver.examconfig.props.label.newBrowserWindowByLinkPolicy.2=open in new window
-sebserver.examconfig.props.label.newBrowserWindowByLinkBlockForeign=Block when directingto a different server
-sebserver.examconfig.props.label.newBrowserWindowByLinkBlockForeign.tooltip=USE WITH CARE: Hyperlinks invoked by JavaScript/plug-ins which direct to a different host than the one of the current main page will be ignored.
+sebserver.examconfig.props.label.newBrowserWindowByLinkBlockForeign=Block when directing
to a different server
+sebserver.examconfig.props.label.newBrowserWindowByLinkBlockForeign.tooltip=USE WITH CARE: Hyperlinks invoked by JavaScript/plug-ins
which direct to a different host than the one of the current main page will be ignored.
sebserver.examconfig.props.group.newwinsize=New browser window size and position
sebserver.examconfig.props.label.newBrowserWindowByLinkWidth=Width
@@ -717,19 +819,19 @@ sebserver.examconfig.props.label.newBrowserWindowByLinkPositioning.2=Right
sebserver.examconfig.props.group.browserSecurity=Browser security
sebserver.examconfig.props.label.enablePlugIns=Enable plug-ins (Win: only Flash)
-sebserver.examconfig.props.label.enablePlugIns.tooltip=Enables web plugins (Mac) or just Flash (Win). For security reasons it\'s recommended to disable this option if you don\'t use any plugin/Flash content.
+sebserver.examconfig.props.label.enablePlugIns.tooltip=Enables web plugins (Mac) or just Flash (Win).
For security reasons it\'s recommended to disable this option if you don\'t use any plugin/Flash content.
sebserver.examconfig.props.label.enableJavaScript=Enable JavaScript
-sebserver.examconfig.props.label.enableJavaScript.tooltip=Enables JavaScript. Please note that most modern web-sites need JavaScript for full functionality.
+sebserver.examconfig.props.label.enableJavaScript.tooltip=Enables JavaScript.
Please note that most modern web-sites need JavaScript for full functionality.
sebserver.examconfig.props.label.enableJava=Enable Java
-sebserver.examconfig.props.label.enableJava.tooltip=Enables Java applets. Note: Only applets with the highest Java security level will run in SEB.
+sebserver.examconfig.props.label.enableJava.tooltip=Enables Java applets.
Note: Only applets with the highest Java security level will run in SEB.
sebserver.examconfig.props.label.blockPopUpWindows=Block pop-up windows
-sebserver.examconfig.props.label.blockPopUpWindows.tooltip=Disables pop-up windows (often advertisement) opened by JavaScript without an user action such as a button click.
+sebserver.examconfig.props.label.blockPopUpWindows.tooltip=Disables pop-up windows
(often advertisement) opened by JavaScript without an user action such as a button click.
sebserver.examconfig.props.label.allowVideoCapture=Allow video capture (webcam)
sebserver.examconfig.props.label.allowVideoCapture.tooltip=Allow web applications to access camera
sebserver.examconfig.props.label.allowAudioCapture=Allow audio capture (microphone)
sebserver.examconfig.props.label.allowAudioCapture.tooltip=Allow web applications to access microphone
sebserver.examconfig.props.label.allowBrowsingBackForward=Allow navigating back/forward in exam
-sebserver.examconfig.props.label.allowBrowsingBackForward.tooltip=Disabling browsing to previously visited pages may increase security, because browsing back might allow to leave an exam
+sebserver.examconfig.props.label.allowBrowsingBackForward.tooltip=Disabling browsing to previously visited pages may increase security,
because browsing back might allow to leave an exam
sebserver.examconfig.props.label.newBrowserWindowNavigation=Allow navigating in additional windows
sebserver.examconfig.props.label.browserWindowAllowReload=Allow reload exam
sebserver.examconfig.props.label.browserWindowAllowReload.tooltip=Allow reload in the exam window with F5 reload button (if displayed)
@@ -742,7 +844,7 @@ sebserver.examconfig.props.label.newBrowserWindowShowReloadWarning.tooltip=User
sebserver.examconfig.props.label.removeBrowserProfile=Remove profile (Win)
sebserver.examconfig.props.label.removeBrowserProfile.tooltip=Remove XULRunner browser profile (containing caches and also local storage) when quitting SEB
sebserver.examconfig.props.label.removeLocalStorage=Disable local storage (Mac)
-sebserver.examconfig.props.label.removeLocalStorage.tooltip=If your web application uses local storage, you have to be sure data is saved encrypted and removed when no longer needed as SEB doesn't remove local storage
+sebserver.examconfig.props.label.removeLocalStorage.tooltip=If your web application uses local storage, you have to be sure data is saved encrypted
and removed when no longer needed as SEB doesn't remove local storage
sebserver.examconfig.props.label.browserUserAgent=Suffix to be added to any user agent
sebserver.examconfig.props.group.userAgentDesktop=User agent for desktop mode
@@ -750,7 +852,7 @@ sebserver.examconfig.props.label.browserUserAgentWinDesktopMode.0=Desktop defaul
sebserver.examconfig.props.label.browserUserAgentWinDesktopMode.0.tooltip=Zoom whole web pages using Ctrl-Mousewheel (Win)
sebserver.examconfig.props.label.browserUserAgentWinDesktopMode.1=Custom
sebserver.examconfig.props.label.browserUserAgentWinDesktopMode.1.tooltip=Zoom only text on web pages using Ctrl-Mousewheel (Win)
-sebserver.examconfig.props.label.browserUserAgentWinDesktopModeCustom.tooltip=Custom desktop user agent string(SEB appends its version number automatically)
+sebserver.examconfig.props.label.browserUserAgentWinDesktopModeCustom.tooltip=Custom desktop user agent string
(SEB appends its version number automatically)
sebserver.examconfig.props.group.userAgentTouch=User agent for touch/table mode
sebserver.examconfig.props.label.browserUserAgentWinTouchMode.0=Touch default
@@ -764,28 +866,28 @@ sebserver.examconfig.props.label.browserUserAgentMac.1=Custom
sebserver.examconfig.props.label.browserUserAgentMac.1.tooltip=Zoom only text on web pages using Ctrl-Mousewheel (Win)
sebserver.examconfig.props.label.enableSebBrowser=Enable SEB with browser window
-sebserver.examconfig.props.label.enableSebBrowser.tooltip=Disable this to start another application in kiosk mode(for example a virtual desktop infrastructure client)
+sebserver.examconfig.props.label.enableSebBrowser.tooltip=Disable this to start another application in kiosk mode
(for example a virtual desktop infrastructure client)
sebserver.examconfig.props.label.browserWindowTitleSuffix=Suffix to be added to every browser window
sebserver.examconfig.props.label.allowDownUploads=Allow downloading and uploading files (Mac)
-sebserver.examconfig.props.label.allowDownUpload.tooltip=Usually to be used with permitted third party applications for which you want to provide files to be down-loaded.
+sebserver.examconfig.props.label.allowDownUpload.tooltip=Usually to be used with permitted third party applications
for which you want to provide files to be down-loaded.
sebserver.examconfig.props.label.downloadDirectoryWin=Download directory (Win)
sebserver.examconfig.props.label.downloadDirectoryOSX=Download directory (Mac)
sebserver.examconfig.props.label.openDownloads=Open files after downloading (Mac)
sebserver.examconfig.props.label.chooseFileToUploadPolicy=Choose file to upload (Mac)
-sebserver.examconfig.props.label.chooseFileToUploadPolicy.tooltip=SEB can let users choose the file to upload or automatically use the same file which was down-loaded before.If not found, a file requester or an error is presented depending on this setting.
+sebserver.examconfig.props.label.chooseFileToUploadPolicy.tooltip=SEB can let users choose the file to upload or automatically use the same file which was down-loaded before.
If not found, a file requester or an error is presented depending on this setting.
sebserver.examconfig.props.label.chooseFileToUploadPolicy.0=manually with file requester
sebserver.examconfig.props.label.chooseFileToUploadPolicy.1=by attempting to upload the same file downloaded before
sebserver.examconfig.props.label.chooseFileToUploadPolicy.2=by only allowing to upload the same file downloaded before
sebserver.examconfig.props.label.downloadPDFFiles=Download and open PDF files instead of displaying them inline (Mac)
-sebserver.examconfig.props.label.downloadPDFFiles.tooltip=PDF files will not be displayed by SEB but downloaded and openend (if "Open files after downloading" is active!) by the application set in Finder (usually Preview or Adobe Acrobat).
+sebserver.examconfig.props.label.downloadPDFFiles.tooltip=PDF files will not be displayed by SEB but downloaded and openend (if "Open files after downloading" is active!)
by the application set in Finder (usually Preview or Adobe Acrobat).
sebserver.examconfig.props.label.allowPDFPlugIn=Allow using Acrobat Reader PDF plugin (insecure! Mac only)
-sebserver.examconfig.props.label.allowPDFPlugIn.tooltip=The Adobe Acrobat Reader browser plugin should only be used on secured managed Mac computers, at it allows limited access the file system and unlimited to cloud services
+sebserver.examconfig.props.label.allowPDFPlugIn.tooltip=The Adobe Acrobat Reader browser plugin should only be used on secured managed Mac computers,
at it allows limited access the file system and unlimited to cloud services
sebserver.examconfig.props.label.downloadAndOpenSebConfig=Download and open SEB Config Files
sebserver.examconfig.props.label.downloadAndOpenSebConfig.tooltip=Download and open .seb config files regardless if downloading and opening other file types is allowed.
sebserver.examconfig.props.group.quitLink=Link to quit SEB after exam
-sebserver.examconfig.props.label.quitURL=Place this quit link to the 'feedback' page displayed after an exam was successfully finished. Clicking that link will quit SEB without having to enter the quit password.
+sebserver.examconfig.props.label.quitURL=Place this quit link to the 'feedback' page displayed after an exam was successfully finished.
Clicking that link will quit SEB without having to enter the quit password.
sebserver.examconfig.props.label.quitURLConfirm=Ask user to confirm quitting
sebserver.examconfig.props.group.backToStart=Back to Start Button
@@ -798,7 +900,7 @@ sebserver.examconfig.props.label.restartExamPasswordProtected=Protect back to st
sebserver.examconfig.props.label.restartExamPasswordProtected.tooltip=The quit/restart password (if set) must be entered when the back to start button was pressed.
sebserver.examconfig.props.label.allowSwitchToApplications=Allow switching to third party application (Mac)
-sebserver.examconfig.props.label.allowSwitchToApplications.tooltip=Decreases security of the kiosk mode by allowing process switcher (Cmd+Tab). The blacked out background of SEB also doesn't cover some alerts and modal windows in this mode.
+sebserver.examconfig.props.label.allowSwitchToApplications.tooltip=Decreases security of the kiosk mode by allowing process switcher (Cmd+Tab).
The blacked out background of SEB also doesn't cover some alerts and modal windows in this mode.
sebserver.examconfig.props.label.allowFlashFullscreen=Allow Flash to switch to fullscreen mode (Mac)
sebserver.examconfig.props.label.permittedProcesses.add.tooltip=Add permitted process
sebserver.examconfig.props.label.permittedProcesses.remove.tooltip=Remove selected permitted process
@@ -811,11 +913,11 @@ sebserver.examconfig.props.label.permittedProcesses.os.tooltip=Indicates on whic
sebserver.examconfig.props.label.permittedProcesses.os.0=OS X
sebserver.examconfig.props.label.permittedProcesses.os.1=Win
sebserver.examconfig.props.label.permittedProcesses.title=Title
-sebserver.examconfig.props.label.permittedProcesses.title.tooltip=Application title which is displayed in the application chooser. Background processes don't have a title, because they can't be selected by users.
+sebserver.examconfig.props.label.permittedProcesses.title.tooltip=Application title which is displayed in the application chooser.
Background processes don't have a title, because they can't be selected by users.
sebserver.examconfig.props.label.permittedProcesses.description=Description
-sebserver.examconfig.props.label.permittedProcesses.description.tooltip=Optional, should explain what kind of process this is, because this might not be obvious only from the executable's name.
+sebserver.examconfig.props.label.permittedProcesses.description.tooltip=Optional, should explain what kind of process this is,
because this might not be obvious only from the executable's name.
sebserver.examconfig.props.label.permittedProcesses.executable=Executable
-sebserver.examconfig.props.label.permittedProcesses.executable.tooltip=File name of the executable, which should not contain any parts of a file system path, only the filename of the exe file (like calc.exe).
+sebserver.examconfig.props.label.permittedProcesses.executable.tooltip=File name of the executable, which should not contain any parts of a file system path,
only the filename of the exe file (like calc.exe).
sebserver.examconfig.props.label.permittedProcesses.originalName=Original Name
sebserver.examconfig.props.label.permittedProcesses.allowedExecutables=Window handling process
sebserver.examconfig.props.label.permittedProcesses.path=Path
@@ -825,13 +927,13 @@ sebserver.examconfig.props.label.permittedProcesses.arguments.argument=Argument
sebserver.examconfig.props.label.permittedProcesses.arguments.addAction=Add new argument
sebserver.examconfig.props.label.permittedProcesses.arguments.removeAction=Remove this argument
sebserver.examconfig.props.label.permittedProcesses.identifier=Identifier
-sebserver.examconfig.props.label.permittedProcesses.identifier.tooltip=(Sub) string in the title of the main window of a tricky third party application (Java, Acrobat etc.). Mac OS X: Bundle identifier of the process in reverse domain notation.
+sebserver.examconfig.props.label.permittedProcesses.identifier.tooltip=(Sub) string in the title of the main window of a tricky third party application (Java, Acrobat etc.).
Mac OS X: Bundle identifier of the process in reverse domain notation.
sebserver.examconfig.props.label.permittedProcesses.iconInTaskbar=Icon in taskbar
-sebserver.examconfig.props.label.permittedProcesses.iconInTaskbar.tooltip=Show icon of permitted application in task bar (not possible when 'run in background' is enabled).
+sebserver.examconfig.props.label.permittedProcesses.iconInTaskbar.tooltip=Show icon of permitted application in task bar
(not possible when 'run in background' is enabled).
sebserver.examconfig.props.label.permittedProcesses.autostart=Autostart
sebserver.examconfig.props.label.permittedProcesses.autostart.tooltip=Start the process automatically together with SEB.
sebserver.examconfig.props.label.permittedProcesses.runInBackground=Allow running in background
-sebserver.examconfig.props.label.permittedProcesses.runInBackground.tooltip=Allow the permitted process to already be running when SEB starts. Such a process can't have an icon in the task bar.
+sebserver.examconfig.props.label.permittedProcesses.runInBackground.tooltip=Allow the permitted process to already be running when SEB starts.
Such a process can't have an icon in the task bar.
sebserver.examconfig.props.label.permittedProcesses.allowUserToChooseApp=Allow user to select location of application
sebserver.examconfig.props.label.permittedProcesses.strongKill=Force quit (risk of data loss)
sebserver.examconfig.props.label.permittedProcesses.strongKill.tooltip=Terminate process in a not-nice way, which may cause data loss if the application had unsaved data
@@ -846,15 +948,15 @@ sebserver.examconfig.props.label.prohibitedProcesses.os=OS
sebserver.examconfig.props.label.prohibitedProcesses.os.0=OS X
sebserver.examconfig.props.label.prohibitedProcesses.os.1=Win
sebserver.examconfig.props.label.prohibitedProcesses.description=Description
-sebserver.examconfig.props.label.prohibitedProcesses.description.tooltip=Optional, to explain what kind of process this is, because this might not be obvious only from the executable's name.
+sebserver.examconfig.props.label.prohibitedProcesses.description.tooltip=Optional, to explain what kind of process this is,
because this might not be obvious only from the executable's name.
sebserver.examconfig.props.label.prohibitedProcesses.executable=Executable
-sebserver.examconfig.props.label.prohibitedProcesses.executable.tooltip=File name of the executable, which should not contain any parts of a file system path, only the filename of the exe file (like calc.exe).
+sebserver.examconfig.props.label.prohibitedProcesses.executable.tooltip=File name of the executable, which should not contain any parts of a file system path,
only the filename of the exe file (like calc.exe).
sebserver.examconfig.props.label.prohibitedProcesses.originalName=Original Name
sebserver.examconfig.props.label.prohibitedProcesses.originalName.tooltip=Original file name (optional)
sebserver.examconfig.props.label.prohibitedProcesses.identifier=Identifier
-sebserver.examconfig.props.label.prohibitedProcesses.identifier.tooltip=Title of the main window of a Java third party application. Mac OS X: Bundle identifier of the process in reverse domain notation.
+sebserver.examconfig.props.label.prohibitedProcesses.identifier.tooltip=Title of the main window of a Java third party application.
Mac OS X: Bundle identifier of the process in reverse domain notation.
sebserver.examconfig.props.label.prohibitedProcesses.strongKill=Force quit (risk of data loss)
-sebserver.examconfig.props.label.prohibitedProcesses.strongKill.tooltip=Terminate process in a not-nice way, which may cause data loss if the application had unsaved data
+sebserver.examconfig.props.label.prohibitedProcesses.strongKill.tooltip=Terminate process in a not-nice way,
which may cause data loss if the application had unsaved data
sebserver.examconfig.props.group.urlFilter=Filter
sebserver.examconfig.props.label.URLFilterEnable=Activate URL Filtering
@@ -1014,7 +1116,7 @@ sebserver.examconfig.props.label.insideSebEnableLogOff.tooltip=Activates the but
sebserver.examconfig.props.label.insideSebEnableShutDown=Enable Shut down
sebserver.examconfig.props.label.insideSebEnableShutDown.tooltip=Activates the button "Shutdown"
sebserver.examconfig.props.label.insideSebEnableEaseOfAccess=Enable Ease of Access
-sebserver.examconfig.props.label.insideSebEnableEaseOfAccess.tooltip=Shows options when the button "Ease of Access" in the lower left corner is clicked,which offers help e.g. to visually or aurally handicapped persons, like the Magnifier Glass.
+sebserver.examconfig.props.label.insideSebEnableEaseOfAccess.tooltip=Shows options when the button "Ease of Access" in the lower left corner is clicked,
which offers help e.g. to visually or aurally handicapped persons, like the Magnifier Glass.
sebserver.examconfig.props.label.insideSebEnableVmWareClientShade=Enable VMware Client Shade
sebserver.examconfig.props.label.insideSebEnableVmWareClientShade.tooltip=Activates the "Shade" bar at the upper edge of a virtual desktop, if existent. If you're not using VMware, this setting doesn't have any effect.
sebserver.examconfig.props.label.insideSebEnableNetworkConnectionSelector=Enable network connection selector
@@ -1103,13 +1205,13 @@ sebserver.configtemplate.attr.type.COMPOSITE_TABLE=Table
sebserver.configtemplate.attrs.list.title=Configuration Attributes
sebserver.configtemplate.attrs.list.title.tooltip=Table of all SEB exam configuration attributes of this template
sebserver.configtemplate.attrs.list.name=Name
-sebserver.configtemplate.attrs.list.name.tooltip=The technical name of the SEB exam configuration attribute with the display name in brackets if available.{0}
+sebserver.configtemplate.attrs.list.name.tooltip=The technical name of the SEB exam configuration attribute with the display name in brackets if available.
{0}
sebserver.configtemplate.attrs.list.view=View
-sebserver.configtemplate.attrs.list.view.tooltip=The view/tab where the SEB exam configuration attribute belongs to.{0}
+sebserver.configtemplate.attrs.list.view.tooltip=The view/tab where the SEB exam configuration attribute belongs to.
{0}
sebserver.configtemplate.attrs.list.group=Group
-sebserver.configtemplate.attrs.list.group.tooltip=The group on the view/tab where the SEB exam configuration attribute belongs to.{0}
+sebserver.configtemplate.attrs.list.group.tooltip=The group on the view/tab where the SEB exam configuration attribute belongs to.
{0}
sebserver.configtemplate.attrs.list.type=Type
-sebserver.configtemplate.attrs.list.type.tooltip=The type of the SEB exam configuration attribute.{0}
+sebserver.configtemplate.attrs.list.type.tooltip=The type of the SEB exam configuration attribute.
{0}
sebserver.configtemplate.attr.list.actions=
sebserver.configtemplate.attr.list.actions.modify=Edit Attribute
@@ -1149,17 +1251,31 @@ sebserver.monitoring.exam.info.pleaseSelect=Please select first an Exam from the
sebserver.monitoring.exam.list.empty=There are currently no running exams
sebserver.monitoring.exam.list.column.name=Name
+sebserver.monitoring.exam.list.column.name.tooltip=The name of the exam.
Use the filter above to narrow down a specific exam name.
{0}
sebserver.monitoring.exam.list.column.type=Type
+sebserver.monitoring.exam.list.column.type.tooltip=The type of the exam.
Use the filter above to set a specific exam type.
{0}
sebserver.monitoring.exam.list.column.startTime=Start Time {0}
+sebserver.monitoring.exam.list.column.startTime.tooltip=The start date and time of the exam.
{0}
sebserver.monitoring.exam.list.column.endTime=End Time {0}
+sebserver.monitoring.exam.list.column.endTime.tooltip=The end date and time of the exam.
{0}
sebserver.monitoring.exam=Monitoring Exam: {0}
-sebserver.monitoring.connection.list.column.id=Client Identifier
+sebserver.monitoring.connection.list.column.id=User Name or Session
+sebserver.monitoring.connection.list.column.id.tooltip=The user session identifier or username sent by the SEB client after LMS login.
sebserver.monitoring.connection.list.column.address=IP Address
+sebserver.monitoring.connection.list.column.address.tooltip=The IP address from the host the SEB client is connecting to the SEB Server.
sebserver.monitoring.connection.list.column.status=Status
-sebserver.monitoring.connection.list.column.examname=Exam
-sebserver.monitoring.connection.list.column.vdiAddress=IP Address (VDI)
+sebserver.monitoring.connection.list.column.status.tooltip=The current connection status
+
+sebserver.monitoring.connection.form.id=User Name or Session
+sebserver.monitoring.connection.form.id.tooltip=The user session identifier or username sent by the SEB client after LMS login.
+sebserver.monitoring.connection.form.address=IP Address
+sebserver.monitoring.connection.form.address.tooltip=The IP address from the host the SEB client is connecting to the SEB Server.
+sebserver.monitoring.connection.form.status=Status
+sebserver.monitoring.connection.form.status.tooltip=The current connection status
+sebserver.monitoring.connection.form.exam=Exam
+sebserver.monitoring.connection.form.exam.tooltip=The exam name
sebserver.monitoring.exam.connection.emptySelection=Please select first a Connection from the list
sebserver.monitoring.exam.connection.emptySelection.active=Please select first an active Connection from the list
@@ -1186,10 +1302,15 @@ sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined
sebserver.monitoring.exam.connection.eventlist.title=Events
sebserver.monitoring.exam.connection.eventlist.empty=No event found
sebserver.monitoring.exam.connection.eventlist.type=Event Type
+sebserver.monitoring.exam.connection.eventlist.type.tooltip=The type of the log event.
Use the filter above to set a specific event type.
{0}
sebserver.monitoring.exam.connection.eventlist.clienttime=Client Time {0}
+sebserver.monitoring.exam.connection.eventlist.clienttime.tooltip=The time the SEB client has sent within the log event.
{0}
sebserver.monitoring.exam.connection.eventlist.servertime=Server Time {0}
+sebserver.monitoring.exam.connection.eventlist.servertime.tooltip=The exact time (UTC) the SEB Server has received the log event.
{0}
sebserver.monitoring.exam.connection.eventlist.value=Value
+sebserver.monitoring.exam.connection.eventlist.value.tooltip=The value of the log event.
{0}
sebserver.monitoring.exam.connection.eventlist.text=Text
+sebserver.monitoring.exam.connection.eventlist.text.tooltip=The text of the log event.
{0}
sebserver.monitoring.exam.connection.event.type.UNKNOWN=Unknown
sebserver.monitoring.exam.connection.event.type.DEBUG_LOG=Debug
@@ -1218,15 +1339,15 @@ sebserver.logs.activity.seblogs.details=Show Details
sebserver.userlogs.list.title=User Activity Logs
sebserver.userlogs.list.column.institution=Institution
-sebserver.userlogs.list.column.institution.tooltip=The institution of the user activity log.Use the filter above to specify the institution.{0}
+sebserver.userlogs.list.column.institution.tooltip=The institution of the user activity log.
Use the filter above to specify the institution.
{0}
sebserver.userlogs.list.column.user=User
-sebserver.userlogs.list.column.user.tooltip=The user account of the user activity log.Use the filter above to specify a user account.{0}
+sebserver.userlogs.list.column.user.tooltip=The user account of the user activity log.
Use the filter above to specify a user account.
{0}
sebserver.userlogs.list.column.dateTime=Date {0}
-sebserver.userlogs.list.column.dateTime.tooltip=The date when the user activity log happened.Use the filter above to specify a from- and to-date range.{0}
+sebserver.userlogs.list.column.dateTime.tooltip=The date when the user activity log happened.
Use the filter above to specify a from- and to-date range.
{0}
sebserver.userlogs.list.column.activityType=User Activity
-sebserver.userlogs.list.column.activityType.tooltip=The type of the user activity.Use the filter above to specify a activity type.{0}
+sebserver.userlogs.list.column.activityType.tooltip=The type of the user activity.
Use the filter above to specify a activity type.
{0}
sebserver.userlogs.list.column.entityType=Domain Type
-sebserver.userlogs.list.column.entityType.tooltip=The domain type of the user activity.Use the filter above to specify a domain type.{0}
+sebserver.userlogs.list.column.entityType.tooltip=The domain type of the user activity.
Use the filter above to specify a domain type.
{0}
sebserver.userlogs.list.column.entityId=Entity-ID
sebserver.userlogs.list.column.message=Message
@@ -1240,13 +1361,19 @@ sebserver.seblogs.list.title=SEB Client Logs
sebserver.seblogs.list.actions=
sebserver.seblogs.list.empty=No SEB client logs has been found. Please adapt or clear the filter
-sebserver.seblogs.info.pleaseSelect=Please select first a SEB Client Log from the list
+sebserver.seblogs.info.pleaseSelect=Please select first a SEB client Log from the list
sebserver.seblogs.list.column.institution=Institution
+sebserver.seblogs.list.column.institution.tooltip=The institution where the exam belongs to.
Use the filter above to specify the institution.
{0}
sebserver.seblogs.list.column.exam=Exam
+sebserver.seblogs.list.column.exam.tooltip=The exam of the SEB client logs.
Use the filter above to specify an exam.
{0}
sebserver.seblogs.list.column.client-session=User Session-ID
+sebserver.seblogs.list.column.client-session.tooltip=The user or user-session identifier.
Use the filter above narrow down a user identifier name.
{0}
sebserver.seblogs.list.column.type=Event Type
+sebserver.seblogs.list.column.type.tooltip=The SEB client log event type.
Use the filter above to specify log type.
{0}
sebserver.seblogs.list.column.time=Event Time {0}
+sebserver.seblogs.list.column.time.tooltip=The SEB client log time.
Use the filter above to specify from- and to-date and time.
{0}
sebserver.seblogs.list.column.value=Value
+sebserver.seblogs.list.column.value.tooltip=The SEB client log value.
{0}
sebserver.seblogs.details.title=SEB Client Log Details
sebserver.seblogs.details.event.title=Event
@@ -1255,19 +1382,34 @@ sebserver.seblogs.details.exam.title=Exam Details
sebserver.seblogs.details.dateTime=Date
sebserver.seblogs.form.column.client-session=Session-ID
+sebserver.seblogs.form.column.client-session.tooltip=The user or user-session identifier.
sebserver.seblogs.form.column.type=Event Type
+sebserver.seblogs.form.column.type.tooltip=The SEB client log event type.
sebserver.seblogs.form.column.server-time=Server Time
+sebserver.seblogs.form.column.server-time.tooltip=The exact time when the SEB Server got the event log sent by an SEB client.
sebserver.seblogs.form.column.client-time=SEB Client Time
+sebserver.seblogs.form.column.client-time.tooltip=The time that was send within the log from SEB client.
sebserver.seblogs.form.column.value=Value
+sebserver.seblogs.form.column.value.tooltip=The SEB client log event value
sebserver.seblogs.form.column.message=Message
+sebserver.seblogs.form.column.message.tooltip=The SEB client log message
sebserver.seblogs.form.column.connection.session-id=User Session-ID
-sebserver.seblogs.form.column.connection.address=SEB Client Address
+sebserver.seblogs.form.column.connection.session-id.tooltip=The user or user-session identifier.
+sebserver.seblogs.form.column.connection.address=SEB client Address
+sebserver.seblogs.form.column.connection.address.tooltip=The IP address of the SEB client
sebserver.seblogs.form.column.connection.token=SEB Connection Token
+sebserver.seblogs.form.column.connection.token.tooltip=The connection token that was generated by the SEB Server to identify the SEB client connection.
sebserver.seblogs.form.column.connection.status=Connection Status
+sebserver.seblogs.form.column.connection.status.tooltip=The current SEB client connection status.
sebserver.seblogs.form.column.exam.name=Name
+sebserver.seblogs.form.column.exam.name.tooltip=The name of the exam.
sebserver.seblogs.form.column.exam.description=Description
+sebserver.seblogs.form.column.exam.description.tooltip=The description of the exam.
sebserver.seblogs.form.column.exam.type=Type
+sebserver.seblogs.form.column.exam.type.tooltip=The type of the exam
sebserver.seblogs.form.column.exam.startTime=Start Time
-sebserver.seblogs.form.column.exam.endTime=End Time
\ No newline at end of file
+sebserver.seblogs.form.column.exam.startTime.tooltip=The start date and time of the exam
+sebserver.seblogs.form.column.exam.endTime=End Time
+sebserver.seblogs.form.column.exam.endTime.tooltip=The end date and time of the exam
\ No newline at end of file