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 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_ENDPOINT = LMS_SETUP_ENDPOINT
|
||||
+ 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 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 SEB_CLIENT_CONFIG_ENDPOINT = "/client_configuration";
|
||||
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_CONFIG_KEY_PATH_SEGMENT = "/configkey";
|
||||
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_COPY_PATH_SEGMENT = "/copy";
|
||||
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 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";
|
||||
|
||||
|
|
|
@ -43,9 +43,12 @@ public class APIMessage implements Serializable {
|
|||
ILLEGAL_API_ARGUMENT("1010", HttpStatus.BAD_REQUEST, "Illegal API request argument"),
|
||||
UNEXPECTED("1100", HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected intenral server-side 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 HttpStatus httpStatus;
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -115,6 +116,7 @@ public final class Exam implements GrantEntity, Activatable {
|
|||
public final String owner;
|
||||
|
||||
@JsonProperty(EXAM.ATTR_SUPPORTER)
|
||||
@NotEmpty(message = "exam:supporter:notNull")
|
||||
public final Collection<String> supporter;
|
||||
|
||||
/** 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 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_DESCRIPTION = "description";
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
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.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.model.Domain;
|
||||
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.event.ActionEvent;
|
||||
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.SebExamConfigDownload;
|
||||
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.DeleteIndicator;
|
||||
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 =
|
||||
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 ResourceService resourceService;
|
||||
private final DownloadService downloadService;
|
||||
private final String downloadFileName;
|
||||
private final WidgetFactory widgetFactory;
|
||||
|
||||
protected ExamForm(
|
||||
final PageService pageService,
|
||||
|
@ -141,15 +156,15 @@ public class ExamForm implements TemplateComposer {
|
|||
this.resourceService = resourceService;
|
||||
this.downloadService = downloadService;
|
||||
this.downloadFileName = downloadFileName;
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
|
||||
final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final boolean readonly = pageContext.isReadonly();
|
||||
|
@ -173,12 +188,20 @@ public class ExamForm implements TemplateComposer {
|
|||
// new PageContext with actual EntityKey
|
||||
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
|
||||
final LocTextKey titleKey = new LocTextKey(
|
||||
importFromQuizData
|
||||
? "sebserver.exam.form.title.import"
|
||||
: "sebserver.exam.form.title");
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
final Composite content = this.widgetFactory.defaultPageLayout(
|
||||
formContext.getParent(),
|
||||
titleKey);
|
||||
|
||||
|
@ -187,9 +210,10 @@ public class ExamForm implements TemplateComposer {
|
|||
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
|
||||
final boolean modifyGrant = userGrantCheck.m();
|
||||
final ExamStatus examStatus = exam.getStatus();
|
||||
final boolean editable = examStatus == ExamStatus.UP_COMING ||
|
||||
examStatus == ExamStatus.RUNNING &&
|
||||
currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
||||
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
|
||||
final boolean editable = examStatus == ExamStatus.UP_COMING
|
||||
|| examStatus == ExamStatus.RUNNING
|
||||
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
||||
|
||||
// The Exam form
|
||||
final FormHandle<Exam> formHandle = this.pageService.formBuilder(
|
||||
|
@ -283,14 +307,14 @@ public class ExamForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.EXAM_CANCEL_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, String.valueOf(importFromQuizData))
|
||||
.withExec(this::cancelModify)
|
||||
.withExec(this.cancelModifyFunction())
|
||||
.publishIf(() -> !readonly);
|
||||
|
||||
// additional data in read-only view
|
||||
if (readonly && !importFromQuizData) {
|
||||
|
||||
// List of SEB Configuration
|
||||
widgetFactory.labelLocalized(
|
||||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
CONFIG_LIST_TITLE_KEY);
|
||||
|
@ -354,6 +378,12 @@ public class ExamForm implements TemplateComposer {
|
|||
getConfigMappingSelection(configurationTable),
|
||||
this::deleteExamConfigMapping,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.withConfirm(() -> {
|
||||
if (isExamRunning) {
|
||||
return CONFIRM_MESSAGE_REMOVE_CONFIG;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
|
||||
|
@ -374,7 +404,7 @@ public class ExamForm implements TemplateComposer {
|
|||
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent());
|
||||
|
||||
// List of Indicators
|
||||
widgetFactory.labelLocalized(
|
||||
this.widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
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) {
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
// TODO find a better way to show a threshold value as text
|
||||
private static String thresholdsValue(final Indicator indicator) {
|
||||
if (indicator.thresholds.isEmpty()) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
|
@ -558,25 +618,31 @@ public class ExamForm implements TemplateComposer {
|
|||
.stream()
|
||||
.reduce(
|
||||
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))
|
||||
.toString();
|
||||
}
|
||||
|
||||
private PageAction cancelModify(final PageAction action) {
|
||||
final boolean importFromQuizData = BooleanUtils.toBoolean(
|
||||
action.pageContext().getAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
||||
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;
|
||||
}
|
||||
private Function<PageAction, PageAction> cancelModifyFunction() {
|
||||
final Function<PageAction, PageAction> backToCurrentFunction = this.pageService.backToCurrentFunction();
|
||||
return action -> {
|
||||
final boolean importFromQuizData = BooleanUtils.toBoolean(
|
||||
|
||||
final PageState lastState = this.pageService.getCurrentState();
|
||||
return lastState.gotoAction;
|
||||
action.pageContext().getAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
||||
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.Function;
|
||||
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.TableItem;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.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.user.UserRole;
|
||||
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.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
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.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
|
@ -133,7 +139,8 @@ public class ExamList implements TemplateComposer {
|
|||
this.pageService.entityTableBuilder(restService.getRestCall(GetExamPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
|
||||
.withRowDecorator(this::decorateOnExamConsistency)
|
||||
|
||||
.withColumnIf(
|
||||
isSebAdmin,
|
||||
() -> new ColumnDefinition<Exam>(
|
||||
|
@ -220,10 +227,27 @@ public class ExamList implements TemplateComposer {
|
|||
|
||||
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) {
|
||||
return exam -> resourceService.getLmsSetupNameFunction()
|
||||
.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;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.rap.rwt.client.service.UrlLauncher;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.slf4j.Logger;
|
||||
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.model.Domain;
|
||||
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.Configuration;
|
||||
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.ConfigurationType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.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.SebExamConfigPlaintextDownload;
|
||||
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.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.SaveExamConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
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 LocTextKey FORM_TITLE_NEW =
|
||||
static final LocTextKey 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");
|
||||
private static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||
static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||
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");
|
||||
private static final LocTextKey FORM_TEMPLATE_TEXT_KEY =
|
||||
static final LocTextKey FORM_TEMPLATE_TEXT_KEY =
|
||||
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");
|
||||
private static final LocTextKey FORM_IMPORT_TEXT_KEY =
|
||||
static final LocTextKey FORM_IMPORT_TEXT_KEY =
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
|
||||
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 RestService restService;
|
||||
private final CurrentUser currentUser;
|
||||
|
@ -127,7 +127,6 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.get(pageContext::notifyError);
|
||||
|
||||
if (examConfig == null) {
|
||||
log.error("Failed to get ConfigurationNode. "
|
||||
+ "Error was notified to the User. "
|
||||
|
@ -139,6 +138,12 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
final boolean writeGrant = entityGrant.w();
|
||||
final boolean modifyGrant = entityGrant.m();
|
||||
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
|
||||
final PageContext formContext = pageContext.withEntityKey(examConfig.getEntityKey());
|
||||
|
@ -151,7 +156,6 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
formContext.getParent(),
|
||||
titleKey);
|
||||
|
||||
// The SebClientConfig form
|
||||
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
|
||||
formContext.copyOf(content), 4)
|
||||
.readonly(isReadonly)
|
||||
|
@ -185,7 +189,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
FORM_STATUS_TEXT_KEY,
|
||||
examConfig.status.name(),
|
||||
resourceService::examConfigStatusResources))
|
||||
() -> resourceService.examConfigStatusResources(isAttachedToExam)))
|
||||
.buildFor((isNew)
|
||||
? this.restService.getRestCall(NewExamConfig.class)
|
||||
: this.restService.getRestCall(SaveExamConfig.class));
|
||||
|
@ -229,7 +233,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_CONFIG)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(SebExamConfigPropForm.importConfigFunction(this.pageService))
|
||||
.withExec(SebExamConfigImport.importConfigFunction(this.pageService))
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
|
@ -237,6 +241,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
|
|||
.withEntityKey(entityKey)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.withConfirm(() -> stateChangeConfirm(isAttachedToExam, formHandle))
|
||||
.publishIf(() -> !isReadonly)
|
||||
|
||||
.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) {
|
||||
final RestService restService = pageService.getResourceService().getRestService();
|
||||
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.sebconfig.Configuration;
|
||||
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.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
@ -112,6 +113,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
.onError(pageContext::notifyError)
|
||||
.getOrThrow();
|
||||
|
||||
final boolean readonly = pageContext.isReadonly() || configNode.status == ConfigurationStatus.IN_USE;
|
||||
final List<View> views = this.examConfigurationService.getViews(attributes);
|
||||
final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content);
|
||||
tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
|
||||
|
@ -124,7 +126,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
view,
|
||||
attributes,
|
||||
20,
|
||||
pageContext.isReadonly());
|
||||
readonly);
|
||||
viewContexts.add(viewContext);
|
||||
|
||||
final Composite viewGrid = this.examConfigurationService.createViewGrid(
|
||||
|
@ -153,7 +155,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
return action;
|
||||
})
|
||||
.withSuccess(KEY_SAVE_TO_HISTORY_SUCCESS)
|
||||
.publishIf(() -> examConfigGrant.iw())
|
||||
.publishIf(() -> examConfigGrant.iw() && !readonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_UNDO)
|
||||
.withEntityKey(entityKey)
|
||||
|
@ -166,7 +168,7 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
|
|||
return action;
|
||||
})
|
||||
.withSuccess(KEY_UNDO_SUCCESS)
|
||||
.publishIf(() -> examConfigGrant.iw())
|
||||
.publishIf(() -> examConfigGrant.iw() && !readonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
||||
.withEntityKey(entityKey)
|
||||
|
|
|
@ -403,6 +403,13 @@ public enum ActionDefinition {
|
|||
ImageIcon.IMPORT,
|
||||
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(
|
||||
new LocTextKey("sebserver.examconfig.action.list.modify"),
|
||||
ImageIcon.EDIT_SETTINGS,
|
||||
|
@ -421,7 +428,7 @@ public enum ActionDefinition {
|
|||
ActionCategory.FORM),
|
||||
|
||||
SEB_EXAM_CONFIG_TEMPLATE_NEW(
|
||||
new LocTextKey("sebserver.exam.configtemplate.action.list.new"),
|
||||
new LocTextKey("sebserver.configtemplate.action.list.new"),
|
||||
ImageIcon.NEW,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_EDIT,
|
||||
ActionCategory.VARIA),
|
||||
|
|
|
@ -365,8 +365,13 @@ public class ResourceService {
|
|||
}
|
||||
|
||||
public List<Tuple<String>> examConfigStatusResources() {
|
||||
return examConfigStatusResources(false);
|
||||
}
|
||||
|
||||
public List<Tuple<String>> examConfigStatusResources(final boolean isAttachedToExam) {
|
||||
return Arrays.asList(ConfigurationStatus.values())
|
||||
.stream()
|
||||
.filter(status -> !isAttachedToExam || status != ConfigurationStatus.READY_TO_USE)
|
||||
.map(type -> new Tuple<>(
|
||||
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,
|
||||
new TypeReference<ConfigurationNode>() {
|
||||
}),
|
||||
HttpMethod.POST,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
HttpMethod.PUT,
|
||||
MediaType.APPLICATION_JSON_UTF8,
|
||||
API.CONFIGURATION_NODE_ENDPOINT
|
||||
+ API.MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.CONFIGURATION_COPY_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
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.auth.CurrentUser;
|
||||
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;
|
||||
|
||||
public class EntityTable<ROW extends Entity> {
|
||||
|
@ -80,6 +82,7 @@ public class EntityTable<ROW extends Entity> {
|
|||
private final Table table;
|
||||
private final TableNavigator navigator;
|
||||
private final MultiValueMap<String, String> staticQueryParams;
|
||||
private final BiConsumer<TableItem, ROW> rowDecorator;
|
||||
|
||||
int pageNumber = 1;
|
||||
int pageSize;
|
||||
|
@ -99,7 +102,8 @@ public class EntityTable<ROW extends Entity> {
|
|||
final LocTextKey emptyMessage,
|
||||
final Function<EntityTable<ROW>, PageAction> defaultActionFunction,
|
||||
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.pageService = pageService;
|
||||
|
@ -120,6 +124,7 @@ public class EntityTable<ROW extends Entity> {
|
|||
GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
|
||||
this.composite.setLayoutData(gridData);
|
||||
this.staticQueryParams = staticQueryParams;
|
||||
this.rowDecorator = rowDecorator;
|
||||
|
||||
// TODO just for debugging, remove when tested
|
||||
// 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);
|
||||
item.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
|
||||
item.setData(TABLE_ROW_DATA, row);
|
||||
if (this.rowDecorator != null) {
|
||||
this.rowDecorator.accept(item, row);
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
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.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.widgets.TableItem;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
|
@ -38,6 +40,7 @@ public class TableBuilder<ROW extends Entity> {
|
|||
private int type = SWT.NONE;
|
||||
private boolean hideNavigation = false;
|
||||
private Function<RestCall<Page<ROW>>.RestCallBuilder, RestCall<Page<ROW>>.RestCallBuilder> restCallAdapter;
|
||||
private BiConsumer<TableItem, ROW> rowDecorator;
|
||||
|
||||
public TableBuilder(
|
||||
final PageService pageService,
|
||||
|
@ -126,6 +129,11 @@ public class TableBuilder<ROW extends Entity> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public TableBuilder<ROW> withRowDecorator(final BiConsumer<TableItem, ROW> rowDecorator) {
|
||||
this.rowDecorator = rowDecorator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EntityTable<ROW> compose(final PageContext pageContext) {
|
||||
return new EntityTable<>(
|
||||
this.type,
|
||||
|
@ -138,7 +146,8 @@ public class TableBuilder<ROW extends Entity> {
|
|||
this.emptyMessage,
|
||||
this.defaultActionFunction,
|
||||
this.hideNavigation,
|
||||
this.staticQueryParams);
|
||||
this.staticQueryParams,
|
||||
this.rowDecorator);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -131,6 +131,7 @@ public class WidgetFactory {
|
|||
|
||||
MESSAGE("message"),
|
||||
ERROR("error"),
|
||||
WARNING("warning"),
|
||||
CONFIG_INPUT_READONLY("inputreadonly")
|
||||
|
||||
;
|
||||
|
@ -218,6 +219,17 @@ public class WidgetFactory {
|
|||
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) {
|
||||
final Button button = new Button(parent, SWT.NONE);
|
||||
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
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.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
|
||||
|
@ -19,7 +20,6 @@ public interface ConfigurationNodeDAO extends
|
|||
Result<ConfigurationNode> createCopy(
|
||||
Long institutionId,
|
||||
String newOwner,
|
||||
Long configurationNodeId,
|
||||
boolean withHistory);
|
||||
ConfigCopyInfo copyInfo);
|
||||
|
||||
}
|
||||
|
|
|
@ -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.EntityType;
|
||||
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.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||
|
@ -208,79 +209,16 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
|
|||
public Result<ConfigurationNode> createCopy(
|
||||
final Long institutionId,
|
||||
final String newOwner,
|
||||
final Long configurationNodeId,
|
||||
final boolean withHistory) {
|
||||
final ConfigCopyInfo copyInfo) {
|
||||
|
||||
return this.recordById(configurationNodeId)
|
||||
return this.recordById(copyInfo.configurationNodeId)
|
||||
.flatMap(nodeRec -> (nodeRec.getInstitutionId().equals(institutionId)
|
||||
? Result.of(nodeRec)
|
||||
: Result.ofError(new IllegalArgumentException("Institution integrity violation"))))
|
||||
.map(nodeRec -> this.copyNodeRecord(nodeRec, newOwner, withHistory))
|
||||
.map(nodeRec -> this.copyNodeRecord(nodeRec, newOwner, copyInfo))
|
||||
.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
|
||||
@Transactional
|
||||
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) {
|
||||
return Result.tryCatch(() -> new ConfigurationNode(
|
||||
record.getId(),
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.function.Predicate;
|
|||
|
||||
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.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -29,6 +30,17 @@ public interface ExamSessionService {
|
|||
* @return the underling ExamDAO service. */
|
||||
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.
|
||||
*
|
||||
* @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.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.NoSuchElementException;
|
||||
|
@ -25,6 +26,8 @@ import org.springframework.context.event.EventListener;
|
|||
import org.springframework.security.access.AccessDeniedException;
|
||||
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.session.ClientConnection;
|
||||
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.ExamDAO;
|
||||
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.session.ExamSessionService;
|
||||
|
||||
|
@ -45,6 +49,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
private static final Logger log = LoggerFactory.getLogger(ExamSessionServiceImpl.class);
|
||||
|
||||
private final ClientConnectionDAO clientConnectionDAO;
|
||||
private final IndicatorDAO indicatorDAO;
|
||||
private final ExamSessionCacheService examSessionCacheService;
|
||||
private final ExamDAO examDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
|
@ -55,6 +60,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
final ExamDAO examDAO,
|
||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||
final ClientConnectionDAO clientConnectionDAO,
|
||||
final IndicatorDAO indicatorDAO,
|
||||
final CacheManager cacheManager) {
|
||||
|
||||
this.examSessionCacheService = examSessionCacheService;
|
||||
|
@ -62,6 +68,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||
this.clientConnectionDAO = clientConnectionDAO;
|
||||
this.cacheManager = cacheManager;
|
||||
this.indicatorDAO = indicatorDAO;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -69,6 +76,40 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
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
|
||||
public boolean isExamRunning(final Long examId) {
|
||||
return !getRunningExam(examId).hasError();
|
||||
|
|
|
@ -62,7 +62,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
|
|||
path = API.ACTIVE_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public Page<T> allActive(
|
||||
@RequestParam(
|
||||
name = Entity.FILTER_ATTR_INSTITUTION,
|
||||
|
@ -90,7 +90,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
|
|||
path = API.INACTIVE_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public Page<T> allInactive(
|
||||
@RequestParam(
|
||||
name = Entity.FILTER_ATTR_INSTITUTION,
|
||||
|
@ -118,7 +118,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
|
|||
path = API.PATH_VAR_ACTIVE,
|
||||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public EntityProcessingReport activate(@PathVariable final String modelId) {
|
||||
return setActive(modelId, true)
|
||||
.getOrThrow();
|
||||
|
@ -128,7 +128,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
|
|||
value = API.PATH_VAR_INACTIVE,
|
||||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public EntityProcessingReport deactivate(@PathVariable final String modelId) {
|
||||
return setActive(modelId, false)
|
||||
.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.api.API;
|
||||
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.sebconfig.Configuration;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||
|
@ -157,7 +159,9 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
.count();
|
||||
|
||||
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 {
|
||||
return Result.of(config);
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ import java.util.List;
|
|||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -28,6 +28,7 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
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.RequestMapping;
|
||||
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.model.Domain.EXAM;
|
||||
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.Configuration;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
|
@ -138,20 +140,18 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_COPY_PATH_SEGMENT,
|
||||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
path = API.CONFIGURATION_COPY_PATH_SEGMENT,
|
||||
method = RequestMethod.PUT,
|
||||
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public ConfigurationNode copyConfiguration(
|
||||
@PathVariable final Long modelId,
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@RequestParam(name = ConfigurationNode.ATTR_COPY_WITH_HISTORY,
|
||||
required = false) final Boolean withHistory) {
|
||||
@Valid @RequestBody final ConfigCopyInfo copyInfo) {
|
||||
|
||||
this.entityDAO.byPK(modelId)
|
||||
this.entityDAO.byPK(copyInfo.configurationNodeId)
|
||||
.flatMap(this.authorization::checkWrite);
|
||||
|
||||
final SEBServerUser currentUser = this.authorization
|
||||
|
@ -161,8 +161,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
return this.configurationNodeDAO.createCopy(
|
||||
institutionId,
|
||||
currentUser.getUserInfo().uuid,
|
||||
modelId,
|
||||
BooleanUtils.toBoolean(withHistory))
|
||||
copyInfo)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
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.lms.LmsAPIService;
|
||||
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;
|
||||
|
||||
@WebServiceProfile
|
||||
|
@ -72,6 +74,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
|||
private final UserDAO userDAO;
|
||||
private final LmsAPIService lmsAPIService;
|
||||
private final SebExamConfigService sebExamConfigService;
|
||||
private final ExamSessionService examSessionService;
|
||||
|
||||
public ExamAdministrationController(
|
||||
final AuthorizationService authorization,
|
||||
|
@ -82,7 +85,8 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
|||
final BeanValidationService beanValidationService,
|
||||
final LmsAPIService lmsAPIService,
|
||||
final UserDAO userDAO,
|
||||
final SebExamConfigService sebExamConfigService) {
|
||||
final SebExamConfigService sebExamConfigService,
|
||||
final ExamSessionService examSessionService) {
|
||||
|
||||
super(authorization,
|
||||
bulkActionService,
|
||||
|
@ -95,6 +99,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
|||
this.userDAO = userDAO;
|
||||
this.lmsAPIService = lmsAPIService;
|
||||
this.sebExamConfigService = sebExamConfigService;
|
||||
this.examSessionService = examSessionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -105,7 +110,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
|
|||
@RequestMapping(
|
||||
method = RequestMethod.GET,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
@Override
|
||||
public Page<Exam> getPage(
|
||||
@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(
|
||||
final Integer pageNumber,
|
||||
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.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.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.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
|
@ -39,6 +43,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
|||
public class ExamConfigurationMappingController extends EntityController<ExamConfigurationMap, ExamConfigurationMap> {
|
||||
|
||||
private final ExamDAO examDao;
|
||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||
|
||||
protected ExamConfigurationMappingController(
|
||||
final AuthorizationService authorization,
|
||||
|
@ -47,7 +52,8 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
|||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ExamDAO examDao) {
|
||||
final ExamDAO examDao,
|
||||
final ConfigurationNodeDAO configurationNodeDAO) {
|
||||
|
||||
super(
|
||||
authorization,
|
||||
|
@ -58,6 +64,7 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
|||
beanValidationService);
|
||||
|
||||
this.examDao = examDao;
|
||||
this.configurationNodeDAO = configurationNodeDAO;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -97,6 +104,7 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
|||
@Override
|
||||
protected Result<ExamConfigurationMap> validForCreate(final ExamConfigurationMap entity) {
|
||||
return super.validForCreate(entity)
|
||||
.map(this::checkConfigurationState)
|
||||
.map(this::checkPasswordMatch);
|
||||
}
|
||||
|
||||
|
@ -106,6 +114,21 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
|||
.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) {
|
||||
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
|
||||
throw new APIMessageException(APIMessage.fieldValidationError(
|
||||
|
@ -118,4 +141,22 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
|
|||
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(
|
||||
path = API.PASSWORD_PATH_SEGMENT,
|
||||
method = RequestMethod.PUT,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public UserInfo changePassword(@Valid @RequestBody final PasswordChange passwordChange) {
|
||||
|
||||
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.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.view=View 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-password=Password
|
||||
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=Exam Configuration
|
||||
|
@ -937,7 +943,7 @@ sebserver.configtemplate.list.actions=Selected Template
|
|||
|
||||
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.view=View Template
|
||||
sebserver.configtemplate.action.list.modify=Edit Template
|
||||
|
|
|
@ -170,6 +170,14 @@ Composite.error {
|
|||
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 {
|
||||
font: bold 12px Arial, Helvetica, sans-serif;
|
||||
color: #FFFFFF;
|
||||
|
@ -740,7 +748,7 @@ TableColumn:hover {
|
|||
background-image: gradient( linear, left top, left bottom, from( #595959 ), to( #595959 ) );
|
||||
}
|
||||
|
||||
TableItem, TableItem:linesvisible:even:rowtemplate {
|
||||
TableItem {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
@ -748,6 +756,12 @@ TableItem, TableItem:linesvisible:even:rowtemplate {
|
|||
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 {
|
||||
background-color: #ffffff;
|
||||
color: inherit;
|
||||
|
@ -759,6 +773,7 @@ Table-RowOverlay {
|
|||
background-image: none;
|
||||
}
|
||||
|
||||
|
||||
Table-RowOverlay:hover {
|
||||
color: #4a4a4a;
|
||||
background-color: #b5b5b5;
|
||||
|
|
Loading…
Add table
Reference in a new issue