SEBSERV-73 made changed to Exam end Exam Config handling
This commit is contained in:
parent
6eaef577d2
commit
83985cdbf7
30 changed files with 844 additions and 259 deletions
|
@ -91,7 +91,7 @@ public final class API {
|
||||||
|
|
||||||
public static final String INSTITUTION_ENDPOINT = "/institution";
|
public static final String INSTITUTION_ENDPOINT = "/institution";
|
||||||
|
|
||||||
public static final String LMS_SETUP_ENDPOINT = "/lms_setup";
|
public static final String LMS_SETUP_ENDPOINT = "/lms-setup";
|
||||||
public static final String LMS_SETUP_TEST_PATH_SEGMENT = "/test";
|
public static final String LMS_SETUP_TEST_PATH_SEGMENT = "/test";
|
||||||
public static final String LMS_SETUP_TEST_ENDPOINT = LMS_SETUP_ENDPOINT
|
public static final String LMS_SETUP_TEST_ENDPOINT = LMS_SETUP_ENDPOINT
|
||||||
+ LMS_SETUP_TEST_PATH_SEGMENT
|
+ LMS_SETUP_TEST_PATH_SEGMENT
|
||||||
|
@ -106,18 +106,19 @@ public final class API {
|
||||||
public static final String QUIZ_DISCOVERY_ENDPOINT = "/quiz";
|
public static final String QUIZ_DISCOVERY_ENDPOINT = "/quiz";
|
||||||
|
|
||||||
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
|
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
|
||||||
public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/downloadConfig";
|
public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config";
|
||||||
|
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency";
|
||||||
|
|
||||||
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
|
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
|
||||||
|
|
||||||
public static final String SEB_CLIENT_CONFIG_ENDPOINT = "/client_configuration";
|
public static final String SEB_CLIENT_CONFIG_ENDPOINT = "/client_configuration";
|
||||||
public static final String SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT = "/download";
|
public static final String SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT = "/download";
|
||||||
|
|
||||||
public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration_node";
|
public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration-node";
|
||||||
public static final String CONFIGURATION_FOLLOWUP_PATH_SEGMENT = "/followup";
|
public static final String CONFIGURATION_FOLLOWUP_PATH_SEGMENT = "/followup";
|
||||||
public static final String CONFIGURATION_CONFIG_KEY_PATH_SEGMENT = "/configkey";
|
public static final String CONFIGURATION_CONFIG_KEY_PATH_SEGMENT = "/configkey";
|
||||||
public static final String CONFIGURATION_ENDPOINT = "/configuration";
|
public static final String CONFIGURATION_ENDPOINT = "/configuration";
|
||||||
public static final String CONFIGURATION_SAVE_TO_HISTORY_PATH_SEGMENT = "/save_to_history";
|
public static final String CONFIGURATION_SAVE_TO_HISTORY_PATH_SEGMENT = "/save-to-history";
|
||||||
public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo";
|
public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo";
|
||||||
public static final String CONFIGURATION_COPY_PATH_SEGMENT = "/copy";
|
public static final String CONFIGURATION_COPY_PATH_SEGMENT = "/copy";
|
||||||
public static final String CONFIGURATION_RESTORE_FROM_HISTORY_PATH_SEGMENT = "/restore";
|
public static final String CONFIGURATION_RESTORE_FROM_HISTORY_PATH_SEGMENT = "/restore";
|
||||||
|
@ -137,7 +138,7 @@ public final class API {
|
||||||
public static final String ORIENTATION_ENDPOINT = "/orientation";
|
public static final String ORIENTATION_ENDPOINT = "/orientation";
|
||||||
public static final String VIEW_ENDPOINT = ORIENTATION_ENDPOINT + "/view";
|
public static final String VIEW_ENDPOINT = ORIENTATION_ENDPOINT + "/view";
|
||||||
|
|
||||||
public static final String EXAM_CONFIGURATION_MAP_ENDPOINT = "/exam_configuration_map";
|
public static final String EXAM_CONFIGURATION_MAP_ENDPOINT = "/exam-configuration-map";
|
||||||
|
|
||||||
public static final String USER_ACTIVITY_LOG_ENDPOINT = "/useractivity";
|
public static final String USER_ACTIVITY_LOG_ENDPOINT = "/useractivity";
|
||||||
|
|
||||||
|
|
|
@ -43,9 +43,12 @@ public class APIMessage implements Serializable {
|
||||||
ILLEGAL_API_ARGUMENT("1010", HttpStatus.BAD_REQUEST, "Illegal API request argument"),
|
ILLEGAL_API_ARGUMENT("1010", HttpStatus.BAD_REQUEST, "Illegal API request argument"),
|
||||||
UNEXPECTED("1100", HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected intenral server-side error"),
|
UNEXPECTED("1100", HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected intenral server-side error"),
|
||||||
FIELD_VALIDATION("1200", HttpStatus.BAD_REQUEST, "Field validation error"),
|
FIELD_VALIDATION("1200", HttpStatus.BAD_REQUEST, "Field validation error"),
|
||||||
PASSWORD_MISMATCH("1300", HttpStatus.BAD_REQUEST, "new password do not match confirmed password")
|
INTEGRITY_VALIDATION("1201", HttpStatus.BAD_REQUEST, "Action would lied to an integrity violation"),
|
||||||
|
PASSWORD_MISMATCH("1300", HttpStatus.BAD_REQUEST, "new password do not match confirmed password"),
|
||||||
|
|
||||||
;
|
EXAM_CONSISTANCY_VALIDATION_SUPPORTER("1400", HttpStatus.OK, "No Exam Supporter defined for the Exam"),
|
||||||
|
EXAM_CONSISTANCY_VALIDATION_CONFIG("1401", HttpStatus.OK, "No SEB Exam Configuration defined for the Exam"),
|
||||||
|
EXAM_CONSISTANCY_VALIDATION_INDICATOR("1402", HttpStatus.OK, "No Indicator defined for the Exam");
|
||||||
|
|
||||||
public final String messageCode;
|
public final String messageCode;
|
||||||
public final HttpStatus httpStatus;
|
public final HttpStatus httpStatus;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -115,6 +116,7 @@ public final class Exam implements GrantEntity, Activatable {
|
||||||
public final String owner;
|
public final String owner;
|
||||||
|
|
||||||
@JsonProperty(EXAM.ATTR_SUPPORTER)
|
@JsonProperty(EXAM.ATTR_SUPPORTER)
|
||||||
|
@NotEmpty(message = "exam:supporter:notNull")
|
||||||
public final Collection<String> supporter;
|
public final Collection<String> supporter;
|
||||||
|
|
||||||
/** Indicates whether this Exam is active or not */
|
/** Indicates whether this Exam is active or not */
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gbl.model.sebconfig;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.Domain.CONFIGURATION_NODE;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
|
|
||||||
|
public final class ConfigCopyInfo implements Entity {
|
||||||
|
|
||||||
|
public static final String ATTR_COPY_WITH_HISTORY = "with-history";
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@JsonProperty(CONFIGURATION_NODE.ATTR_ID)
|
||||||
|
public final Long configurationNodeId;
|
||||||
|
|
||||||
|
@NotNull(message = "configurationNode:name:notNull")
|
||||||
|
@Size(min = 3, max = 255, message = "configurationNode:name:size:{min}:{max}:${validatedValue}")
|
||||||
|
@JsonProperty(CONFIGURATION_NODE.ATTR_NAME)
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
@Size(max = 4000, message = "configurationNode:description:size:{min}:{max}:${validatedValue}")
|
||||||
|
@JsonProperty(CONFIGURATION_NODE.ATTR_DESCRIPTION)
|
||||||
|
public final String description;
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_COPY_WITH_HISTORY)
|
||||||
|
public final Boolean withHistory;
|
||||||
|
|
||||||
|
protected ConfigCopyInfo(
|
||||||
|
@JsonProperty(CONFIGURATION_NODE.ATTR_ID) final Long configurationNodeId,
|
||||||
|
@JsonProperty(CONFIGURATION_NODE.ATTR_NAME) final String name,
|
||||||
|
@JsonProperty(CONFIGURATION_NODE.ATTR_DESCRIPTION) final String description,
|
||||||
|
@JsonProperty(ATTR_COPY_WITH_HISTORY) final Boolean withHistory) {
|
||||||
|
|
||||||
|
this.configurationNodeId = configurationNodeId;
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
this.withHistory = withHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getConfigurationNodeId() {
|
||||||
|
return this.configurationNodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getWithHistory() {
|
||||||
|
return this.withHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModelId() {
|
||||||
|
return (this.configurationNodeId != null)
|
||||||
|
? String.valueOf(this.configurationNodeId)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityType entityType() {
|
||||||
|
return EntityType.CONFIGURATION_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("ConfigCopyInfo [configurationNodeId=");
|
||||||
|
builder.append(this.configurationNodeId);
|
||||||
|
builder.append(", name=");
|
||||||
|
builder.append(this.name);
|
||||||
|
builder.append(", description=");
|
||||||
|
builder.append(this.description);
|
||||||
|
builder.append(", withHistory=");
|
||||||
|
builder.append(this.withHistory);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,7 +25,6 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||||
public final class ConfigurationNode implements GrantEntity {
|
public final class ConfigurationNode implements GrantEntity {
|
||||||
|
|
||||||
public static final Long DEFAULT_TEMPLATE_ID = 0L;
|
public static final Long DEFAULT_TEMPLATE_ID = 0L;
|
||||||
public static final String ATTR_COPY_WITH_HISTORY = "with-history";
|
|
||||||
|
|
||||||
public static final String FILTER_ATTR_TEMPLATE_ID = "templateId";
|
public static final String FILTER_ATTR_TEMPLATE_ID = "templateId";
|
||||||
public static final String FILTER_ATTR_DESCRIPTION = "description";
|
public static final String FILTER_ATTR_DESCRIPTION = "description";
|
||||||
|
|
|
@ -9,10 +9,12 @@
|
||||||
package ch.ethz.seb.sebserver.gui.content;
|
package ch.ethz.seb.sebserver.gui.content;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
@ -28,6 +30,8 @@ import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
@ -53,10 +57,10 @@ 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.TemplateComposer;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
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.download.DownloadService;
|
import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigDownload;
|
import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigDownload;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
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.DeleteExamConfigMapping;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||||
|
@ -126,10 +130,21 @@ public class ExamForm implements TemplateComposer {
|
||||||
private final static LocTextKey INDICATOR_EMPTY_SELECTION_TEXT_KEY =
|
private final static LocTextKey INDICATOR_EMPTY_SELECTION_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
|
new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
|
||||||
|
|
||||||
|
private final static LocTextKey CONSISTENCY_MESSAGE_TITLE =
|
||||||
|
new LocTextKey("sebserver.exam.consistency.title");
|
||||||
|
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SUPPORTER =
|
||||||
|
new LocTextKey("sebserver.exam.consistency.missing-supporter");
|
||||||
|
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_CONFIG =
|
||||||
|
new LocTextKey("sebserver.exam.consistency.missing-config");
|
||||||
|
|
||||||
|
private final static LocTextKey CONFIRM_MESSAGE_REMOVE_CONFIG =
|
||||||
|
new LocTextKey("sebserver.exam.confirm.remove-config");
|
||||||
|
|
||||||
private final PageService pageService;
|
private final PageService pageService;
|
||||||
private final ResourceService resourceService;
|
private final ResourceService resourceService;
|
||||||
private final DownloadService downloadService;
|
private final DownloadService downloadService;
|
||||||
private final String downloadFileName;
|
private final String downloadFileName;
|
||||||
|
private final WidgetFactory widgetFactory;
|
||||||
|
|
||||||
protected ExamForm(
|
protected ExamForm(
|
||||||
final PageService pageService,
|
final PageService pageService,
|
||||||
|
@ -141,15 +156,15 @@ public class ExamForm implements TemplateComposer {
|
||||||
this.resourceService = resourceService;
|
this.resourceService = resourceService;
|
||||||
this.downloadService = downloadService;
|
this.downloadService = downloadService;
|
||||||
this.downloadFileName = downloadFileName;
|
this.downloadFileName = downloadFileName;
|
||||||
|
this.widgetFactory = pageService.getWidgetFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void compose(final PageContext pageContext) {
|
public void compose(final PageContext pageContext) {
|
||||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||||
final RestService restService = this.resourceService.getRestService();
|
final RestService restService = this.resourceService.getRestService();
|
||||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
|
||||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
|
||||||
|
|
||||||
|
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||||
final EntityKey entityKey = pageContext.getEntityKey();
|
final EntityKey entityKey = pageContext.getEntityKey();
|
||||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||||
final boolean readonly = pageContext.isReadonly();
|
final boolean readonly = pageContext.isReadonly();
|
||||||
|
@ -173,12 +188,20 @@ public class ExamForm implements TemplateComposer {
|
||||||
// new PageContext with actual EntityKey
|
// new PageContext with actual EntityKey
|
||||||
final PageContext formContext = pageContext.withEntityKey(exam.getEntityKey());
|
final PageContext formContext = pageContext.withEntityKey(exam.getEntityKey());
|
||||||
|
|
||||||
|
// check exam consistency and inform the user if needed
|
||||||
|
if (readonly) {
|
||||||
|
restService.getBuilder(CheckExamConsistency.class)
|
||||||
|
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||||
|
.call()
|
||||||
|
.ifPresent(result -> showConsistencyChecks(result, formContext.getParent()));
|
||||||
|
}
|
||||||
|
|
||||||
// the default page layout with title
|
// the default page layout with title
|
||||||
final LocTextKey titleKey = new LocTextKey(
|
final LocTextKey titleKey = new LocTextKey(
|
||||||
importFromQuizData
|
importFromQuizData
|
||||||
? "sebserver.exam.form.title.import"
|
? "sebserver.exam.form.title.import"
|
||||||
: "sebserver.exam.form.title");
|
: "sebserver.exam.form.title");
|
||||||
final Composite content = widgetFactory.defaultPageLayout(
|
final Composite content = this.widgetFactory.defaultPageLayout(
|
||||||
formContext.getParent(),
|
formContext.getParent(),
|
||||||
titleKey);
|
titleKey);
|
||||||
|
|
||||||
|
@ -187,9 +210,10 @@ public class ExamForm implements TemplateComposer {
|
||||||
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
|
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
|
||||||
final boolean modifyGrant = userGrantCheck.m();
|
final boolean modifyGrant = userGrantCheck.m();
|
||||||
final ExamStatus examStatus = exam.getStatus();
|
final ExamStatus examStatus = exam.getStatus();
|
||||||
final boolean editable = examStatus == ExamStatus.UP_COMING ||
|
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
|
||||||
examStatus == ExamStatus.RUNNING &&
|
final boolean editable = examStatus == ExamStatus.UP_COMING
|
||||||
currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
|| examStatus == ExamStatus.RUNNING
|
||||||
|
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
||||||
|
|
||||||
// The Exam form
|
// The Exam form
|
||||||
final FormHandle<Exam> formHandle = this.pageService.formBuilder(
|
final FormHandle<Exam> formHandle = this.pageService.formBuilder(
|
||||||
|
@ -283,14 +307,14 @@ public class ExamForm implements TemplateComposer {
|
||||||
.newAction(ActionDefinition.EXAM_CANCEL_MODIFY)
|
.newAction(ActionDefinition.EXAM_CANCEL_MODIFY)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, String.valueOf(importFromQuizData))
|
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, String.valueOf(importFromQuizData))
|
||||||
.withExec(this::cancelModify)
|
.withExec(this.cancelModifyFunction())
|
||||||
.publishIf(() -> !readonly);
|
.publishIf(() -> !readonly);
|
||||||
|
|
||||||
// additional data in read-only view
|
// additional data in read-only view
|
||||||
if (readonly && !importFromQuizData) {
|
if (readonly && !importFromQuizData) {
|
||||||
|
|
||||||
// List of SEB Configuration
|
// List of SEB Configuration
|
||||||
widgetFactory.labelLocalized(
|
this.widgetFactory.labelLocalized(
|
||||||
content,
|
content,
|
||||||
CustomVariant.TEXT_H3,
|
CustomVariant.TEXT_H3,
|
||||||
CONFIG_LIST_TITLE_KEY);
|
CONFIG_LIST_TITLE_KEY);
|
||||||
|
@ -354,6 +378,12 @@ public class ExamForm implements TemplateComposer {
|
||||||
getConfigMappingSelection(configurationTable),
|
getConfigMappingSelection(configurationTable),
|
||||||
this::deleteExamConfigMapping,
|
this::deleteExamConfigMapping,
|
||||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||||
|
.withConfirm(() -> {
|
||||||
|
if (isExamRunning) {
|
||||||
|
return CONFIRM_MESSAGE_REMOVE_CONFIG;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable)
|
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable)
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
|
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
|
||||||
|
@ -374,7 +404,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent());
|
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent());
|
||||||
|
|
||||||
// List of Indicators
|
// List of Indicators
|
||||||
widgetFactory.labelLocalized(
|
this.widgetFactory.labelLocalized(
|
||||||
content,
|
content,
|
||||||
CustomVariant.TEXT_H3,
|
CustomVariant.TEXT_H3,
|
||||||
INDICATOR_LIST_TITLE_KEY);
|
INDICATOR_LIST_TITLE_KEY);
|
||||||
|
@ -432,6 +462,35 @@ public class ExamForm implements TemplateComposer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showConsistencyChecks(final Collection<APIMessage> result, final Composite parent) {
|
||||||
|
if (result == null || result.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Composite warningPanel = this.widgetFactory.createWarningPanel(parent);
|
||||||
|
this.widgetFactory.labelLocalized(
|
||||||
|
warningPanel,
|
||||||
|
CustomVariant.TITLE_LABEL,
|
||||||
|
CONSISTENCY_MESSAGE_TITLE);
|
||||||
|
|
||||||
|
result
|
||||||
|
.stream()
|
||||||
|
.map(message -> {
|
||||||
|
if (message.messageCode.equals(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_SUPPORTER.messageCode)) {
|
||||||
|
return CONSISTENCY_MESSAGE_MISSING_SUPPORTER;
|
||||||
|
} else if (message.messageCode
|
||||||
|
.equals(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_CONFIG.messageCode)) {
|
||||||
|
return CONSISTENCY_MESSAGE_MISSING_CONFIG;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(message -> message != null)
|
||||||
|
.forEach(message -> this.widgetFactory.labelLocalized(
|
||||||
|
warningPanel,
|
||||||
|
CustomVariant.MESSAGE,
|
||||||
|
message));
|
||||||
|
}
|
||||||
|
|
||||||
private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) {
|
private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) {
|
||||||
|
|
||||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(table.getPageContext()
|
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(table.getPageContext()
|
||||||
|
@ -549,6 +608,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
.getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
|
.getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO find a better way to show a threshold value as text
|
||||||
private static String thresholdsValue(final Indicator indicator) {
|
private static String thresholdsValue(final Indicator indicator) {
|
||||||
if (indicator.thresholds.isEmpty()) {
|
if (indicator.thresholds.isEmpty()) {
|
||||||
return Constants.EMPTY_NOTE;
|
return Constants.EMPTY_NOTE;
|
||||||
|
@ -558,25 +618,31 @@ public class ExamForm implements TemplateComposer {
|
||||||
.stream()
|
.stream()
|
||||||
.reduce(
|
.reduce(
|
||||||
new StringBuilder(),
|
new StringBuilder(),
|
||||||
(sb, threshold) -> sb.append(threshold.value).append(":").append(threshold.color).append("|"),
|
(sb, threshold) -> sb.append(threshold.value)
|
||||||
|
.append(":")
|
||||||
|
.append(threshold.color)
|
||||||
|
.append("|"),
|
||||||
(sb1, sb2) -> sb1.append(sb2))
|
(sb1, sb2) -> sb1.append(sb2))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private PageAction cancelModify(final PageAction action) {
|
private Function<PageAction, PageAction> cancelModifyFunction() {
|
||||||
final boolean importFromQuizData = BooleanUtils.toBoolean(
|
final Function<PageAction, PageAction> backToCurrentFunction = this.pageService.backToCurrentFunction();
|
||||||
action.pageContext().getAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
return action -> {
|
||||||
if (importFromQuizData) {
|
final boolean importFromQuizData = BooleanUtils.toBoolean(
|
||||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(action.pageContext());
|
|
||||||
final PageAction activityHomeAction = actionBuilder
|
|
||||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_VIEW_LIST)
|
|
||||||
.create();
|
|
||||||
this.pageService.firePageEvent(new ActionEvent(activityHomeAction), action.pageContext());
|
|
||||||
return activityHomeAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
final PageState lastState = this.pageService.getCurrentState();
|
action.pageContext().getAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
||||||
return lastState.gotoAction;
|
if (importFromQuizData) {
|
||||||
|
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(action.pageContext());
|
||||||
|
final PageAction activityHomeAction = actionBuilder
|
||||||
|
.newAction(ActionDefinition.QUIZ_DISCOVERY_VIEW_LIST)
|
||||||
|
.create();
|
||||||
|
this.pageService.firePageEvent(new ActionEvent(activityHomeAction), action.pageContext());
|
||||||
|
return activityHomeAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return backToCurrentFunction.apply(action);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,18 +11,22 @@ package ch.ethz.seb.sebserver.gui.content;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.eclipse.rap.rwt.RWT;
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.eclipse.swt.widgets.TableItem;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.DateTimeZone;
|
import org.joda.time.DateTimeZone;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
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.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
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;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
@ -38,6 +42,7 @@ 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.TemplateComposer;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
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.RestService;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage;
|
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;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
||||||
|
@ -46,6 +51,7 @@ import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
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;
|
||||||
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
|
@ -133,7 +139,8 @@ public class ExamList implements TemplateComposer {
|
||||||
this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
|
this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
|
||||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||||
.withPaging(this.pageSize)
|
.withPaging(this.pageSize)
|
||||||
|
.withRowDecorator(this::decorateOnExamConsistency)
|
||||||
|
|
||||||
.withColumnIf(
|
.withColumnIf(
|
||||||
isSebAdmin,
|
isSebAdmin,
|
||||||
() -> new ColumnDefinition<Exam>(
|
() -> new ColumnDefinition<Exam>(
|
||||||
|
@ -220,10 +227,27 @@ public class ExamList implements TemplateComposer {
|
||||||
|
|
||||||
return action.withEntityKey(action.getSingleSelection());
|
return action.withEntityKey(action.getSingleSelection());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void decorateOnExamConsistency(TableItem item, Exam exam) {
|
||||||
|
if (exam.getStatus() != ExamStatus.RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.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<Exam, String> examLmsSetupNameFunction(final ResourceService resourceService) {
|
private static Function<Exam, String> examLmsSetupNameFunction(final ResourceService resourceService) {
|
||||||
return exam -> resourceService.getLmsSetupNameFunction()
|
return exam -> resourceService.getLmsSetupNameFunction()
|
||||||
.apply(String.valueOf(exam.lmsSetupId));
|
.apply(String.valueOf(exam.lmsSetupId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* 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.Supplier;
|
||||||
|
|
||||||
|
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.ConfigCopyInfo;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||||
|
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||||
|
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.seb.examconfig.CopyConfiguration;
|
||||||
|
|
||||||
|
public final class SebExamConfigCopy {
|
||||||
|
|
||||||
|
static Function<PageAction, PageAction> importConfigFunction(
|
||||||
|
final PageService pageService,
|
||||||
|
final PageContext pageContext) {
|
||||||
|
|
||||||
|
return action -> {
|
||||||
|
|
||||||
|
final ModalInputDialog<FormHandle<ConfigCopyInfo>> dialog =
|
||||||
|
new ModalInputDialog<FormHandle<ConfigCopyInfo>>(
|
||||||
|
action.pageContext().getParent().getShell(),
|
||||||
|
pageService.getWidgetFactory())
|
||||||
|
.setDialogWidth(600);
|
||||||
|
|
||||||
|
final CopyFormContext formContext = new CopyFormContext(
|
||||||
|
pageService,
|
||||||
|
action.pageContext());
|
||||||
|
|
||||||
|
dialog.open(
|
||||||
|
SebExamConfigPropForm.FORM_COPY_TEXT_KEY,
|
||||||
|
formHandle -> doCopy(
|
||||||
|
pageService,
|
||||||
|
pageContext,
|
||||||
|
formHandle),
|
||||||
|
Utils.EMPTY_EXECUTION,
|
||||||
|
formContext);
|
||||||
|
|
||||||
|
return action;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final void doCopy(
|
||||||
|
final PageService pageService,
|
||||||
|
final PageContext pageContext,
|
||||||
|
final FormHandle<ConfigCopyInfo> formHandle) {
|
||||||
|
|
||||||
|
final ConfigurationNode newConfig = pageService.getRestService().getBuilder(CopyConfiguration.class)
|
||||||
|
.withFormBinding(formHandle.getFormBinding())
|
||||||
|
.call()
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
final PageAction viewNewConfig = pageService.pageActionBuilder(pageContext.copy().clearAttributes())
|
||||||
|
.withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
|
||||||
|
.create();
|
||||||
|
|
||||||
|
pageService.executePageAction(viewNewConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CopyFormContext implements ModalInputDialogComposer<FormHandle<ConfigCopyInfo>> {
|
||||||
|
|
||||||
|
private final PageService pageService;
|
||||||
|
private final PageContext pageContext;
|
||||||
|
|
||||||
|
protected CopyFormContext(final PageService pageService, final PageContext pageContext) {
|
||||||
|
this.pageService = pageService;
|
||||||
|
this.pageContext = pageContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Supplier<FormHandle<ConfigCopyInfo>> compose(final Composite parent) {
|
||||||
|
|
||||||
|
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||||
|
final FormHandle<ConfigCopyInfo> formHandle = this.pageService.formBuilder(
|
||||||
|
this.pageContext.copyOf(parent), 4)
|
||||||
|
.readonly(false)
|
||||||
|
.putStaticValue(
|
||||||
|
Domain.CONFIGURATION_NODE.ATTR_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())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return () -> formHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* 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.io.InputStream;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.eclipse.swt.widgets.Control;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||||
|
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.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.seb.examconfig.ImportExamConfig;
|
||||||
|
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
|
||||||
|
|
||||||
|
public final class SebExamConfigImport {
|
||||||
|
|
||||||
|
static Function<PageAction, PageAction> importConfigFunction(final PageService pageService) {
|
||||||
|
return action -> {
|
||||||
|
|
||||||
|
final ModalInputDialog<FormHandle<ConfigurationNode>> dialog =
|
||||||
|
new ModalInputDialog<FormHandle<ConfigurationNode>>(
|
||||||
|
action.pageContext().getParent().getShell(),
|
||||||
|
pageService.getWidgetFactory())
|
||||||
|
.setDialogWidth(600);
|
||||||
|
|
||||||
|
final ImportFormContext importFormContext = new ImportFormContext(
|
||||||
|
pageService,
|
||||||
|
action.pageContext());
|
||||||
|
|
||||||
|
dialog.open(
|
||||||
|
SebExamConfigPropForm.FORM_IMPORT_TEXT_KEY,
|
||||||
|
formHandle -> doImport(
|
||||||
|
pageService,
|
||||||
|
formHandle),
|
||||||
|
importFormContext::cancelUpload,
|
||||||
|
importFormContext);
|
||||||
|
|
||||||
|
return action;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final void doImport(
|
||||||
|
final PageService pageService,
|
||||||
|
final FormHandle<ConfigurationNode> formHandle) {
|
||||||
|
|
||||||
|
final Form form = formHandle.getForm();
|
||||||
|
final EntityKey entityKey = formHandle.getContext().getEntityKey();
|
||||||
|
final Control fieldControl = form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
|
||||||
|
final PageContext context = formHandle.getContext();
|
||||||
|
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
|
||||||
|
final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl;
|
||||||
|
final InputStream inputStream = fileUpload.getInputStream();
|
||||||
|
if (inputStream != null) {
|
||||||
|
final Configuration configuration = pageService.getRestService()
|
||||||
|
.getBuilder(ImportExamConfig.class)
|
||||||
|
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||||
|
.withHeader(
|
||||||
|
API.IMPORT_PASSWORD_ATTR_NAME,
|
||||||
|
form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME))
|
||||||
|
.withBody(inputStream)
|
||||||
|
.call()
|
||||||
|
.get(e -> {
|
||||||
|
fileUpload.close();
|
||||||
|
return context.notifyError(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configuration != null) {
|
||||||
|
context.publishInfo(SebExamConfigPropForm.FORM_IMPORT_CONFIRM_TEXT_KEY);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formHandle.getContext().publishPageMessage(
|
||||||
|
new LocTextKey("sebserver.error.unexpected"),
|
||||||
|
new LocTextKey("Please selecte a valid SEB Exam Configuration File"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ImportFormContext implements ModalInputDialogComposer<FormHandle<ConfigurationNode>> {
|
||||||
|
|
||||||
|
private final PageService pageService;
|
||||||
|
private final PageContext pageContext;
|
||||||
|
|
||||||
|
private Form form = null;
|
||||||
|
|
||||||
|
protected ImportFormContext(final PageService pageService, final PageContext pageContext) {
|
||||||
|
this.pageService = pageService;
|
||||||
|
this.pageContext = pageContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) {
|
||||||
|
|
||||||
|
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
|
||||||
|
this.pageContext.copyOf(parent), 4)
|
||||||
|
.readonly(false)
|
||||||
|
.addField(FormBuilder.fileUpload(
|
||||||
|
API.IMPORT_FILE_ATTR_NAME,
|
||||||
|
SebExamConfigPropForm.FORM_IMPORT_SELECT_TEXT_KEY,
|
||||||
|
null,
|
||||||
|
API.SEB_FILE_EXTENSION))
|
||||||
|
.addField(FormBuilder.text(
|
||||||
|
API.IMPORT_PASSWORD_ATTR_NAME,
|
||||||
|
SebExamConfigPropForm.FORM_IMPORT_PASSWORD_TEXT_KEY,
|
||||||
|
"").asPasswordField())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.form = formHandle.getForm();
|
||||||
|
return () -> formHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelUpload() {
|
||||||
|
if (this.form != null) {
|
||||||
|
final Control fieldControl = this.form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
|
||||||
|
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
|
||||||
|
((FileUploadSelection) fieldControl).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,15 +8,12 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gui.content;
|
package ch.ethz.seb.sebserver.gui.content;
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import org.eclipse.rap.rwt.RWT;
|
import org.eclipse.rap.rwt.RWT;
|
||||||
import org.eclipse.rap.rwt.client.service.UrlLauncher;
|
import org.eclipse.rap.rwt.client.service.UrlLauncher;
|
||||||
import org.eclipse.swt.SWT;
|
import org.eclipse.swt.SWT;
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import org.eclipse.swt.widgets.Composite;
|
||||||
import org.eclipse.swt.widgets.Control;
|
|
||||||
import org.eclipse.swt.widgets.Text;
|
import org.eclipse.swt.widgets.Text;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -27,20 +24,18 @@ import org.springframework.stereotype.Component;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
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.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
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.FormBuilder;
|
||||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
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.PageContext;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
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.TemplateComposer;
|
||||||
|
@ -49,14 +44,13 @@ import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
|
import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigPlaintextDownload;
|
import ch.ethz.seb.sebserver.gui.service.remote.download.SebExamConfigPlaintextDownload;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingNames;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportConfigKey;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportConfigKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfig;
|
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfig;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfig;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
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.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
|
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||||
|
|
||||||
|
@ -67,29 +61,35 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SebExamConfigPropForm.class);
|
private static final Logger log = LoggerFactory.getLogger(SebExamConfigPropForm.class);
|
||||||
|
|
||||||
private static final LocTextKey FORM_TITLE_NEW =
|
static final LocTextKey FORM_TITLE_NEW =
|
||||||
new LocTextKey("sebserver.examconfig.form.title.new");
|
new LocTextKey("sebserver.examconfig.form.title.new");
|
||||||
private static final LocTextKey FORM_TITLE =
|
static final LocTextKey FORM_TITLE =
|
||||||
new LocTextKey("sebserver.examconfig.form.title");
|
new LocTextKey("sebserver.examconfig.form.title");
|
||||||
private static final LocTextKey FORM_NAME_TEXT_KEY =
|
static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.form.name");
|
new LocTextKey("sebserver.examconfig.form.name");
|
||||||
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.form.description");
|
new LocTextKey("sebserver.examconfig.form.description");
|
||||||
private static final LocTextKey FORM_TEMPLATE_TEXT_KEY =
|
static final LocTextKey FORM_TEMPLATE_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.form.template");
|
new LocTextKey("sebserver.examconfig.form.template");
|
||||||
private static final LocTextKey FORM_STATUS_TEXT_KEY =
|
static final LocTextKey FORM_STATUS_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.form.status");
|
new LocTextKey("sebserver.examconfig.form.status");
|
||||||
private static final LocTextKey FORM_IMPORT_TEXT_KEY =
|
static final LocTextKey FORM_IMPORT_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.action.import-config");
|
new LocTextKey("sebserver.examconfig.action.import-config");
|
||||||
private static final LocTextKey FORM_IMPORT_SELECT_TEXT_KEY =
|
static final LocTextKey FORM_IMPORT_SELECT_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.action.import-file-select");
|
new LocTextKey("sebserver.examconfig.action.import-file-select");
|
||||||
private static final LocTextKey FORM_IMPORT_PASSWORD_TEXT_KEY =
|
static final LocTextKey FORM_IMPORT_PASSWORD_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.action.import-file-password");
|
new LocTextKey("sebserver.examconfig.action.import-file-password");
|
||||||
private static final LocTextKey CONFIG_KEY_TITLE_TEXT_KEY =
|
static final LocTextKey CONFIG_KEY_TITLE_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.form.config-key.title");
|
new LocTextKey("sebserver.examconfig.form.config-key.title");
|
||||||
private static final LocTextKey FORM_IMPORT_CONFIRM_TEXT_KEY =
|
static final LocTextKey FORM_IMPORT_CONFIRM_TEXT_KEY =
|
||||||
new LocTextKey("sebserver.examconfig.action.import-config.confirm");
|
new LocTextKey("sebserver.examconfig.action.import-config.confirm");
|
||||||
|
|
||||||
|
static final LocTextKey FORM_COPY_TEXT_KEY =
|
||||||
|
new LocTextKey("sebserver.examconfig.action.copy");
|
||||||
|
|
||||||
|
static final LocTextKey SAVE_CONFIRM_STATE_CHANGE_WHILE_ATTACHED =
|
||||||
|
new LocTextKey("sebserver.examconfig.action.state-change.confirm");
|
||||||
|
|
||||||
private final PageService pageService;
|
private final PageService pageService;
|
||||||
private final RestService restService;
|
private final RestService restService;
|
||||||
private final CurrentUser currentUser;
|
private final CurrentUser currentUser;
|
||||||
|
@ -127,7 +127,6 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||||
.call()
|
.call()
|
||||||
.get(pageContext::notifyError);
|
.get(pageContext::notifyError);
|
||||||
|
|
||||||
if (examConfig == null) {
|
if (examConfig == null) {
|
||||||
log.error("Failed to get ConfigurationNode. "
|
log.error("Failed to get ConfigurationNode. "
|
||||||
+ "Error was notified to the User. "
|
+ "Error was notified to the User. "
|
||||||
|
@ -139,6 +138,12 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
final boolean writeGrant = entityGrant.w();
|
final boolean writeGrant = entityGrant.w();
|
||||||
final boolean modifyGrant = entityGrant.m();
|
final boolean modifyGrant = entityGrant.m();
|
||||||
final boolean isReadonly = pageContext.isReadonly();
|
final boolean isReadonly = pageContext.isReadonly();
|
||||||
|
final boolean isAttachedToExam = this.restService
|
||||||
|
.getBuilder(GetExamConfigMappingNames.class)
|
||||||
|
.withQueryParam(ExamConfigurationMap.FILTER_ATTR_CONFIG_ID, examConfig.getModelId())
|
||||||
|
.call()
|
||||||
|
.map(names -> names != null && !names.isEmpty())
|
||||||
|
.getOr(Boolean.FALSE);
|
||||||
|
|
||||||
// new PageContext with actual EntityKey
|
// new PageContext with actual EntityKey
|
||||||
final PageContext formContext = pageContext.withEntityKey(examConfig.getEntityKey());
|
final PageContext formContext = pageContext.withEntityKey(examConfig.getEntityKey());
|
||||||
|
@ -151,7 +156,6 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
formContext.getParent(),
|
formContext.getParent(),
|
||||||
titleKey);
|
titleKey);
|
||||||
|
|
||||||
// The SebClientConfig form
|
|
||||||
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
|
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
|
||||||
formContext.copyOf(content), 4)
|
formContext.copyOf(content), 4)
|
||||||
.readonly(isReadonly)
|
.readonly(isReadonly)
|
||||||
|
@ -185,7 +189,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||||
FORM_STATUS_TEXT_KEY,
|
FORM_STATUS_TEXT_KEY,
|
||||||
examConfig.status.name(),
|
examConfig.status.name(),
|
||||||
resourceService::examConfigStatusResources))
|
() -> resourceService.examConfigStatusResources(isAttachedToExam)))
|
||||||
.buildFor((isNew)
|
.buildFor((isNew)
|
||||||
? this.restService.getRestCall(NewExamConfig.class)
|
? this.restService.getRestCall(NewExamConfig.class)
|
||||||
: this.restService.getRestCall(SaveExamConfig.class));
|
: this.restService.getRestCall(SaveExamConfig.class));
|
||||||
|
@ -229,7 +233,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
|
|
||||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_CONFIG)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_CONFIG)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(SebExamConfigPropForm.importConfigFunction(this.pageService))
|
.withExec(SebExamConfigImport.importConfigFunction(this.pageService))
|
||||||
.noEventPropagation()
|
.noEventPropagation()
|
||||||
.publishIf(() -> modifyGrant && isReadonly)
|
.publishIf(() -> modifyGrant && isReadonly)
|
||||||
|
|
||||||
|
@ -237,6 +241,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(formHandle::processFormSave)
|
.withExec(formHandle::processFormSave)
|
||||||
.ignoreMoveAwayFromEdit()
|
.ignoreMoveAwayFromEdit()
|
||||||
|
.withConfirm(() -> stateChangeConfirm(isAttachedToExam, formHandle))
|
||||||
.publishIf(() -> !isReadonly)
|
.publishIf(() -> !isReadonly)
|
||||||
|
|
||||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY)
|
||||||
|
@ -246,6 +251,26 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LocTextKey stateChangeConfirm(
|
||||||
|
final boolean isAttachedToExam,
|
||||||
|
final FormHandle<ConfigurationNode> formHandle) {
|
||||||
|
|
||||||
|
if (isAttachedToExam) {
|
||||||
|
final String fieldValue = formHandle
|
||||||
|
.getForm()
|
||||||
|
.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS);
|
||||||
|
|
||||||
|
if (fieldValue != null) {
|
||||||
|
final ConfigurationStatus state = ConfigurationStatus.valueOf(fieldValue);
|
||||||
|
if (state != ConfigurationStatus.IN_USE) {
|
||||||
|
return SAVE_CONFIRM_STATE_CHANGE_WHILE_ATTACHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static Function<PageAction, PageAction> getConfigKeyFunction(final PageService pageService) {
|
public static Function<PageAction, PageAction> getConfigKeyFunction(final PageService pageService) {
|
||||||
final RestService restService = pageService.getResourceService().getRestService();
|
final RestService restService = pageService.getResourceService().getRestService();
|
||||||
return action -> {
|
return action -> {
|
||||||
|
@ -281,108 +306,4 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Function<PageAction, PageAction> importConfigFunction(final PageService pageService) {
|
|
||||||
return action -> {
|
|
||||||
|
|
||||||
final ModalInputDialog<FormHandle<ConfigurationNode>> dialog =
|
|
||||||
new ModalInputDialog<FormHandle<ConfigurationNode>>(
|
|
||||||
action.pageContext().getParent().getShell(),
|
|
||||||
pageService.getWidgetFactory())
|
|
||||||
.setDialogWidth(600);
|
|
||||||
|
|
||||||
final ImportFormContext importFormContext = new ImportFormContext(
|
|
||||||
pageService,
|
|
||||||
action.pageContext());
|
|
||||||
|
|
||||||
dialog.open(
|
|
||||||
FORM_IMPORT_TEXT_KEY,
|
|
||||||
formHandle -> SebExamConfigPropForm.doImport(
|
|
||||||
pageService,
|
|
||||||
formHandle),
|
|
||||||
importFormContext::cancelUpload,
|
|
||||||
importFormContext);
|
|
||||||
|
|
||||||
return action;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final void doImport(
|
|
||||||
final PageService pageService,
|
|
||||||
final FormHandle<ConfigurationNode> formHandle) {
|
|
||||||
|
|
||||||
final Form form = formHandle.getForm();
|
|
||||||
final EntityKey entityKey = formHandle.getContext().getEntityKey();
|
|
||||||
final Control fieldControl = form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
|
|
||||||
final PageContext context = formHandle.getContext();
|
|
||||||
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
|
|
||||||
final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl;
|
|
||||||
final InputStream inputStream = fileUpload.getInputStream();
|
|
||||||
if (inputStream != null) {
|
|
||||||
final Configuration configuration = pageService.getRestService()
|
|
||||||
.getBuilder(ImportExamConfig.class)
|
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
|
||||||
.withHeader(
|
|
||||||
API.IMPORT_PASSWORD_ATTR_NAME,
|
|
||||||
form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME))
|
|
||||||
.withBody(inputStream)
|
|
||||||
.call()
|
|
||||||
.get(e -> {
|
|
||||||
fileUpload.close();
|
|
||||||
return context.notifyError(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (configuration != null) {
|
|
||||||
context.publishInfo(FORM_IMPORT_CONFIRM_TEXT_KEY);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
formHandle.getContext().publishPageMessage(
|
|
||||||
new LocTextKey("sebserver.error.unexpected"),
|
|
||||||
new LocTextKey("Please selecte a valid SEB Exam Configuration File"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class ImportFormContext implements ModalInputDialogComposer<FormHandle<ConfigurationNode>> {
|
|
||||||
|
|
||||||
private final PageService pageService;
|
|
||||||
private final PageContext pageContext;
|
|
||||||
|
|
||||||
private Form form = null;
|
|
||||||
|
|
||||||
protected ImportFormContext(final PageService pageService, final PageContext pageContext) {
|
|
||||||
this.pageService = pageService;
|
|
||||||
this.pageContext = pageContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) {
|
|
||||||
|
|
||||||
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
|
|
||||||
this.pageContext.copyOf(parent), 4)
|
|
||||||
.readonly(false)
|
|
||||||
.addField(FormBuilder.fileUpload(
|
|
||||||
API.IMPORT_FILE_ATTR_NAME,
|
|
||||||
FORM_IMPORT_SELECT_TEXT_KEY,
|
|
||||||
null,
|
|
||||||
API.SEB_FILE_EXTENSION))
|
|
||||||
.addField(FormBuilder.text(
|
|
||||||
API.IMPORT_PASSWORD_ATTR_NAME,
|
|
||||||
FORM_IMPORT_PASSWORD_TEXT_KEY,
|
|
||||||
"").asPasswordField())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
this.form = formHandle.getForm();
|
|
||||||
return () -> formHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cancelUpload() {
|
|
||||||
if (this.form != null) {
|
|
||||||
final Control fieldControl = this.form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
|
|
||||||
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
|
|
||||||
((FileUploadSelection) fieldControl).close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
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.sebconfig.View;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
@ -112,6 +113,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
||||||
.onError(pageContext::notifyError)
|
.onError(pageContext::notifyError)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
final boolean readonly = pageContext.isReadonly() || configNode.status == ConfigurationStatus.IN_USE;
|
||||||
final List<View> views = this.examConfigurationService.getViews(attributes);
|
final List<View> views = this.examConfigurationService.getViews(attributes);
|
||||||
final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content);
|
final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content);
|
||||||
tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
|
tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
|
||||||
|
@ -124,7 +126,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
||||||
view,
|
view,
|
||||||
attributes,
|
attributes,
|
||||||
20,
|
20,
|
||||||
pageContext.isReadonly());
|
readonly);
|
||||||
viewContexts.add(viewContext);
|
viewContexts.add(viewContext);
|
||||||
|
|
||||||
final Composite viewGrid = this.examConfigurationService.createViewGrid(
|
final Composite viewGrid = this.examConfigurationService.createViewGrid(
|
||||||
|
@ -153,7 +155,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
||||||
return action;
|
return action;
|
||||||
})
|
})
|
||||||
.withSuccess(KEY_SAVE_TO_HISTORY_SUCCESS)
|
.withSuccess(KEY_SAVE_TO_HISTORY_SUCCESS)
|
||||||
.publishIf(() -> examConfigGrant.iw())
|
.publishIf(() -> examConfigGrant.iw() && !readonly)
|
||||||
|
|
||||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_UNDO)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_UNDO)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
|
@ -166,7 +168,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
||||||
return action;
|
return action;
|
||||||
})
|
})
|
||||||
.withSuccess(KEY_UNDO_SUCCESS)
|
.withSuccess(KEY_UNDO_SUCCESS)
|
||||||
.publishIf(() -> examConfigGrant.iw())
|
.publishIf(() -> examConfigGrant.iw() && !readonly)
|
||||||
|
|
||||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
|
|
|
@ -403,6 +403,13 @@ public enum ActionDefinition {
|
||||||
ImageIcon.IMPORT,
|
ImageIcon.IMPORT,
|
||||||
ActionCategory.FORM),
|
ActionCategory.FORM),
|
||||||
|
|
||||||
|
// TODO copy config action
|
||||||
|
// TODO
|
||||||
|
SEB_EXAM_CONFIG_COPY_CONFIG(
|
||||||
|
new LocTextKey("sebserver.examconfig.action.copy-config"),
|
||||||
|
ImageIcon.IMPORT,
|
||||||
|
ActionCategory.FORM),
|
||||||
|
|
||||||
SEB_EXAM_CONFIG_MODIFY_FROM_LIST(
|
SEB_EXAM_CONFIG_MODIFY_FROM_LIST(
|
||||||
new LocTextKey("sebserver.examconfig.action.list.modify"),
|
new LocTextKey("sebserver.examconfig.action.list.modify"),
|
||||||
ImageIcon.EDIT_SETTINGS,
|
ImageIcon.EDIT_SETTINGS,
|
||||||
|
@ -421,7 +428,7 @@ public enum ActionDefinition {
|
||||||
ActionCategory.FORM),
|
ActionCategory.FORM),
|
||||||
|
|
||||||
SEB_EXAM_CONFIG_TEMPLATE_NEW(
|
SEB_EXAM_CONFIG_TEMPLATE_NEW(
|
||||||
new LocTextKey("sebserver.exam.configtemplate.action.list.new"),
|
new LocTextKey("sebserver.configtemplate.action.list.new"),
|
||||||
ImageIcon.NEW,
|
ImageIcon.NEW,
|
||||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_EDIT,
|
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_EDIT,
|
||||||
ActionCategory.VARIA),
|
ActionCategory.VARIA),
|
||||||
|
|
|
@ -365,8 +365,13 @@ public class ResourceService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Tuple<String>> examConfigStatusResources() {
|
public List<Tuple<String>> examConfigStatusResources() {
|
||||||
|
return examConfigStatusResources(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Tuple<String>> examConfigStatusResources(final boolean isAttachedToExam) {
|
||||||
return Arrays.asList(ConfigurationStatus.values())
|
return Arrays.asList(ConfigurationStatus.values())
|
||||||
.stream()
|
.stream()
|
||||||
|
.filter(status -> !isAttachedToExam || status != ConfigurationStatus.READY_TO_USE)
|
||||||
.map(type -> new Tuple<>(
|
.map(type -> new Tuple<>(
|
||||||
type.name(),
|
type.name(),
|
||||||
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name())))
|
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name())))
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
@GuiProfile
|
||||||
|
public class CheckExamConsistency extends RestCall<Collection<APIMessage>> {
|
||||||
|
|
||||||
|
public CheckExamConsistency() {
|
||||||
|
super(new TypeKey<>(
|
||||||
|
CallType.UNDEFINED,
|
||||||
|
EntityType.EXAM,
|
||||||
|
new TypeReference<Collection<APIMessage>>() {
|
||||||
|
}),
|
||||||
|
HttpMethod.GET,
|
||||||
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
|
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||||
|
+ API.MODEL_ID_VAR_PATH_SEGMENT
|
||||||
|
+ API.EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -32,10 +32,9 @@ public class CopyConfiguration extends RestCall<ConfigurationNode> {
|
||||||
EntityType.CONFIGURATION_NODE,
|
EntityType.CONFIGURATION_NODE,
|
||||||
new TypeReference<ConfigurationNode>() {
|
new TypeReference<ConfigurationNode>() {
|
||||||
}),
|
}),
|
||||||
HttpMethod.POST,
|
HttpMethod.PUT,
|
||||||
MediaType.APPLICATION_FORM_URLENCODED,
|
MediaType.APPLICATION_JSON_UTF8,
|
||||||
API.CONFIGURATION_NODE_ENDPOINT
|
API.CONFIGURATION_NODE_ENDPOINT
|
||||||
+ API.MODEL_ID_VAR_PATH_SEGMENT
|
|
||||||
+ API.CONFIGURATION_COPY_PATH_SEGMENT);
|
+ API.CONFIGURATION_COPY_PATH_SEGMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
@ -54,6 +55,7 @@ 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.RestCall;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||||
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
|
||||||
|
|
||||||
public class EntityTable<ROW extends Entity> {
|
public class EntityTable<ROW extends Entity> {
|
||||||
|
@ -80,6 +82,7 @@ public class EntityTable<ROW extends Entity> {
|
||||||
private final Table table;
|
private final Table table;
|
||||||
private final TableNavigator navigator;
|
private final TableNavigator navigator;
|
||||||
private final MultiValueMap<String, String> staticQueryParams;
|
private final MultiValueMap<String, String> staticQueryParams;
|
||||||
|
private final BiConsumer<TableItem, ROW> rowDecorator;
|
||||||
|
|
||||||
int pageNumber = 1;
|
int pageNumber = 1;
|
||||||
int pageSize;
|
int pageSize;
|
||||||
|
@ -99,7 +102,8 @@ public class EntityTable<ROW extends Entity> {
|
||||||
final LocTextKey emptyMessage,
|
final LocTextKey emptyMessage,
|
||||||
final Function<EntityTable<ROW>, PageAction> defaultActionFunction,
|
final Function<EntityTable<ROW>, PageAction> defaultActionFunction,
|
||||||
final boolean hideNavigation,
|
final boolean hideNavigation,
|
||||||
final MultiValueMap<String, String> staticQueryParams) {
|
final MultiValueMap<String, String> staticQueryParams,
|
||||||
|
final BiConsumer<TableItem, ROW> rowDecorator) {
|
||||||
|
|
||||||
this.composite = new Composite(pageContext.getParent(), type);
|
this.composite = new Composite(pageContext.getParent(), type);
|
||||||
this.pageService = pageService;
|
this.pageService = pageService;
|
||||||
|
@ -120,6 +124,7 @@ public class EntityTable<ROW extends Entity> {
|
||||||
GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
|
GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
|
||||||
this.composite.setLayoutData(gridData);
|
this.composite.setLayoutData(gridData);
|
||||||
this.staticQueryParams = staticQueryParams;
|
this.staticQueryParams = staticQueryParams;
|
||||||
|
this.rowDecorator = rowDecorator;
|
||||||
|
|
||||||
// TODO just for debugging, remove when tested
|
// TODO just for debugging, remove when tested
|
||||||
// this.composite.setBackground(new Color(parent.getDisplay(), new RGB(0, 200, 0)));
|
// this.composite.setBackground(new Color(parent.getDisplay(), new RGB(0, 200, 0)));
|
||||||
|
@ -404,6 +409,9 @@ public class EntityTable<ROW extends Entity> {
|
||||||
final TableItem item = new TableItem(this.table, SWT.NONE);
|
final TableItem item = new TableItem(this.table, SWT.NONE);
|
||||||
item.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
|
item.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
|
||||||
item.setData(TABLE_ROW_DATA, row);
|
item.setData(TABLE_ROW_DATA, row);
|
||||||
|
if (this.rowDecorator != null) {
|
||||||
|
this.rowDecorator.accept(item, row);
|
||||||
|
}
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (final ColumnDefinition<ROW> column : this.columns) {
|
for (final ColumnDefinition<ROW> column : this.columns) {
|
||||||
|
|
|
@ -10,11 +10,13 @@ package ch.ethz.seb.sebserver.gui.table;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.eclipse.swt.SWT;
|
import org.eclipse.swt.SWT;
|
||||||
|
import org.eclipse.swt.widgets.TableItem;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
@ -38,6 +40,7 @@ public class TableBuilder<ROW extends Entity> {
|
||||||
private int type = SWT.NONE;
|
private int type = SWT.NONE;
|
||||||
private boolean hideNavigation = false;
|
private boolean hideNavigation = false;
|
||||||
private Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter;
|
private Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter;
|
||||||
|
private BiConsumer<TableItem, ROW> rowDecorator;
|
||||||
|
|
||||||
public TableBuilder(
|
public TableBuilder(
|
||||||
final PageService pageService,
|
final PageService pageService,
|
||||||
|
@ -126,6 +129,11 @@ public class TableBuilder<ROW extends Entity> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TableBuilder<ROW> withRowDecorator(final BiConsumer<TableItem, ROW> rowDecorator) {
|
||||||
|
this.rowDecorator = rowDecorator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public EntityTable<ROW> compose(final PageContext pageContext) {
|
public EntityTable<ROW> compose(final PageContext pageContext) {
|
||||||
return new EntityTable<>(
|
return new EntityTable<>(
|
||||||
this.type,
|
this.type,
|
||||||
|
@ -138,7 +146,8 @@ public class TableBuilder<ROW extends Entity> {
|
||||||
this.emptyMessage,
|
this.emptyMessage,
|
||||||
this.defaultActionFunction,
|
this.defaultActionFunction,
|
||||||
this.hideNavigation,
|
this.hideNavigation,
|
||||||
this.staticQueryParams);
|
this.staticQueryParams,
|
||||||
|
this.rowDecorator);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,7 @@ public class WidgetFactory {
|
||||||
|
|
||||||
MESSAGE("message"),
|
MESSAGE("message"),
|
||||||
ERROR("error"),
|
ERROR("error"),
|
||||||
|
WARNING("warning"),
|
||||||
CONFIG_INPUT_READONLY("inputreadonly")
|
CONFIG_INPUT_READONLY("inputreadonly")
|
||||||
|
|
||||||
;
|
;
|
||||||
|
@ -218,6 +219,17 @@ public class WidgetFactory {
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Composite createWarningPanel(final Composite parent) {
|
||||||
|
final Composite composite = new Composite(parent, SWT.NONE);
|
||||||
|
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
|
||||||
|
final GridLayout gridLayout = new GridLayout(1, true);
|
||||||
|
gridLayout.marginWidth = 20;
|
||||||
|
gridLayout.marginHeight = 20;
|
||||||
|
composite.setLayout(gridLayout);
|
||||||
|
composite.setData(RWT.CUSTOM_VARIANT, CustomVariant.WARNING.key);
|
||||||
|
return composite;
|
||||||
|
}
|
||||||
|
|
||||||
public Button buttonLocalized(final Composite parent, final String locTextKey) {
|
public Button buttonLocalized(final Composite parent, final String locTextKey) {
|
||||||
final Button button = new Button(parent, SWT.NONE);
|
final Button button = new Button(parent, SWT.NONE);
|
||||||
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
|
||||||
|
@ -19,7 +20,6 @@ public interface ConfigurationNodeDAO extends
|
||||||
Result<ConfigurationNode> createCopy(
|
Result<ConfigurationNode> createCopy(
|
||||||
Long institutionId,
|
Long institutionId,
|
||||||
String newOwner,
|
String newOwner,
|
||||||
Long configurationNodeId,
|
ConfigCopyInfo copyInfo);
|
||||||
boolean withHistory);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
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.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||||
|
@ -208,79 +209,16 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
||||||
public Result<ConfigurationNode> createCopy(
|
public Result<ConfigurationNode> createCopy(
|
||||||
final Long institutionId,
|
final Long institutionId,
|
||||||
final String newOwner,
|
final String newOwner,
|
||||||
final Long configurationNodeId,
|
final ConfigCopyInfo copyInfo) {
|
||||||
final boolean withHistory) {
|
|
||||||
|
|
||||||
return this.recordById(configurationNodeId)
|
return this.recordById(copyInfo.configurationNodeId)
|
||||||
.flatMap(nodeRec -> (nodeRec.getInstitutionId().equals(institutionId)
|
.flatMap(nodeRec -> (nodeRec.getInstitutionId().equals(institutionId)
|
||||||
? Result.of(nodeRec)
|
? Result.of(nodeRec)
|
||||||
: Result.ofError(new IllegalArgumentException("Institution integrity violation"))))
|
: Result.ofError(new IllegalArgumentException("Institution integrity violation"))))
|
||||||
.map(nodeRec -> this.copyNodeRecord(nodeRec, newOwner, withHistory))
|
.map(nodeRec -> this.copyNodeRecord(nodeRec, newOwner, copyInfo))
|
||||||
.flatMap(ConfigurationNodeDAOImpl::toDomainModel);
|
.flatMap(ConfigurationNodeDAOImpl::toDomainModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConfigurationNodeRecord copyNodeRecord(
|
|
||||||
final ConfigurationNodeRecord nodeRec,
|
|
||||||
final String newOwner,
|
|
||||||
final boolean withHistory) {
|
|
||||||
|
|
||||||
final ConfigurationNodeRecord newNodeRec = new ConfigurationNodeRecord(
|
|
||||||
null,
|
|
||||||
nodeRec.getInstitutionId(),
|
|
||||||
nodeRec.getTemplateId(),
|
|
||||||
StringUtils.isNotBlank(newOwner) ? newOwner : nodeRec.getOwner(),
|
|
||||||
this.copyNamePrefix + nodeRec.getName() + this.copyNameSuffix,
|
|
||||||
nodeRec.getDescription(),
|
|
||||||
nodeRec.getType(),
|
|
||||||
ConfigurationStatus.CONSTRUCTION.name());
|
|
||||||
this.configurationNodeRecordMapper.insert(newNodeRec);
|
|
||||||
|
|
||||||
final List<ConfigurationRecord> configs = this.configurationRecordMapper
|
|
||||||
.selectByExample()
|
|
||||||
.where(
|
|
||||||
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
|
|
||||||
isEqualTo(nodeRec.getId()))
|
|
||||||
.build()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
if (withHistory) {
|
|
||||||
configs
|
|
||||||
.stream()
|
|
||||||
.forEach(configRec -> this.configurationDAOBatchService.copyConfiguration(
|
|
||||||
configRec.getInstitutionId(),
|
|
||||||
configRec.getId(),
|
|
||||||
newNodeRec.getId()));
|
|
||||||
} else {
|
|
||||||
configs
|
|
||||||
.stream()
|
|
||||||
.filter(configRec -> configRec.getVersionDate() == null)
|
|
||||||
.findFirst()
|
|
||||||
.map(configRec -> {
|
|
||||||
// No history means to create a first version and a follow-up with the copied values
|
|
||||||
final ConfigurationRecord newFirstVersion = new ConfigurationRecord(
|
|
||||||
null,
|
|
||||||
configRec.getInstitutionId(),
|
|
||||||
configRec.getConfigurationNodeId(),
|
|
||||||
ConfigurationDAOBatchService.INITIAL_VERSION_NAME,
|
|
||||||
DateTime.now(DateTimeZone.UTC),
|
|
||||||
BooleanUtils.toInteger(false));
|
|
||||||
this.configurationRecordMapper.insert(newFirstVersion);
|
|
||||||
this.configurationDAOBatchService.copyValues(
|
|
||||||
configRec.getInstitutionId(),
|
|
||||||
configRec.getId(),
|
|
||||||
newFirstVersion.getId());
|
|
||||||
// and copy the follow-up
|
|
||||||
this.configurationDAOBatchService.copyConfiguration(
|
|
||||||
configRec.getInstitutionId(),
|
|
||||||
configRec.getId(),
|
|
||||||
newNodeRec.getId());
|
|
||||||
return configRec;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return newNodeRec;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
|
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
|
||||||
|
@ -345,6 +283,68 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConfigurationNodeRecord copyNodeRecord(
|
||||||
|
final ConfigurationNodeRecord nodeRec,
|
||||||
|
final String newOwner,
|
||||||
|
final ConfigCopyInfo copyInfo) {
|
||||||
|
|
||||||
|
final ConfigurationNodeRecord newNodeRec = new ConfigurationNodeRecord(
|
||||||
|
null,
|
||||||
|
nodeRec.getInstitutionId(),
|
||||||
|
nodeRec.getTemplateId(),
|
||||||
|
StringUtils.isNotBlank(newOwner) ? newOwner : nodeRec.getOwner(),
|
||||||
|
this.copyNamePrefix + nodeRec.getName() + this.copyNameSuffix,
|
||||||
|
nodeRec.getDescription(),
|
||||||
|
nodeRec.getType(),
|
||||||
|
ConfigurationStatus.CONSTRUCTION.name());
|
||||||
|
this.configurationNodeRecordMapper.insert(newNodeRec);
|
||||||
|
|
||||||
|
final List<ConfigurationRecord> configs = this.configurationRecordMapper
|
||||||
|
.selectByExample()
|
||||||
|
.where(
|
||||||
|
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
|
||||||
|
isEqualTo(nodeRec.getId()))
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
if (BooleanUtils.toBoolean(copyInfo.withHistory)) {
|
||||||
|
configs
|
||||||
|
.stream()
|
||||||
|
.forEach(configRec -> this.configurationDAOBatchService.copyConfiguration(
|
||||||
|
configRec.getInstitutionId(),
|
||||||
|
configRec.getId(),
|
||||||
|
newNodeRec.getId()));
|
||||||
|
} else {
|
||||||
|
configs
|
||||||
|
.stream()
|
||||||
|
.filter(configRec -> configRec.getVersionDate() == null)
|
||||||
|
.findFirst()
|
||||||
|
.map(configRec -> {
|
||||||
|
// No history means to create a first version and a follow-up with the copied values
|
||||||
|
final ConfigurationRecord newFirstVersion = new ConfigurationRecord(
|
||||||
|
null,
|
||||||
|
configRec.getInstitutionId(),
|
||||||
|
configRec.getConfigurationNodeId(),
|
||||||
|
ConfigurationDAOBatchService.INITIAL_VERSION_NAME,
|
||||||
|
DateTime.now(DateTimeZone.UTC),
|
||||||
|
BooleanUtils.toInteger(false));
|
||||||
|
this.configurationRecordMapper.insert(newFirstVersion);
|
||||||
|
this.configurationDAOBatchService.copyValues(
|
||||||
|
configRec.getInstitutionId(),
|
||||||
|
configRec.getId(),
|
||||||
|
newFirstVersion.getId());
|
||||||
|
// and copy the follow-up
|
||||||
|
this.configurationDAOBatchService.copyConfiguration(
|
||||||
|
configRec.getInstitutionId(),
|
||||||
|
configRec.getId(),
|
||||||
|
newNodeRec.getId());
|
||||||
|
return configRec;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNodeRec;
|
||||||
|
}
|
||||||
|
|
||||||
static Result<ConfigurationNode> toDomainModel(final ConfigurationNodeRecord record) {
|
static Result<ConfigurationNode> toDomainModel(final ConfigurationNodeRecord record) {
|
||||||
return Result.tryCatch(() -> new ConfigurationNode(
|
return Result.tryCatch(() -> new ConfigurationNode(
|
||||||
record.getId(),
|
record.getId(),
|
||||||
|
|
|
@ -14,6 +14,7 @@ import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
@ -29,6 +30,17 @@ public interface ExamSessionService {
|
||||||
* @return the underling ExamDAO service. */
|
* @return the underling ExamDAO service. */
|
||||||
ExamDAO getExamDAO();
|
ExamDAO getExamDAO();
|
||||||
|
|
||||||
|
/** Use this to check the consistency of a running Exam.
|
||||||
|
* Current consistency checks are:
|
||||||
|
* - Check if there is at least one Exam supporter attached to the Exam
|
||||||
|
* - Check if there is one default SEB Exam Configuration attached to the Exam
|
||||||
|
* - Check if there is at least one Indicator defined for the monitoring of the Exam
|
||||||
|
*
|
||||||
|
* @param examId the identifier of the Exam to check
|
||||||
|
* @return Result of one APIMessage per consistency check if the check failed. An empty Collection of everything is
|
||||||
|
* okay. */
|
||||||
|
Result<Collection<APIMessage>> checkRunningExamConsystency(Long examId);
|
||||||
|
|
||||||
/** Indicates whether an Exam is currently running or not.
|
/** Indicates whether an Exam is currently running or not.
|
||||||
*
|
*
|
||||||
* @param examId the PK of the Exam to test
|
* @param examId the PK of the Exam to test
|
||||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
@ -25,6 +26,8 @@ import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.stereotype.Service;
|
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;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
|
@ -34,6 +37,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
|
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.dao.ExamDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||||
|
|
||||||
|
@ -45,6 +49,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ExamSessionServiceImpl.class);
|
private static final Logger log = LoggerFactory.getLogger(ExamSessionServiceImpl.class);
|
||||||
|
|
||||||
private final ClientConnectionDAO clientConnectionDAO;
|
private final ClientConnectionDAO clientConnectionDAO;
|
||||||
|
private final IndicatorDAO indicatorDAO;
|
||||||
private final ExamSessionCacheService examSessionCacheService;
|
private final ExamSessionCacheService examSessionCacheService;
|
||||||
private final ExamDAO examDAO;
|
private final ExamDAO examDAO;
|
||||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||||
|
@ -55,6 +60,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
final ExamDAO examDAO,
|
final ExamDAO examDAO,
|
||||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||||
final ClientConnectionDAO clientConnectionDAO,
|
final ClientConnectionDAO clientConnectionDAO,
|
||||||
|
final IndicatorDAO indicatorDAO,
|
||||||
final CacheManager cacheManager) {
|
final CacheManager cacheManager) {
|
||||||
|
|
||||||
this.examSessionCacheService = examSessionCacheService;
|
this.examSessionCacheService = examSessionCacheService;
|
||||||
|
@ -62,6 +68,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||||
this.clientConnectionDAO = clientConnectionDAO;
|
this.clientConnectionDAO = clientConnectionDAO;
|
||||||
this.cacheManager = cacheManager;
|
this.cacheManager = cacheManager;
|
||||||
|
this.indicatorDAO = indicatorDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -69,6 +76,40 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
return this.examDAO;
|
return this.examDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Collection<APIMessage>> checkRunningExamConsystency(final Long examId) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
final Collection<APIMessage> result = new ArrayList<>();
|
||||||
|
|
||||||
|
if (isExamRunning(examId)) {
|
||||||
|
final Exam exam = getRunningExam(examId)
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
// check exam supporter
|
||||||
|
if (exam.getSupporter().isEmpty()) {
|
||||||
|
result.add(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_SUPPORTER.of(exam.getModelId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check SEB configuration
|
||||||
|
this.examConfigurationMapDAO.getDefaultConfigurationForExam(examId)
|
||||||
|
.get(t -> {
|
||||||
|
result.add(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_CONFIG.of(exam.getModelId()));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// check indicator exists
|
||||||
|
if (this.indicatorDAO.allForExam(examId)
|
||||||
|
.getOrThrow()
|
||||||
|
.isEmpty()) {
|
||||||
|
|
||||||
|
result.add(ErrorMessage.EXAM_CONSISTANCY_VALIDATION_INDICATOR.of(exam.getModelId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isExamRunning(final Long examId) {
|
public boolean isExamRunning(final Long examId) {
|
||||||
return !getRunningExam(examId).hasError();
|
return !getRunningExam(examId).hasError();
|
||||||
|
|
|
@ -62,7 +62,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
|
||||||
path = API.ACTIVE_PATH_SEGMENT,
|
path = API.ACTIVE_PATH_SEGMENT,
|
||||||
method = RequestMethod.GET,
|
method = RequestMethod.GET,
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
public Page<T> allActive(
|
public Page<T> allActive(
|
||||||
@RequestParam(
|
@RequestParam(
|
||||||
name = Entity.FILTER_ATTR_INSTITUTION,
|
name = Entity.FILTER_ATTR_INSTITUTION,
|
||||||
|
@ -90,7 +90,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
|
||||||
path = API.INACTIVE_PATH_SEGMENT,
|
path = API.INACTIVE_PATH_SEGMENT,
|
||||||
method = RequestMethod.GET,
|
method = RequestMethod.GET,
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
public Page<T> allInactive(
|
public Page<T> allInactive(
|
||||||
@RequestParam(
|
@RequestParam(
|
||||||
name = Entity.FILTER_ATTR_INSTITUTION,
|
name = Entity.FILTER_ATTR_INSTITUTION,
|
||||||
|
@ -118,7 +118,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
|
||||||
path = API.PATH_VAR_ACTIVE,
|
path = API.PATH_VAR_ACTIVE,
|
||||||
method = RequestMethod.POST,
|
method = RequestMethod.POST,
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
public EntityProcessingReport activate(@PathVariable final String modelId) {
|
public EntityProcessingReport activate(@PathVariable final String modelId) {
|
||||||
return setActive(modelId, true)
|
return setActive(modelId, true)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
@ -128,7 +128,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
|
||||||
value = API.PATH_VAR_INACTIVE,
|
value = API.PATH_VAR_INACTIVE,
|
||||||
method = RequestMethod.POST,
|
method = RequestMethod.POST,
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
public EntityProcessingReport deactivate(@PathVariable final String modelId) {
|
public EntityProcessingReport deactivate(@PathVariable final String modelId) {
|
||||||
return setActive(modelId, false)
|
return setActive(modelId, false)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
|
@ -22,6 +22,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
|
@ -157,7 +159,9 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
if (activeConnections > 0) {
|
if (activeConnections > 0) {
|
||||||
throw new IllegalStateException("Integrity violation: There are currently active SEB Client connection.");
|
throw new APIMessage.APIMessageException(
|
||||||
|
ErrorMessage.INTEGRITY_VALIDATION,
|
||||||
|
"Integrity violation: There are currently active SEB Client connection.");
|
||||||
} else {
|
} else {
|
||||||
return Result.of(config);
|
return Result.of(config);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,9 @@ import java.util.List;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
|
||||||
import org.mybatis.dynamic.sql.SqlTable;
|
import org.mybatis.dynamic.sql.SqlTable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -28,6 +28,7 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
@ -40,6 +41,7 @@ import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
|
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
|
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCopyInfo;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||||
|
@ -138,20 +140,18 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_COPY_PATH_SEGMENT,
|
path = API.CONFIGURATION_COPY_PATH_SEGMENT,
|
||||||
method = RequestMethod.POST,
|
method = RequestMethod.PUT,
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
|
||||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
public ConfigurationNode copyConfiguration(
|
public ConfigurationNode copyConfiguration(
|
||||||
@PathVariable final Long modelId,
|
|
||||||
@RequestParam(
|
@RequestParam(
|
||||||
name = API.PARAM_INSTITUTION_ID,
|
name = API.PARAM_INSTITUTION_ID,
|
||||||
required = true,
|
required = true,
|
||||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||||
@RequestParam(name = ConfigurationNode.ATTR_COPY_WITH_HISTORY,
|
@Valid @RequestBody final ConfigCopyInfo copyInfo) {
|
||||||
required = false) final Boolean withHistory) {
|
|
||||||
|
|
||||||
this.entityDAO.byPK(modelId)
|
this.entityDAO.byPK(copyInfo.configurationNodeId)
|
||||||
.flatMap(this.authorization::checkWrite);
|
.flatMap(this.authorization::checkWrite);
|
||||||
|
|
||||||
final SEBServerUser currentUser = this.authorization
|
final SEBServerUser currentUser = this.authorization
|
||||||
|
@ -161,8 +161,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
||||||
return this.configurationNodeDAO.createCopy(
|
return this.configurationNodeDAO.createCopy(
|
||||||
institutionId,
|
institutionId,
|
||||||
currentUser.getUserInfo().uuid,
|
currentUser.getUserInfo().uuid,
|
||||||
modelId,
|
copyInfo)
|
||||||
BooleanUtils.toBoolean(withHistory))
|
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -59,6 +60,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||||
|
|
||||||
@WebServiceProfile
|
@WebServiceProfile
|
||||||
|
@ -72,6 +74,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
||||||
private final UserDAO userDAO;
|
private final UserDAO userDAO;
|
||||||
private final LmsAPIService lmsAPIService;
|
private final LmsAPIService lmsAPIService;
|
||||||
private final SebExamConfigService sebExamConfigService;
|
private final SebExamConfigService sebExamConfigService;
|
||||||
|
private final ExamSessionService examSessionService;
|
||||||
|
|
||||||
public ExamAdministrationController(
|
public ExamAdministrationController(
|
||||||
final AuthorizationService authorization,
|
final AuthorizationService authorization,
|
||||||
|
@ -82,7 +85,8 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
||||||
final BeanValidationService beanValidationService,
|
final BeanValidationService beanValidationService,
|
||||||
final LmsAPIService lmsAPIService,
|
final LmsAPIService lmsAPIService,
|
||||||
final UserDAO userDAO,
|
final UserDAO userDAO,
|
||||||
final SebExamConfigService sebExamConfigService) {
|
final SebExamConfigService sebExamConfigService,
|
||||||
|
final ExamSessionService examSessionService) {
|
||||||
|
|
||||||
super(authorization,
|
super(authorization,
|
||||||
bulkActionService,
|
bulkActionService,
|
||||||
|
@ -95,6 +99,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
||||||
this.userDAO = userDAO;
|
this.userDAO = userDAO;
|
||||||
this.lmsAPIService = lmsAPIService;
|
this.lmsAPIService = lmsAPIService;
|
||||||
this.sebExamConfigService = sebExamConfigService;
|
this.sebExamConfigService = sebExamConfigService;
|
||||||
|
this.examSessionService = examSessionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -105,7 +110,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
method = RequestMethod.GET,
|
method = RequestMethod.GET,
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
@Override
|
@Override
|
||||||
public Page<Exam> getPage(
|
public Page<Exam> getPage(
|
||||||
@RequestParam(
|
@RequestParam(
|
||||||
|
@ -187,6 +192,17 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping(
|
||||||
|
path = API.MODEL_ID_VAR_PATH_SEGMENT
|
||||||
|
+ API.EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT,
|
||||||
|
method = RequestMethod.GET,
|
||||||
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
|
public Collection<APIMessage> checkExamConsistency(@PathVariable final Long modelId) {
|
||||||
|
return this.examSessionService
|
||||||
|
.checkRunningExamConsystency(modelId)
|
||||||
|
.getOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
public static Page<Exam> buildSortedExamPage(
|
public static Page<Exam> buildSortedExamPage(
|
||||||
final Integer pageNumber,
|
final Integer pageNumber,
|
||||||
final Integer pageSize,
|
final Integer pageSize,
|
||||||
|
|
|
@ -16,11 +16,14 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
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.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.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
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.model.user.PasswordChange;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
@ -28,6 +31,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfiguration
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
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.AuthorizationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
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.EntityDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
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.dao.UserActivityLogDAO;
|
||||||
|
@ -39,6 +43,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
||||||
public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> {
|
public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> {
|
||||||
|
|
||||||
private final ExamDAO examDao;
|
private final ExamDAO examDao;
|
||||||
|
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||||
|
|
||||||
protected ExamConfigurationMappingController(
|
protected ExamConfigurationMappingController(
|
||||||
final AuthorizationService authorization,
|
final AuthorizationService authorization,
|
||||||
|
@ -47,7 +52,8 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
||||||
final UserActivityLogDAO userActivityLogDAO,
|
final UserActivityLogDAO userActivityLogDAO,
|
||||||
final PaginationService paginationService,
|
final PaginationService paginationService,
|
||||||
final BeanValidationService beanValidationService,
|
final BeanValidationService beanValidationService,
|
||||||
final ExamDAO examDao) {
|
final ExamDAO examDao,
|
||||||
|
final ConfigurationNodeDAO configurationNodeDAO) {
|
||||||
|
|
||||||
super(
|
super(
|
||||||
authorization,
|
authorization,
|
||||||
|
@ -58,6 +64,7 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
||||||
beanValidationService);
|
beanValidationService);
|
||||||
|
|
||||||
this.examDao = examDao;
|
this.examDao = examDao;
|
||||||
|
this.configurationNodeDAO = configurationNodeDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -97,6 +104,7 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
||||||
@Override
|
@Override
|
||||||
protected Result<ExamConfigurationMap> validForCreate(final ExamConfigurationMap entity) {
|
protected Result<ExamConfigurationMap> validForCreate(final ExamConfigurationMap entity) {
|
||||||
return super.validForCreate(entity)
|
return super.validForCreate(entity)
|
||||||
|
.map(this::checkConfigurationState)
|
||||||
.map(this::checkPasswordMatch);
|
.map(this::checkPasswordMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +114,21 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
||||||
.map(this::checkPasswordMatch);
|
.map(this::checkPasswordMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<ExamConfigurationMap> notifyCreated(final ExamConfigurationMap entity) {
|
||||||
|
// update the attached configurations state to "In Use"
|
||||||
|
return this.configurationNodeDAO.save(new ConfigurationNode(
|
||||||
|
entity.configurationNodeId,
|
||||||
|
entity.institutionId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
ConfigurationStatus.IN_USE))
|
||||||
|
.map(config -> entity);
|
||||||
|
}
|
||||||
|
|
||||||
private ExamConfigurationMap checkPasswordMatch(final ExamConfigurationMap entity) {
|
private ExamConfigurationMap checkPasswordMatch(final ExamConfigurationMap entity) {
|
||||||
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
|
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
|
||||||
throw new APIMessageException(APIMessage.fieldValidationError(
|
throw new APIMessageException(APIMessage.fieldValidationError(
|
||||||
|
@ -118,4 +141,22 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
||||||
return entity;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,8 +130,8 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
path = API.PASSWORD_PATH_SEGMENT,
|
path = API.PASSWORD_PATH_SEGMENT,
|
||||||
method = RequestMethod.PUT,
|
method = RequestMethod.PUT,
|
||||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
|
||||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
public UserInfo changePassword(@Valid @RequestBody final PasswordChange passwordChange) {
|
public UserInfo changePassword(@Valid @RequestBody final PasswordChange passwordChange) {
|
||||||
|
|
||||||
final String modelId = passwordChange.getModelId();
|
final String modelId = passwordChange.getModelId();
|
||||||
|
|
|
@ -288,6 +288,11 @@ sebserver.exam.list.empty=No Exam has been found. Please adapt the filter or imp
|
||||||
sebserver.exam.list.modify.out.dated=Finished exams cannot be modified.
|
sebserver.exam.list.modify.out.dated=Finished exams cannot be modified.
|
||||||
sebserver.exam.list.action.no.modify.privilege=No Access: An Exam from other institution cannot be modified.
|
sebserver.exam.list.action.no.modify.privilege=No Access: An Exam from other institution cannot be modified.
|
||||||
|
|
||||||
|
sebserver.exam.consistency.title=Note: This exam is already running but has some missing configurations
|
||||||
|
sebserver.exam.consistency.missing-supporter= - There currently are no Exam-Supporter defined for this exam. Edit the exam to add an Exam-Supporter
|
||||||
|
sebserver.exam.consistency.missing-config= - There is currently no exam-configuration defined for this exam. Use 'Add Configuration' to attach one
|
||||||
|
sebserver.exam.confirm.remove-config=This exam is current running. The remove of the attached configuration will led to an invalid state<br/>where connecting SEB clients cannot download the configuration for the exam.<br/><br/>Are you sure to remove the Configuration?
|
||||||
|
|
||||||
sebserver.exam.action.list=Exam
|
sebserver.exam.action.list=Exam
|
||||||
sebserver.exam.action.list.view=View Exam
|
sebserver.exam.action.list.view=View Exam
|
||||||
sebserver.exam.action.list.modify=Edit Exam
|
sebserver.exam.action.list.modify=Edit Exam
|
||||||
|
@ -451,6 +456,7 @@ sebserver.examconfig.action.import-config=Import Configuration
|
||||||
sebserver.examconfig.action.import-file-select=Import From File
|
sebserver.examconfig.action.import-file-select=Import From File
|
||||||
sebserver.examconfig.action.import-file-password=Password
|
sebserver.examconfig.action.import-file-password=Password
|
||||||
sebserver.examconfig.action.import-config.confirm=Configuration successfully imported
|
sebserver.examconfig.action.import-config.confirm=Configuration successfully imported
|
||||||
|
sebserver.examconfig.action.state-change.confirm=This configuration is already attached to an exam.<br/>Please note that changing an attached configuration will take effect on the exam when the configuration changes are saved<br/><br/>Are you sure to change this configuration to an editable state?
|
||||||
|
|
||||||
sebserver.examconfig.form.title.new=Add Exam Configuration
|
sebserver.examconfig.form.title.new=Add Exam Configuration
|
||||||
sebserver.examconfig.form.title=Exam Configuration
|
sebserver.examconfig.form.title=Exam Configuration
|
||||||
|
@ -937,7 +943,7 @@ sebserver.configtemplate.list.actions=Selected Template
|
||||||
|
|
||||||
sebserver.configtemplate.info.pleaseSelect=Please select an exam configuration template first
|
sebserver.configtemplate.info.pleaseSelect=Please select an exam configuration template first
|
||||||
|
|
||||||
sebserver.exam.configtemplate.action.list.new=Add Template
|
sebserver.configtemplate.action.list.new=Add Template
|
||||||
sebserver.configtemplate.action.list.view=View Template
|
sebserver.configtemplate.action.list.view=View Template
|
||||||
sebserver.configtemplate.action.view=View Template
|
sebserver.configtemplate.action.view=View Template
|
||||||
sebserver.configtemplate.action.list.modify=Edit Template
|
sebserver.configtemplate.action.list.modify=Edit Template
|
||||||
|
|
|
@ -170,6 +170,14 @@ Composite.error {
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Composite.warning {
|
||||||
|
background-gradient-color: rgba( 168, 50, 45, 0.5 );
|
||||||
|
background-image: gradient( linear, left top, left bottom, from(rgba( 168, 50, 45, 0.5 ) ), to( rgba( 168, 50, 45, 0.5 ) ) );
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-position: left top;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
*.header {
|
*.header {
|
||||||
font: bold 12px Arial, Helvetica, sans-serif;
|
font: bold 12px Arial, Helvetica, sans-serif;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
|
@ -740,7 +748,7 @@ TableColumn:hover {
|
||||||
background-image: gradient( linear, left top, left bottom, from( #595959 ), to( #595959 ) );
|
background-image: gradient( linear, left top, left bottom, from( #595959 ), to( #595959 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
TableItem, TableItem:linesvisible:even:rowtemplate {
|
TableItem {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -748,6 +756,12 @@ TableItem, TableItem:linesvisible:even:rowtemplate {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Table-RowOverlay.warning {
|
||||||
|
background-color: rgba( 168, 50, 45, 0.5 );
|
||||||
|
background-gradient-color: rgba( 168, 50, 45, 0.5 );
|
||||||
|
background-image: gradient( linear, left top, left bottom, from(rgba( 168, 50, 45, 0.5 ) ), to( rgba( 168, 50, 45, 0.5 ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
TableItem:linesvisible:even {
|
TableItem:linesvisible:even {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
@ -759,6 +773,7 @@ Table-RowOverlay {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Table-RowOverlay:hover {
|
Table-RowOverlay:hover {
|
||||||
color: #4a4a4a;
|
color: #4a4a4a;
|
||||||
background-color: #b5b5b5;
|
background-color: #b5b5b5;
|
||||||
|
|
Loading…
Add table
Reference in a new issue