SEBSERV-139 gui implementations

This commit is contained in:
anhefti 2020-08-05 16:55:19 +02:00
parent 6f9daddf53
commit ec1630fa2f
18 changed files with 911 additions and 397 deletions

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.gbl.model.exam;
import org.apache.commons.lang3.BooleanUtils;
import org.hibernate.validator.constraints.URL;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@ -19,7 +20,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Entity;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ExamProctoring implements Entity {
public class ProctoringSettings implements Entity {
public enum ServerType {
JITSI_MEET
@ -33,19 +34,25 @@ public class ExamProctoring implements Entity {
@JsonProperty(Domain.EXAM.ATTR_ID)
public final Long examId;
@JsonProperty(ATTR_ENABLE_PROCTORING)
public final Boolean enableProctoring;
@JsonProperty(ATTR_SERVER_TYPE)
public final ServerType serverType;
@JsonProperty(ATTR_SERVER_URL)
@URL(message = "examProctoring:serverURL:invalidURL")
public final String serverURL;
@JsonProperty(ATTR_APP_KEY)
public final String appKey;
@JsonProperty(ATTR_APP_SECRET)
public final CharSequence appSecret;
@JsonCreator
public ExamProctoring(
public ProctoringSettings(
@JsonProperty(Domain.EXAM.ATTR_ID) final Long examId,
@JsonProperty(ATTR_ENABLE_PROCTORING) final Boolean enableProctoring,
@JsonProperty(ATTR_SERVER_TYPE) final ServerType serverType,
@ -117,7 +124,7 @@ public class ExamProctoring implements Entity {
return false;
if (getClass() != obj.getClass())
return false;
final ExamProctoring other = (ExamProctoring) obj;
final ProctoringSettings other = (ProctoringSettings) obj;
if (this.examId == null) {
if (other.examId != null)
return false;

View file

@ -8,27 +8,20 @@
package ch.ethz.seb.sebserver.gui.content;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.UrlLauncher;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@ -40,15 +33,12 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult.ErrorType;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle;
@ -63,15 +53,10 @@ 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.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.CheckSEBRestriction;
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;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
@ -79,8 +64,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@ -91,14 +74,15 @@ public class ExamForm implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(ExamForm.class);
protected static final String ATTR_READ_GRANT = "ATTR_READ_GRANT";
protected static final String ATTR_MODIFY_GRANT = "ATTR_MODIFY_GRANT";
protected static final String ATTR_EXAM_STATUS = "ATTR_EXAM_STATUS";
public static final LocTextKey EXAM_FORM_TITLE_KEY =
new LocTextKey("sebserver.exam.form.title");
public static final LocTextKey EXAM_FORM_TITLE_IMPORT_KEY =
new LocTextKey("sebserver.exam.form.title.import");
private static final LocTextKey CONFIG_EMPTY_LIST_MESSAGE =
new LocTextKey("sebserver.exam.configuration.list.empty");
private static final LocTextKey INDICATOR_EMPTY_LIST_MESSAGE =
new LocTextKey("sebserver.exam.indicator.list.empty");
private static final LocTextKey FORM_SUPPORTER_TEXT_KEY =
new LocTextKey("sebserver.exam.form.supporter");
private static final LocTextKey FORM_STATUS_TEXT_KEY =
@ -120,32 +104,6 @@ public class ExamForm implements TemplateComposer {
private static final LocTextKey FORM_LMSSETUP_TEXT_KEY =
new LocTextKey("sebserver.exam.form.lmssetup");
private final static LocTextKey CONFIG_LIST_TITLE_KEY =
new LocTextKey("sebserver.exam.configuration.list.title");
private final static LocTextKey CONFIG_LIST_TITLE_TOOLTIP_KEY =
new LocTextKey("sebserver.exam.configuration.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private final static LocTextKey CONFIG_NAME_COLUMN_KEY =
new LocTextKey("sebserver.exam.configuration.list.column.name");
private final static LocTextKey CONFIG_DESCRIPTION_COLUMN_KEY =
new LocTextKey("sebserver.exam.configuration.list.column.description");
private final static LocTextKey CONFIG_STATUS_COLUMN_KEY =
new LocTextKey("sebserver.exam.configuration.list.column.status");
private final static LocTextKey CONFIG_EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.exam.configuration.list.pleaseSelect");
private final static LocTextKey INDICATOR_LIST_TITLE_KEY =
new LocTextKey("sebserver.exam.indicator.list.title");
private final static LocTextKey INDICATOR_LIST_TITLE_TOOLTIP_KEY =
new LocTextKey("sebserver.exam.indicator.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private final static LocTextKey INDICATOR_TYPE_COLUMN_KEY =
new LocTextKey("sebserver.exam.indicator.list.column.type");
private final static LocTextKey INDICATOR_NAME_COLUMN_KEY =
new LocTextKey("sebserver.exam.indicator.list.column.name");
private final static LocTextKey INDICATOR_THRESHOLD_COLUMN_KEY =
new LocTextKey("sebserver.exam.indicator.list.column.thresholds");
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 =
@ -158,19 +116,14 @@ public class ExamForm implements TemplateComposer {
new LocTextKey("sebserver.exam.consistency.missing-seb-restriction");
private final Map<String, LocTextKey> consistencyMessageMapping;
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 ExamSEBRestrictionSettings examSEBRestrictionSettings;
private final ExamToConfigBindingForm examToConfigBindingForm;
private final DownloadService downloadService;
private final String downloadFileName;
private final WidgetFactory widgetFactory;
private final RestService restService;
private final ExamDeletePopup examDeletePopup;
private final ExamFormConfigs examFormConfigs;
private final ExamFormIndicators examFormIndicators;
protected ExamForm(
final PageService pageService,
@ -178,17 +131,17 @@ public class ExamForm implements TemplateComposer {
final ExamToConfigBindingForm examToConfigBindingForm,
final DownloadService downloadService,
final ExamDeletePopup examDeletePopup,
@Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) {
final ExamFormConfigs examFormConfigs,
final ExamFormIndicators examFormIndicators) {
this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.examSEBRestrictionSettings = examSEBRestrictionSettings;
this.examToConfigBindingForm = examToConfigBindingForm;
this.downloadService = downloadService;
this.downloadFileName = downloadFileName;
this.widgetFactory = pageService.getWidgetFactory();
this.restService = this.resourceService.getRestService();
this.examDeletePopup = examDeletePopup;
this.examFormConfigs = examFormConfigs;
this.examFormIndicators = examFormIndicators;
this.consistencyMessageMapping = new HashMap<>();
this.consistencyMessageMapping.put(
@ -255,7 +208,6 @@ public class ExamForm implements TemplateComposer {
final boolean modifyGrant = userGrantCheck.m();
final boolean writeGrant = userGrantCheck.w();
final ExamStatus examStatus = exam.getStatus();
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
final boolean editable = examStatus == ExamStatus.UP_COMING
|| examStatus == ExamStatus.RUNNING
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
@ -439,165 +391,21 @@ public class ExamForm implements TemplateComposer {
// additional data in read-only view
if (readonly && !importFromQuizData) {
// Configurations
this.examFormConfigs.compose(
formContext
.copyOf(content)
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
.withAttribute(ATTR_MODIFY_GRANT, String.valueOf(modifyGrant))
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
// List of SEB Configuration
this.widgetFactory.addFormSubContextHeader(
content,
CONFIG_LIST_TITLE_KEY,
CONFIG_LIST_TITLE_TOOLTIP_KEY);
final EntityTable<ExamConfigurationMap> configurationTable =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetExamConfigMappingsPage.class))
.withRestCallAdapter(builder -> builder.withQueryParam(
ExamConfigurationMap.FILTER_ATTR_EXAM_ID,
entityKey.modelId))
.withEmptyMessage(CONFIG_EMPTY_LIST_MESSAGE)
.withPaging(1)
.hideNavigation()
.withColumn(new ColumnDefinition<>(
Domain.CONFIGURATION_NODE.ATTR_NAME,
CONFIG_NAME_COLUMN_KEY,
ExamConfigurationMap::getConfigName)
.widthProportion(2))
.withColumn(new ColumnDefinition<>(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
CONFIG_DESCRIPTION_COLUMN_KEY,
ExamConfigurationMap::getConfigDescription)
.widthProportion(4))
.withColumn(new ColumnDefinition<ExamConfigurationMap>(
Domain.CONFIGURATION_NODE.ATTR_STATUS,
CONFIG_STATUS_COLUMN_KEY,
this.resourceService::localizedExamConfigStatusName)
.widthProportion(1))
.withDefaultActionIf(
() -> modifyGrant,
this::viewExamConfigPageAction)
.withSelectionListener(this.pageService.getSelectionPublisher(
pageContext,
ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP,
ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST,
ActionDefinition.EXAM_CONFIGURATION_EXPORT,
ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY))
.compose(pageContext.copyOf(content));
final EntityKey configMapKey = (configurationTable.hasAnyContent())
? new EntityKey(
configurationTable.getFirstRowData().configurationNodeId,
EntityType.CONFIGURATION_NODE)
: null;
actionBuilder
.newAction(ActionDefinition.EXAM_CONFIGURATION_NEW)
.withParentEntityKey(entityKey)
.withExec(this.examToConfigBindingForm.bindFunction())
.noEventPropagation()
.publishIf(() -> modifyGrant && editable && !configurationTable.hasAnyContent())
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
.withParentEntityKey(entityKey)
.withEntityKey(configMapKey)
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
.withEntityKey(entityKey)
.withSelect(
getConfigMappingSelection(configurationTable),
this::deleteExamConfigMapping,
CONFIG_EMPTY_SELECTION_TEXT_KEY)
.withConfirm(() -> {
if (isExamRunning) {
return CONFIRM_MESSAGE_REMOVE_CONFIG;
}
return null;
})
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable, false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
.withParentEntityKey(entityKey)
.withSelect(
getConfigSelection(configurationTable),
this::downloadExamConfigAction,
CONFIG_EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation()
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
.withSelect(
getConfigSelection(configurationTable),
this::getExamConfigKey,
CONFIG_EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation()
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent(), false);
// List of Indicators
this.widgetFactory.addFormSubContextHeader(
content,
INDICATOR_LIST_TITLE_KEY,
INDICATOR_LIST_TITLE_TOOLTIP_KEY);
final EntityTable<Indicator> indicatorTable =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetIndicatorPage.class))
.withRestCallAdapter(builder -> builder.withQueryParam(
Indicator.FILTER_ATTR_EXAM_ID,
entityKey.modelId))
.withEmptyMessage(INDICATOR_EMPTY_LIST_MESSAGE)
.withMarkup()
.withPaging(5)
.hideNavigation()
.withColumn(new ColumnDefinition<>(
Domain.INDICATOR.ATTR_NAME,
INDICATOR_NAME_COLUMN_KEY,
Indicator::getName)
.widthProportion(2))
.withColumn(new ColumnDefinition<>(
Domain.INDICATOR.ATTR_TYPE,
INDICATOR_TYPE_COLUMN_KEY,
this::indicatorTypeName)
.widthProportion(1))
.withColumn(new ColumnDefinition<>(
Domain.THRESHOLD.REFERENCE_NAME,
INDICATOR_THRESHOLD_COLUMN_KEY,
ExamForm::thresholdsValue)
.asMarkup()
.widthProportion(4))
.withDefaultActionIf(
() -> modifyGrant,
() -> actionBuilder
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.create())
.withSelectionListener(this.pageService.getSelectionPublisher(
pageContext,
ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST,
ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST))
.compose(pageContext.copyOf(content));
actionBuilder
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.withSelect(
indicatorTable::getSelection,
PageAction::applySingleSelectionAsEntityKey,
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
.withEntityKey(entityKey)
.withSelect(
indicatorTable::getSelection,
this::deleteSelectedIndicator,
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_INDICATOR_NEW)
.withParentEntityKey(entityKey)
.publishIf(() -> modifyGrant);
// Indicators
this.examFormIndicators.compose(
formContext
.copyOf(content)
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
.withAttribute(ATTR_MODIFY_GRANT, String.valueOf(modifyGrant))
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
}
}
@ -656,95 +464,6 @@ public class ExamForm implements TemplateComposer {
message));
}
private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) {
return this.pageService.pageActionBuilder(table.getPageContext()
.clearEntityKeys()
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA))
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
.withSelectionSupplier(() -> {
final ExamConfigurationMap selectedROWData = table.getSingleSelectedROWData();
final HashSet<EntityKey> result = new HashSet<>();
if (selectedROWData != null) {
result.add(new EntityKey(
selectedROWData.configurationNodeId,
EntityType.CONFIGURATION_NODE));
}
return result;
})
.create();
}
private PageAction downloadExamConfigAction(final PageAction action) {
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
final EntityKey selection = action.getSingleSelection();
if (selection != null) {
final String downloadURL = this.downloadService.createDownloadURL(
selection.modelId,
action.pageContext().getParentEntityKey().modelId,
SEBExamConfigDownload.class,
this.downloadFileName);
urlLauncher.openURL(downloadURL);
}
return action;
}
private Supplier<Set<EntityKey>> getConfigMappingSelection(
final EntityTable<ExamConfigurationMap> configurationTable) {
return () -> {
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
if (firstRowData == null) {
return Collections.emptySet();
} else {
return new HashSet<>(Arrays.asList(firstRowData.getEntityKey()));
}
};
}
private Supplier<Set<EntityKey>> getConfigSelection(final EntityTable<ExamConfigurationMap> configurationTable) {
return () -> {
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
if (firstRowData == null) {
return Collections.emptySet();
} else {
return new HashSet<>(Arrays.asList(new EntityKey(
firstRowData.configurationNodeId,
EntityType.CONFIGURATION_NODE)));
}
};
}
private PageAction deleteSelectedIndicator(final PageAction action) {
final EntityKey indicatorKey = action.getSingleSelection();
this.resourceService.getRestService()
.getBuilder(DeleteIndicator.class)
.withURIVariable(API.PARAM_MODEL_ID, indicatorKey.modelId)
.call();
return action;
}
private PageAction getExamConfigKey(final PageAction action) {
final EntityKey examConfigMappingKey = action.getSingleSelection();
if (examConfigMappingKey != null) {
action.withEntityKey(examConfigMappingKey);
return SEBExamConfigForm
.getConfigKeyFunction(this.pageService)
.apply(action);
}
return action;
}
private PageAction deleteExamConfigMapping(final PageAction action) {
final EntityKey examConfigMappingKey = action.getSingleSelection();
this.resourceService.getRestService()
.getBuilder(DeleteExamConfigMapping.class)
.withURIVariable(API.PARAM_MODEL_ID, examConfigMappingKey.modelId)
.call()
.onError(error -> action.pageContext().notifyRemoveError(EntityType.EXAM_CONFIGURATION_MAP, error));
return action;
}
private Result<Exam> getExistingExam(final PageContext pageContext) {
final EntityKey entityKey = pageContext.getEntityKey();
return this.restService.getBuilder(GetExam.class)
@ -763,43 +482,6 @@ public class ExamForm implements TemplateComposer {
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error));
}
private String indicatorTypeName(final Indicator indicator) {
if (indicator.type == null) {
return Constants.EMPTY_NOTE;
}
return this.resourceService.getI18nSupport()
.getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
}
private static String thresholdsValue(final Indicator indicator) {
if (indicator.thresholds.isEmpty()) {
return Constants.EMPTY_NOTE;
}
final StringBuilder builder = indicator.thresholds
.stream()
.reduce(
new StringBuilder(),
(sb, threshold) -> sb
.append("<span style='padding: 2px 5px 2px 5px; background-color: #")
.append(threshold.color)
.append("; ")
.append((Utils.darkColor(Utils.parseRGB(threshold.color)))
? "color: #4a4a4a; "
: "color: #FFFFFF;")
.append("'>")
.append(Indicator.getDisplayValue(indicator.type, threshold.value))
.append(" (")
.append(threshold.color)
.append(")")
.append("</span>")
.append(" | "),
StringBuilder::append);
builder.delete(builder.length() - 3, builder.length() - 1);
return builder.toString();
}
private Function<PageAction, PageAction> cancelModifyFunction() {
final Function<PageAction, PageAction> backToCurrentFunction = this.pageService.backToCurrentFunction();
return action -> {

View file

@ -0,0 +1,294 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.content;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.UrlLauncher;
import org.eclipse.swt.widgets.Composite;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.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.DeleteExamConfigMapping;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class ExamFormConfigs implements TemplateComposer {
private final static LocTextKey CONFIG_LIST_TITLE_KEY =
new LocTextKey("sebserver.exam.configuration.list.title");
private final static LocTextKey CONFIG_LIST_TITLE_TOOLTIP_KEY =
new LocTextKey("sebserver.exam.configuration.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private final static LocTextKey CONFIG_NAME_COLUMN_KEY =
new LocTextKey("sebserver.exam.configuration.list.column.name");
private final static LocTextKey CONFIG_DESCRIPTION_COLUMN_KEY =
new LocTextKey("sebserver.exam.configuration.list.column.description");
private final static LocTextKey CONFIG_STATUS_COLUMN_KEY =
new LocTextKey("sebserver.exam.configuration.list.column.status");
private final static LocTextKey CONFIG_EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.exam.configuration.list.pleaseSelect");
private static final LocTextKey CONFIG_EMPTY_LIST_MESSAGE =
new LocTextKey("sebserver.exam.configuration.list.empty");
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 ExamToConfigBindingForm examToConfigBindingForm;
private final DownloadService downloadService;
private final String downloadFileName;
private final WidgetFactory widgetFactory;
private final RestService restService;
protected ExamFormConfigs(
final PageService pageService,
final ExamSEBRestrictionSettings examSEBRestrictionSettings,
final ExamToConfigBindingForm examToConfigBindingForm,
final DownloadService downloadService,
@Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) {
this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.examToConfigBindingForm = examToConfigBindingForm;
this.downloadService = downloadService;
this.downloadFileName = downloadFileName;
this.widgetFactory = pageService.getWidgetFactory();
this.restService = pageService.getRestService();
}
@Override
public void compose(final PageContext pageContext) {
final CurrentUser currentUser = this.resourceService.getCurrentUser();
final Composite content = pageContext.getParent();
final EntityKey entityKey = pageContext.getEntityKey();
final boolean modifyGrant = BooleanUtils.toBoolean(
pageContext.getAttribute(ExamForm.ATTR_MODIFY_GRANT));
final boolean readGrant = BooleanUtils.toBoolean(
pageContext.getAttribute(ExamForm.ATTR_READ_GRANT));
final ExamStatus examStatus = ExamStatus.valueOf(
pageContext.getAttribute(ExamForm.ATTR_EXAM_STATUS));
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
final boolean editable = examStatus == ExamStatus.UP_COMING
|| examStatus == ExamStatus.RUNNING
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
// List of SEB Configuration
this.widgetFactory.addFormSubContextHeader(
content,
CONFIG_LIST_TITLE_KEY,
CONFIG_LIST_TITLE_TOOLTIP_KEY);
final EntityTable<ExamConfigurationMap> configurationTable =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetExamConfigMappingsPage.class))
.withRestCallAdapter(builder -> builder.withQueryParam(
ExamConfigurationMap.FILTER_ATTR_EXAM_ID,
entityKey.modelId))
.withEmptyMessage(CONFIG_EMPTY_LIST_MESSAGE)
.withPaging(1)
.hideNavigation()
.withColumn(new ColumnDefinition<>(
Domain.CONFIGURATION_NODE.ATTR_NAME,
CONFIG_NAME_COLUMN_KEY,
ExamConfigurationMap::getConfigName)
.widthProportion(2))
.withColumn(new ColumnDefinition<>(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
CONFIG_DESCRIPTION_COLUMN_KEY,
ExamConfigurationMap::getConfigDescription)
.widthProportion(4))
.withColumn(new ColumnDefinition<ExamConfigurationMap>(
Domain.CONFIGURATION_NODE.ATTR_STATUS,
CONFIG_STATUS_COLUMN_KEY,
this.resourceService::localizedExamConfigStatusName)
.widthProportion(1))
.withDefaultActionIf(
() -> modifyGrant,
this::viewExamConfigPageAction)
.withSelectionListener(this.pageService.getSelectionPublisher(
pageContext,
ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP,
ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST,
ActionDefinition.EXAM_CONFIGURATION_EXPORT,
ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY))
.compose(pageContext.copyOf(content));
final EntityKey configMapKey = (configurationTable.hasAnyContent())
? new EntityKey(
configurationTable.getFirstRowData().configurationNodeId,
EntityType.CONFIGURATION_NODE)
: null;
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext
.clearEntityKeys()
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
actionBuilder
.newAction(ActionDefinition.EXAM_CONFIGURATION_NEW)
.withParentEntityKey(entityKey)
.withExec(this.examToConfigBindingForm.bindFunction())
.noEventPropagation()
.publishIf(() -> modifyGrant && editable && !configurationTable.hasAnyContent())
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
.withParentEntityKey(entityKey)
.withEntityKey(configMapKey)
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
.withEntityKey(entityKey)
.withSelect(
getConfigMappingSelection(configurationTable),
this::deleteExamConfigMapping,
CONFIG_EMPTY_SELECTION_TEXT_KEY)
.withConfirm(() -> {
if (isExamRunning) {
return CONFIRM_MESSAGE_REMOVE_CONFIG;
}
return null;
})
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable, false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
.withParentEntityKey(entityKey)
.withSelect(
getConfigSelection(configurationTable),
this::downloadExamConfigAction,
CONFIG_EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation()
.publishIf(() -> readGrant && configurationTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
.withSelect(
getConfigSelection(configurationTable),
this::getExamConfigKey,
CONFIG_EMPTY_SELECTION_TEXT_KEY)
.noEventPropagation()
.publishIf(() -> readGrant && configurationTable.hasAnyContent(), false);
}
private PageAction downloadExamConfigAction(final PageAction action) {
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
final EntityKey selection = action.getSingleSelection();
if (selection != null) {
final String downloadURL = this.downloadService.createDownloadURL(
selection.modelId,
action.pageContext().getParentEntityKey().modelId,
SEBExamConfigDownload.class,
this.downloadFileName);
urlLauncher.openURL(downloadURL);
}
return action;
}
private Supplier<Set<EntityKey>> getConfigMappingSelection(
final EntityTable<ExamConfigurationMap> configurationTable) {
return () -> {
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
if (firstRowData == null) {
return Collections.emptySet();
} else {
return new HashSet<>(Arrays.asList(firstRowData.getEntityKey()));
}
};
}
private Supplier<Set<EntityKey>> getConfigSelection(final EntityTable<ExamConfigurationMap> configurationTable) {
return () -> {
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
if (firstRowData == null) {
return Collections.emptySet();
} else {
return new HashSet<>(Arrays.asList(new EntityKey(
firstRowData.configurationNodeId,
EntityType.CONFIGURATION_NODE)));
}
};
}
private PageAction deleteExamConfigMapping(final PageAction action) {
final EntityKey examConfigMappingKey = action.getSingleSelection();
this.resourceService.getRestService()
.getBuilder(DeleteExamConfigMapping.class)
.withURIVariable(API.PARAM_MODEL_ID, examConfigMappingKey.modelId)
.call()
.onError(error -> action.pageContext().notifyRemoveError(EntityType.EXAM_CONFIGURATION_MAP, error));
return action;
}
private PageAction getExamConfigKey(final PageAction action) {
final EntityKey examConfigMappingKey = action.getSingleSelection();
if (examConfigMappingKey != null) {
action.withEntityKey(examConfigMappingKey);
return SEBExamConfigForm
.getConfigKeyFunction(this.pageService)
.apply(action);
}
return action;
}
private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) {
return this.pageService.pageActionBuilder(table.getPageContext()
.clearEntityKeys()
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA))
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
.withSelectionSupplier(() -> {
final ExamConfigurationMap selectedROWData = table.getSingleSelectedROWData();
final HashSet<EntityKey> result = new HashSet<>();
if (selectedROWData != null) {
result.add(new EntityKey(
selectedROWData.configurationNodeId,
EntityType.CONFIGURATION_NODE));
}
return result;
})
.create();
}
}

View file

@ -0,0 +1,196 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.content;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.widgets.Composite;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorPage;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class ExamFormIndicators implements TemplateComposer {
private final static LocTextKey INDICATOR_LIST_TITLE_KEY =
new LocTextKey("sebserver.exam.indicator.list.title");
private final static LocTextKey INDICATOR_LIST_TITLE_TOOLTIP_KEY =
new LocTextKey("sebserver.exam.indicator.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private final static LocTextKey INDICATOR_TYPE_COLUMN_KEY =
new LocTextKey("sebserver.exam.indicator.list.column.type");
private final static LocTextKey INDICATOR_NAME_COLUMN_KEY =
new LocTextKey("sebserver.exam.indicator.list.column.name");
private final static LocTextKey INDICATOR_THRESHOLD_COLUMN_KEY =
new LocTextKey("sebserver.exam.indicator.list.column.thresholds");
private final static LocTextKey INDICATOR_EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
private static final LocTextKey INDICATOR_EMPTY_LIST_MESSAGE =
new LocTextKey("sebserver.exam.indicator.list.empty");
private final PageService pageService;
private final ResourceService resourceService;
private final WidgetFactory widgetFactory;
private final RestService restService;
public ExamFormIndicators(final PageService pageService) {
this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.widgetFactory = pageService.getWidgetFactory();
this.restService = pageService.getRestService();
}
@Override
public void compose(final PageContext pageContext) {
final Composite content = pageContext.getParent();
final EntityKey entityKey = pageContext.getEntityKey();
final boolean modifyGrant = BooleanUtils.toBoolean(
pageContext.getAttribute(ExamForm.ATTR_MODIFY_GRANT));
// List of Indicators
this.widgetFactory.addFormSubContextHeader(
content,
INDICATOR_LIST_TITLE_KEY,
INDICATOR_LIST_TITLE_TOOLTIP_KEY);
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext
.clearEntityKeys()
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
final EntityTable<Indicator> indicatorTable =
this.pageService.entityTableBuilder(this.restService.getRestCall(GetIndicatorPage.class))
.withRestCallAdapter(builder -> builder.withQueryParam(
Indicator.FILTER_ATTR_EXAM_ID,
entityKey.modelId))
.withEmptyMessage(INDICATOR_EMPTY_LIST_MESSAGE)
.withMarkup()
.withPaging(5)
.hideNavigation()
.withColumn(new ColumnDefinition<>(
Domain.INDICATOR.ATTR_NAME,
INDICATOR_NAME_COLUMN_KEY,
Indicator::getName)
.widthProportion(2))
.withColumn(new ColumnDefinition<>(
Domain.INDICATOR.ATTR_TYPE,
INDICATOR_TYPE_COLUMN_KEY,
this::indicatorTypeName)
.widthProportion(1))
.withColumn(new ColumnDefinition<>(
Domain.THRESHOLD.REFERENCE_NAME,
INDICATOR_THRESHOLD_COLUMN_KEY,
this::thresholdsValue)
.asMarkup()
.widthProportion(4))
.withDefaultActionIf(
() -> modifyGrant,
() -> actionBuilder
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.create())
.withSelectionListener(this.pageService.getSelectionPublisher(
pageContext,
ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST,
ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST))
.compose(pageContext.copyOf(content));
actionBuilder
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.withSelect(
indicatorTable::getSelection,
PageAction::applySingleSelectionAsEntityKey,
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
.withEntityKey(entityKey)
.withSelect(
indicatorTable::getSelection,
this::deleteSelectedIndicator,
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_INDICATOR_NEW)
.withParentEntityKey(entityKey)
.publishIf(() -> modifyGrant);
}
private PageAction deleteSelectedIndicator(final PageAction action) {
final EntityKey indicatorKey = action.getSingleSelection();
this.resourceService.getRestService()
.getBuilder(DeleteIndicator.class)
.withURIVariable(API.PARAM_MODEL_ID, indicatorKey.modelId)
.call();
return action;
}
private String indicatorTypeName(final Indicator indicator) {
if (indicator.type == null) {
return Constants.EMPTY_NOTE;
}
return this.resourceService.getI18nSupport()
.getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
}
private String thresholdsValue(final Indicator indicator) {
if (indicator.thresholds.isEmpty()) {
return Constants.EMPTY_NOTE;
}
final StringBuilder builder = indicator.thresholds
.stream()
.reduce(
new StringBuilder(),
(sb, threshold) -> sb
.append("<span style='padding: 2px 5px 2px 5px; background-color: #")
.append(threshold.color)
.append("; ")
.append((Utils.darkColor(Utils.parseRGB(threshold.color)))
? "color: #4a4a4a; "
: "color: #FFFFFF;")
.append("'>")
.append(Indicator.getDisplayValue(indicator.type, threshold.value))
.append(" (")
.append(threshold.color)
.append(")")
.append("</span>")
.append(" | "),
StringBuilder::append);
builder.delete(builder.length() - 3, builder.length() - 1);
return builder.toString();
}
}

View file

@ -0,0 +1,209 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.content;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings;
public class ExamProctoringSettings {
private static final Logger log = LoggerFactory.getLogger(ExamProctoringSettings.class);
private final static LocTextKey SEB_PROCTORING_FORM_TITLE =
new LocTextKey("sebserver.exam.proctoring.form.title");
private final static LocTextKey SEB_PROCTORING_FORM_INFO =
new LocTextKey("sebserver.exam.proctoring.form.info");
private final static LocTextKey SEB_PROCTORING_FORM_ENABLE =
new LocTextKey("sebserver.exam.proctoring.form.enabled");
private final static LocTextKey SEB_PROCTORING_FORM_TYPE =
new LocTextKey("sebserver.exam.proctoring.form.type");
private final static LocTextKey SEB_PROCTORING_FORM_URL =
new LocTextKey("sebserver.exam.proctoring.form.url");
private final static LocTextKey SEB_PROCTORING_FORM_APPKEY =
new LocTextKey("sebserver.exam.proctoring.form.appkey");
private final static LocTextKey SEB_PROCTORING_FORM_SECRET =
new LocTextKey("sebserver.exam.proctoring.form.secret");
public ExamProctoringSettings() {
// TODO Auto-generated constructor stub
}
Function<PageAction, PageAction> settingsFunction(final PageService pageService) {
return action -> {
final PageContext pageContext = action.pageContext();
final ModalInputDialog<FormHandle<?>> dialog =
new ModalInputDialog<FormHandle<?>>(
action.pageContext().getParent().getShell(),
pageService.getWidgetFactory())
.setDialogWidth(740)
.setDialogHeight(400);
final SEBProctoringPropertiesForm bindFormContext = new SEBProctoringPropertiesForm(
pageService,
action.pageContext());
final Predicate<FormHandle<?>> doBind = formHandle -> doCreate(
pageService,
pageContext,
formHandle);
dialog.open(
SEB_PROCTORING_FORM_TITLE,
doBind,
Utils.EMPTY_EXECUTION,
bindFormContext);
return action;
};
}
private boolean doCreate(
final PageService pageService,
final PageContext pageContext,
final FormHandle<?> formHandle) {
final boolean isReadonly = BooleanUtils.toBoolean(
pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY));
if (isReadonly) {
return true;
}
final EntityKey entityKey = pageContext.getEntityKey();
ProctoringSettings examProctoring = null;
try {
final Form form = formHandle.getForm();
final boolean enabled = BooleanUtils.toBoolean(
form.getFieldValue(ProctoringSettings.ATTR_ENABLE_PROCTORING));
final ServerType serverType = ServerType.valueOf(
form.getFieldValue(ProctoringSettings.ATTR_SERVER_TYPE));
examProctoring = new ProctoringSettings(
Long.parseLong(entityKey.modelId),
enabled,
serverType,
form.getFieldValue(ProctoringSettings.ATTR_SERVER_URL),
form.getFieldValue(ProctoringSettings.ATTR_APP_KEY),
form.getFieldValue(ProctoringSettings.ATTR_APP_SECRET));
} catch (final Exception e) {
log.error("Unexpected error while trying to get settings from form: ", e);
}
if (examProctoring == null) {
return false;
}
return !pageService
.getRestService()
.getBuilder(SaveProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.withBody(examProctoring)
.call()
.onError(formHandle::handleError)
.hasError();
}
private final class SEBProctoringPropertiesForm
implements ModalInputDialogComposer<FormHandle<?>> {
private final PageService pageService;
private final PageContext pageContext;
protected SEBProctoringPropertiesForm(
final PageService pageService,
final PageContext pageContext) {
this.pageService = pageService;
this.pageContext = pageContext;
}
@Override
public Supplier<FormHandle<?>> compose(final Composite parent) {
final RestService restService = this.pageService.getRestService();
final ResourceService resourceService = this.pageService.getResourceService();
final EntityKey entityKey = this.pageContext.getEntityKey();
final boolean isReadonly = BooleanUtils.toBoolean(
this.pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY));
final Composite content = this.pageService
.getWidgetFactory()
.createPopupScrollComposite(parent);
final ProctoringSettings proctoringSettings = restService
.getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.getOrThrow();
final PageContext formContext = this.pageContext
.copyOf(content)
.clearEntityKeys();
final FormHandle<ProctoringSettings> formHandle = this.pageService.formBuilder(
formContext)
.withDefaultSpanInput(6)
.withEmptyCellSeparation(false)
.readonly(isReadonly)
.addField(FormBuilder.text(
"Info",
SEB_PROCTORING_FORM_INFO,
this.pageService.getI18nSupport().getText(SEB_PROCTORING_FORM_INFO))
.asArea(50)
.asHTML()
.readonly(true))
.addField(FormBuilder.checkbox(
ProctoringSettings.ATTR_ENABLE_PROCTORING,
SEB_PROCTORING_FORM_ENABLE,
String.valueOf(proctoringSettings.enableProctoring)))
.addField(FormBuilder.singleSelection(
ProctoringSettings.ATTR_SERVER_TYPE,
SEB_PROCTORING_FORM_TYPE,
proctoringSettings.serverType.name(),
this.pageService.getResourceService()::examProctoringTypeResources))
// TODO
.build();
return () -> formHandle;
}
}
}

View file

@ -20,6 +20,8 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@ -47,7 +49,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ActivateSEBRestriction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeactivateSEBRestriction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetCourseChapters;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSEBRestriction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSEBRestrictionSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSEBRestriction;
@Lazy
@ -55,6 +57,8 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSEBRestr
@GuiProfile
public class ExamSEBRestrictionSettings {
private static final Logger log = LoggerFactory.getLogger(ExamSEBRestrictionSettings.class);
private final static LocTextKey SEB_RESTRICTION_ERROR =
new LocTextKey("sebserver.error.exam.seb.restriction");
private final static LocTextKey SEB_RESTRICTION_FORM_TITLE =
@ -153,7 +157,11 @@ public class ExamSEBRestrictionSettings {
additionalAttributes);
} catch (final Exception e) {
e.printStackTrace();
log.error("Unexpected error while trying to get settings from form: ", e);
}
if (bodyValue == null) {
return false;
}
return !pageService
@ -195,7 +203,7 @@ public class ExamSEBRestrictionSettings {
.createPopupScrollComposite(parent);
final SEBRestriction sebRestriction = restService
.getBuilder(GetSEBRestriction.class)
.getBuilder(GetSEBRestrictionSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.getOrThrow();

View file

@ -20,9 +20,6 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Tuple3;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTimeZone;
import org.springframework.beans.factory.annotation.Value;
@ -47,6 +44,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
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.sebconfig.SEBClientConfig;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
@ -60,6 +58,8 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Tuple3;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
@ -109,6 +109,7 @@ public class ResourceService {
public static final String SEB_RESTRICTION_WHITE_LIST_PREFIX = "sebserver.exam.form.sebrestriction.whiteListPaths.";
public static final String SEB_RESTRICTION_PERMISSIONS_PREFIX = "sebserver.exam.form.sebrestriction.permissions.";
public static final String SEB_CLIENT_CONFIG_PURPOSE_PREFIX = "sebserver.clientconfig.config.purpose.";
public static final String EXAM_PROCTORING_TYPE_PREFIX = "sebserver.exam.proctoring.type.servertype.";
public static final EnumSet<AttributeType> ATTRIBUTE_TYPES_NOT_DISPLAYED = EnumSet.of(
AttributeType.LABEL,
@ -370,6 +371,18 @@ public class ResourceService {
.collect(Collectors.toList());
}
public List<Tuple<String>> examProctoringTypeResources() {
return Arrays.stream(ExamType.values())
.map(type -> new Tuple3<>(
type.name(),
this.i18nSupport.getText(EXAM_PROCTORING_TYPE_PREFIX + type.name()),
Utils.formatLineBreaks(this.i18nSupport.getText(
EXAM_PROCTORING_TYPE_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
StringUtils.EMPTY))))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}
public List<Tuple<String>> examConfigStatusResources() {
return examConfigStatusResources(false);
}
@ -387,9 +400,9 @@ public class ResourceService {
type.name(),
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name()),
Utils.formatLineBreaks(this.i18nSupport.getText(
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name()) + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
StringUtils.EMPTY))
))
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name())
+ Constants.TOOLTIP_TEXT_KEY_SUFFIX,
StringUtils.EMPTY))))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetProctoringSettings extends RestCall<ProctoringSettings> {
public GetProctoringSettings() {
super(new TypeKey<>(
CallType.GET_SINGLE,
EntityType.EXAM_PROCTOR_DATA,
new TypeReference<ProctoringSettings>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT);
}
}

View file

@ -24,9 +24,9 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetSEBRestriction extends RestCall<SEBRestriction> {
public class GetSEBRestrictionSettings extends RestCall<SEBRestriction> {
public GetSEBRestriction() {
public GetSEBRestrictionSettings() {
super(new TypeKey<>(
CallType.GET_SINGLE,
EntityType.EXAM_SEB_RESTRICTION,

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class SaveProctoringSettings extends RestCall<Exam> {
public SaveProctoringSettings() {
super(new TypeKey<>(
CallType.SAVE,
EntityType.EXAM_PROCTOR_DATA,
new TypeReference<Exam>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT);
}
}

View file

@ -28,7 +28,7 @@ public class SaveSEBRestriction extends RestCall<Exam> {
public SaveSEBRestriction() {
super(new TypeKey<>(
CallType.GET_SINGLE,
CallType.SAVE,
EntityType.EXAM_SEB_RESTRICTION,
new TypeReference<Exam>() {
}),

View file

@ -9,8 +9,8 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
import ch.ethz.seb.sebserver.gbl.util.Result;
public interface ExamAdminService {
@ -38,7 +38,7 @@ public interface ExamAdminService {
*
* @param examId the exam instance
* @return Result refer to ExamProctoring data for the exam. */
default Result<ExamProctoring> getExamProctoring(final Exam exam) {
default Result<ProctoringSettings> getExamProctoring(final Exam exam) {
if (exam == null || exam.id == null) {
return Result.ofRuntimeError("Invalid Exam model");
}
@ -49,14 +49,14 @@ public interface ExamAdminService {
*
* @param examId the exam identifier
* @return Result refer to ExamProctoring data for the exam. */
Result<ExamProctoring> getExamProctoring(Long examId);
Result<ProctoringSettings> getExamProctoring(Long examId);
/** Save the given ExamProctoring data for an existing Exam.
*
* @param examId the exam identifier
* @param examProctoring The ExamProctoring data to save for the exam
* @return Result refer to saved ExamProctoring data or to an error when happened. */
Result<ExamProctoring> saveExamProctoring(Long examId, ExamProctoring examProctoring);
Result<ProctoringSettings> saveExamProctoring(Long examId, ProctoringSettings examProctoring);
/** This indicates if proctoring is set and enabled for a certain exam.
*

View file

@ -8,8 +8,8 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -17,15 +17,15 @@ public interface ExamProctoringService {
ServerType getType();
Result<Boolean> testExamProctoring(final ExamProctoring examProctoring);
Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring);
public Result<String> createProctoringURL(
final ExamProctoring examProctoring,
final ProctoringSettings examProctoring,
final String connectionToken,
final boolean server);
Result<String> createProctoringURL(
final ExamProctoring examProctoring,
final ProctoringSettings examProctoring,
ClientConnection clientConnection,
boolean server);

View file

@ -28,8 +28,8 @@ import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
@ -156,56 +156,56 @@ public class ExamAdminServiceImpl implements ExamAdminService {
}
@Override
public Result<ExamProctoring> getExamProctoring(final Long examId) {
public Result<ProctoringSettings> getExamProctoring(final Long examId) {
return this.additionalAttributesDAO.getAdditionalAttributes(EntityType.EXAM, examId)
.map(attrs -> attrs.stream()
.collect(Collectors.toMap(
attr -> attr.getName(),
Function.identity())))
.map(mapping -> {
return new ExamProctoring(
return new ProctoringSettings(
examId,
getEnabled(mapping),
getServerType(mapping),
getString(mapping, ExamProctoring.ATTR_SERVER_URL),
getString(mapping, ExamProctoring.ATTR_APP_KEY),
getString(mapping, ExamProctoring.ATTR_APP_SECRET));
getString(mapping, ProctoringSettings.ATTR_SERVER_URL),
getString(mapping, ProctoringSettings.ATTR_APP_KEY),
getString(mapping, ProctoringSettings.ATTR_APP_SECRET));
});
}
@Override
@Transactional
public Result<ExamProctoring> saveExamProctoring(final Long examId, final ExamProctoring examProctoring) {
public Result<ProctoringSettings> saveExamProctoring(final Long examId, final ProctoringSettings examProctoring) {
return Result.tryCatch(() -> {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ExamProctoring.ATTR_ENABLE_PROCTORING,
ProctoringSettings.ATTR_ENABLE_PROCTORING,
String.valueOf(examProctoring.enableProctoring));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ExamProctoring.ATTR_SERVER_TYPE,
ProctoringSettings.ATTR_SERVER_TYPE,
examProctoring.serverType.name());
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ExamProctoring.ATTR_SERVER_URL,
ProctoringSettings.ATTR_SERVER_URL,
examProctoring.serverURL);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ExamProctoring.ATTR_APP_KEY,
ProctoringSettings.ATTR_APP_KEY,
examProctoring.appKey);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ExamProctoring.ATTR_APP_SECRET,
ProctoringSettings.ATTR_APP_SECRET,
this.cryptor.encrypt(examProctoring.appSecret).toString());
return examProctoring;
@ -217,7 +217,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
return this.additionalAttributesDAO.getAdditionalAttribute(
EntityType.EXAM,
examId,
ExamProctoring.ATTR_ENABLE_PROCTORING)
ProctoringSettings.ATTR_ENABLE_PROCTORING)
.map(rec -> rec != null && BooleanUtils.toBoolean(rec.getValue()));
}
@ -227,16 +227,16 @@ public class ExamAdminServiceImpl implements ExamAdminService {
}
private Boolean getEnabled(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ExamProctoring.ATTR_ENABLE_PROCTORING)) {
return BooleanUtils.toBoolean(mapping.get(ExamProctoring.ATTR_ENABLE_PROCTORING).getValue());
if (mapping.containsKey(ProctoringSettings.ATTR_ENABLE_PROCTORING)) {
return BooleanUtils.toBoolean(mapping.get(ProctoringSettings.ATTR_ENABLE_PROCTORING).getValue());
} else {
return false;
}
}
private ServerType getServerType(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ExamProctoring.ATTR_SERVER_TYPE)) {
return ServerType.valueOf(mapping.get(ExamProctoring.ATTR_SERVER_TYPE).getValue());
if (mapping.containsKey(ProctoringSettings.ATTR_SERVER_TYPE)) {
return ServerType.valueOf(mapping.get(ProctoringSettings.ATTR_SERVER_TYPE).getValue());
} else {
return ServerType.JITSI_MEET;
}

View file

@ -21,8 +21,8 @@ import org.springframework.web.util.UriComponentsBuilder;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -53,14 +53,14 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
}
@Override
public Result<Boolean> testExamProctoring(final ExamProctoring examProctoring) {
public Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring) {
// TODO Auto-generated method stub
return null;
}
@Override
public Result<String> createProctoringURL(
final ExamProctoring examProctoring,
final ProctoringSettings examProctoring,
final String connectionToken,
final boolean server) {
@ -76,7 +76,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
@Override
public Result<String> createProctoringURL(
final ExamProctoring examProctoring,
final ProctoringSettings examProctoring,
final ClientConnection clientConnection,
final boolean server) {

View file

@ -16,7 +16,7 @@ import java.util.stream.Collectors;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamProctoringService;

View file

@ -50,7 +50,7 @@ import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
@ -386,7 +386,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ExamProctoring getExamProctoring(
public ProctoringSettings getExamProctoring(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
@ -405,18 +405,22 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ExamProctoring saveExamProctoring(
public Exam saveExamProctoring(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(API.PARAM_MODEL_ID) final Long examId,
@Valid @RequestBody final ExamProctoring examProctoring) {
@Valid @RequestBody final ProctoringSettings examProctoring) {
checkModifyPrivilege(institutionId);
return this.entityDAO.byPK(examId)
.flatMap(this.authorization::checkModify)
.flatMap(exam -> this.examAdminService.saveExamProctoring(examId, examProctoring))
.map(exam -> {
this.examAdminService.saveExamProctoring(examId, examProctoring);
return exam;
})
.flatMap(this.userActivityLogDAO::logModify)
.getOrThrow();
}

View file

@ -621,6 +621,23 @@ sebserver.exam.delete.confirm.title=Deletion Successful
sebserver.exam.delete.confirm.message=The Exam ({0}) was successfully deleted.<br/>Also the following number dependencies where successfully deleted: {1}.<br/><br/>And there where {2} errors.
sebserver.exam.delete.report.list.empty=No dependencies will be deleted.
sebserver.exam.proctoring.actions.open=Set Proctoring
sebserver.exam.proctoring.form.title=Exam Proctoring Settings
sebserver.exam.proctoring.form.info=This allows to integrate a supported external proctoring service.
sebserver.exam.proctoring.form.enabled=Proctoring enabled
sebserver.exam.proctoring.form.enabled.tooltip=Indicates whether the exam proctoring feature is enabled for this exam or not.
sebserver.exam.proctoring.form.type=Type
sebserver.exam.proctoring.form.type.tooltip=The type and server type of the external proctoring service.
sebserver.exam.proctoring.form.url=Server URL
sebserver.exam.proctoring.form.url.tooltip=The proctoring server URL
sebserver.exam.proctoring.form.appkey=Application Key
sebserver.exam.proctoring.form.appkey.tooltip=The application key of the proctoring service server
sebserver.exam.proctoring.form.secret=Secret
sebserver.exam.proctoring.form.secret.tooltip=The secret used to access the proctoring service
sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet Server for proctoring
################################
# Client configuration
################################