diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ExamProctoring.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringSettings.java similarity index 91% rename from src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ExamProctoring.java rename to src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringSettings.java index 9cc60f8a..4881ea06 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ExamProctoring.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringSettings.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java index d87c3229..39ea8f96 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java @@ -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 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 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( - 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 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 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 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> getConfigMappingSelection( - final EntityTable configurationTable) { - return () -> { - final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData(); - if (firstRowData == null) { - return Collections.emptySet(); - } else { - return new HashSet<>(Arrays.asList(firstRowData.getEntityKey())); - } - }; - } - - private Supplier> getConfigSelection(final EntityTable 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 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("") - .append(Indicator.getDisplayValue(indicator.type, threshold.value)) - .append(" (") - .append(threshold.color) - .append(")") - .append("") - .append(" | "), - StringBuilder::append); - builder.delete(builder.length() - 3, builder.length() - 1); - return builder.toString(); - } - private Function cancelModifyFunction() { final Function backToCurrentFunction = this.pageService.backToCurrentFunction(); return action -> { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamFormConfigs.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamFormConfigs.java new file mode 100644 index 00000000..0e723415 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamFormConfigs.java @@ -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 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( + 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> getConfigMappingSelection( + final EntityTable configurationTable) { + return () -> { + final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData(); + if (firstRowData == null) { + return Collections.emptySet(); + } else { + return new HashSet<>(Arrays.asList(firstRowData.getEntityKey())); + } + }; + } + + private Supplier> getConfigSelection(final EntityTable 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 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 result = new HashSet<>(); + if (selectedROWData != null) { + result.add(new EntityKey( + selectedROWData.configurationNodeId, + EntityType.CONFIGURATION_NODE)); + } + return result; + }) + .create(); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamFormIndicators.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamFormIndicators.java new file mode 100644 index 00000000..ece120cf --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamFormIndicators.java @@ -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 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("") + .append(Indicator.getDisplayValue(indicator.type, threshold.value)) + .append(" (") + .append(threshold.color) + .append(")") + .append("") + .append(" | "), + StringBuilder::append); + builder.delete(builder.length() - 3, builder.length() - 1); + return builder.toString(); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java new file mode 100644 index 00000000..3bdfbaa2 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java @@ -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 settingsFunction(final PageService pageService) { + + return action -> { + + final PageContext pageContext = action.pageContext(); + final ModalInputDialog> dialog = + new ModalInputDialog>( + action.pageContext().getParent().getShell(), + pageService.getWidgetFactory()) + .setDialogWidth(740) + .setDialogHeight(400); + + final SEBProctoringPropertiesForm bindFormContext = new SEBProctoringPropertiesForm( + pageService, + action.pageContext()); + + final Predicate> 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> { + + 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> 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 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; + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamSEBRestrictionSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamSEBRestrictionSettings.java index 01ef10ba..ac63677c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamSEBRestrictionSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamSEBRestrictionSettings.java @@ -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(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index 2576bbaa..c2f0bc0e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -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 ATTRIBUTE_TYPES_NOT_DISPLAYED = EnumSet.of( AttributeType.LABEL, @@ -370,6 +371,18 @@ public class ResourceService { .collect(Collectors.toList()); } + public List> 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> 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()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetProctoringSettings.java new file mode 100644 index 00000000..1a3ed6fd --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetProctoringSettings.java @@ -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 { + + public GetProctoringSettings() { + super(new TypeKey<>( + CallType.GET_SINGLE, + EntityType.EXAM_PROCTOR_DATA, + new TypeReference() { + }), + HttpMethod.GET, + MediaType.APPLICATION_JSON_UTF8, + API.EXAM_ADMINISTRATION_ENDPOINT + + API.MODEL_ID_VAR_PATH_SEGMENT + + API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetSEBRestriction.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetSEBRestrictionSettings.java similarity index 89% rename from src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetSEBRestriction.java rename to src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetSEBRestrictionSettings.java index 3efbef01..701239c5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetSEBRestriction.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetSEBRestrictionSettings.java @@ -24,9 +24,9 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; @Lazy @Component @GuiProfile -public class GetSEBRestriction extends RestCall { +public class GetSEBRestrictionSettings extends RestCall { - public GetSEBRestriction() { + public GetSEBRestrictionSettings() { super(new TypeKey<>( CallType.GET_SINGLE, EntityType.EXAM_SEB_RESTRICTION, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java new file mode 100644 index 00000000..220917c7 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java @@ -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 { + + public SaveProctoringSettings() { + super(new TypeKey<>( + CallType.SAVE, + EntityType.EXAM_PROCTOR_DATA, + new TypeReference() { + }), + HttpMethod.POST, + MediaType.APPLICATION_JSON_UTF8, + API.EXAM_ADMINISTRATION_ENDPOINT + + API.MODEL_ID_VAR_PATH_SEGMENT + + API.EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveSEBRestriction.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveSEBRestriction.java index 1cc262ce..d3e79c18 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveSEBRestriction.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveSEBRestriction.java @@ -28,7 +28,7 @@ public class SaveSEBRestriction extends RestCall { public SaveSEBRestriction() { super(new TypeKey<>( - CallType.GET_SINGLE, + CallType.SAVE, EntityType.EXAM_SEB_RESTRICTION, new TypeReference() { }), diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java index c35bfe5b..3d6ec20c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java @@ -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 getExamProctoring(final Exam exam) { + default Result 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 getExamProctoring(Long examId); + Result 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 saveExamProctoring(Long examId, ExamProctoring examProctoring); + Result saveExamProctoring(Long examId, ProctoringSettings examProctoring); /** This indicates if proctoring is set and enabled for a certain exam. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamProctoringService.java index b0a1459f..e3d3c6de 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamProctoringService.java @@ -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 testExamProctoring(final ExamProctoring examProctoring); + Result testExamProctoring(final ProctoringSettings examProctoring); public Result createProctoringURL( - final ExamProctoring examProctoring, + final ProctoringSettings examProctoring, final String connectionToken, final boolean server); Result createProctoringURL( - final ExamProctoring examProctoring, + final ProctoringSettings examProctoring, ClientConnection clientConnection, boolean server); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java index 25385d36..01199727 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java @@ -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 getExamProctoring(final Long examId) { + public Result 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 saveExamProctoring(final Long examId, final ExamProctoring examProctoring) { + public Result 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 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 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; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamJITSIProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamJITSIProctoringService.java index 59cd3090..57415104 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamJITSIProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamJITSIProctoringService.java @@ -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 testExamProctoring(final ExamProctoring examProctoring) { + public Result testExamProctoring(final ProctoringSettings examProctoring) { // TODO Auto-generated method stub return null; } @Override public Result 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 createProctoringURL( - final ExamProctoring examProctoring, + final ProctoringSettings examProctoring, final ClientConnection clientConnection, final boolean server) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamProctoringServiceFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamProctoringServiceFactory.java index 913fb120..b3cebe8c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamProctoringServiceFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamProctoringServiceFactory.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index 51b1cc72..bdc11849 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -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 { + 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 { + 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(); } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 13713ae0..03e91493 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -621,6 +621,23 @@ sebserver.exam.delete.confirm.title=Deletion Successful sebserver.exam.delete.confirm.message=The Exam ({0}) was successfully deleted.
Also the following number dependencies where successfully deleted: {1}.

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 ################################