diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 10ea439b..827077df 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -417,6 +417,16 @@ public enum ActionDefinition { ImageIcon.DELETE, PageStateDefinitionImpl.EXAM_TEMPLATE_LIST, ActionCategory.FORM), + EXAM_TEMPLATE_PROCTORING_ON( + new LocTextKey("sebserver.examtemplate.proctoring.actions.open"), + ImageIcon.VISIBILITY, + PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW, + ActionCategory.FORM), + EXAM_TEMPLATE_PROCTORING_OFF( + new LocTextKey("sebserver.examtemplate.proctoring.actions.open"), + ImageIcon.VISIBILITY_OFF, + PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW, + ActionCategory.FORM), INDICATOR_TEMPLATE_NEW( new LocTextKey("sebserver.examtemplate.indicator.action.list.new"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java index 8bf0fa11..feb4c1d0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamTemplateForm.java @@ -23,6 +23,7 @@ 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.ExamTemplate; import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; @@ -39,10 +40,12 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicatorTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplateProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorTemplatePage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; import ch.ethz.seb.sebserver.gui.table.EntityTable; @@ -93,13 +96,17 @@ public class ExamTemplateForm implements TemplateComposer { private final ResourceService resourceService; private final WidgetFactory widgetFactory; private final RestService restService; + private final ProctoringSettingsPopup proctoringSettingsPopup; - public ExamTemplateForm(final PageService pageService) { + public ExamTemplateForm( + final PageService pageService, + final ProctoringSettingsPopup proctoringSettingsPopup) { this.pageService = pageService; this.resourceService = pageService.getResourceService(); this.widgetFactory = pageService.getWidgetFactory(); this.restService = pageService.getRestService(); + this.proctoringSettingsPopup = proctoringSettingsPopup; } @Override @@ -185,7 +192,16 @@ public class ExamTemplateForm implements TemplateComposer { ? this.restService.getRestCall(NewExamTemplate.class) : this.restService.getRestCall(SaveExamTemplate.class)); + final boolean proctoringEnabled = this.restService + .getBuilder(GetExamTemplateProctoringSettings.class) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .call() + .map(ProctoringServiceSettings::getEnableProctoring) + .getOr(false); + final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM_TEMPLATE); + final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(examTemplate); + final boolean modifyGrant = userGrantCheck.m(); // propagate content actions to action-pane this.pageService.pageActionBuilder(formContext.clearEntityKeys()) @@ -210,7 +226,17 @@ public class ExamTemplateForm implements TemplateComposer { .withExec(this::deleteExamTemplate) .publishIf(() -> userGrant.iw() && readonly) - ; + .newAction(ActionDefinition.EXAM_TEMPLATE_PROCTORING_ON) + .withEntityKey(entityKey) + .withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant)) + .noEventPropagation() + .publishIf(() -> proctoringEnabled && readonly) + + .newAction(ActionDefinition.EXAM_TEMPLATE_PROCTORING_OFF) + .withEntityKey(entityKey) + .withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant)) + .noEventPropagation() + .publishIf(() -> !proctoringEnabled && readonly); if (readonly) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java index 570540fd..54b32d39 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java @@ -193,7 +193,10 @@ public class ProctoringSettingsPopup { if (saveOk) { final PageAction action = pageService.pageActionBuilder(pageContext) - .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST) + .newAction( + entityKey.entityType == EntityType.EXAM + ? ActionDefinition.EXAM_VIEW_FROM_LIST + : ActionDefinition.EXAM_TEMPLATE_VIEW_FROM_LIST) .create(); pageService.firePageEvent( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java index dbcc1562..27d8df6a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamTemplateServiceImpl.java @@ -28,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate; @@ -44,6 +45,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamTemplateDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService; @@ -177,28 +179,7 @@ public class ExamTemplateServiceImpl implements ExamTemplateService { if (examTemplate.configTemplateId != null) { - // create new exam configuration for the exam - final ConfigurationNode configurationNode = new ConfigurationNode( - null, - exam.institutionId, - examTemplate.configTemplateId, - replaceVars(this.defaultExamConfigNameTemplate, exam, examTemplate), - replaceVars(this.defaultExamConfigDescTemplate, exam, examTemplate), - ConfigurationType.EXAM_CONFIG, - exam.owner, - ConfigurationStatus.IN_USE); - - final ConfigurationNode examConfig = this.configurationNodeDAO - .createNew(configurationNode) - .onError(error -> log.error( - "Failed to create exam configuration for exam: {} from template: {} examConfig: {}", - exam.name, - examTemplate.name, - configurationNode, - error)) - .getOrThrow(error -> new APIMessageException( - ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG, - error)); + final ConfigurationNode examConfig = createOrReuseConfig(exam, examTemplate); // map the exam configuration to the exam this.examConfigurationMapDAO.createNew(new ExamConfigurationMap( @@ -226,6 +207,68 @@ public class ExamTemplateServiceImpl implements ExamTemplateService { }).onError(error -> log.error("Failed to create exam configuration defined by template for exam: ", error)); } + private ConfigurationNode createOrReuseConfig(final Exam exam, final ExamTemplate examTemplate) { + final String configName = replaceVars(this.defaultExamConfigNameTemplate, exam, examTemplate); + final FilterMap filterMap = new FilterMap(); + filterMap.putIfAbsent(Entity.FILTER_ATTR_INSTITUTION, exam.institutionId.toString()); + filterMap.putIfAbsent(Entity.FILTER_ATTR_NAME, configName); + + // get existing config if available + final ConfigurationNode examConfig = this.configurationNodeDAO + .allMatching(filterMap) + .getOrThrow() + .stream() + .filter(res -> res.name.equals(configName)) + .findFirst() + .orElse(null); + + if (examConfig == null || examConfig.status != ConfigurationStatus.READY_TO_USE) { + final ConfigurationNode config = new ConfigurationNode( + null, + exam.institutionId, + examTemplate.configTemplateId, + configName, + replaceVars(this.defaultExamConfigDescTemplate, exam, examTemplate), + ConfigurationType.EXAM_CONFIG, + exam.owner, + ConfigurationStatus.IN_USE); + + return this.configurationNodeDAO + .createNew(config) + .onError(error -> log.error( + "Failed to create exam configuration for exam: {} from template: {} examConfig: {}", + exam.name, + examTemplate.name, + config, + error)) + .getOrThrow(error -> new APIMessageException( + ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG, + error)); + } else { + final ConfigurationNode config = new ConfigurationNode( + examConfig.id, + null, + null, + null, + null, + null, + null, + ConfigurationStatus.IN_USE); + + return this.configurationNodeDAO + .save(config) + .onError(error -> log.error( + "Failed to save exam configuration for exam: {} from template: {} examConfig: {}", + exam.name, + examTemplate.name, + config, + error)) + .getOrThrow(error -> new APIMessageException( + ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG, + error)); + } + } + private Result addIndicatorsFromTemplate(final Exam exam) { return Result.tryCatch(() -> { diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 831957bf..d73992df 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1713,7 +1713,7 @@ sebserver.examtemplate.indicator.action.list.new=Add Indicator sebserver.examtemplate.indicator.action.list.modify=Edit Indicator sebserver.examtemplate.indicator.action.list.delete=Delete Indicator - +sebserver.examtemplate.proctoring.actions.open=Proctoring Settings ################################ # Certificates