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 9e345c889..277c8e855 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 @@ -8,7 +8,6 @@ package ch.ethz.seb.sebserver.gui.content.action; -import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gui.content.activity.PageStateDefinitionImpl; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.page.PageStateDefinition; @@ -300,12 +299,12 @@ public enum ActionDefinition { ActionCategory.FORM), EXAM_TOGGLE_TEST_RUN_ON( new LocTextKey("sebserver.exam.action.test.run.on"), - ImageIcon.ARCHIVE, + ImageIcon.TEST_RUN_OFF, PageStateDefinitionImpl.EXAM_VIEW, ActionCategory.FORM), EXAM_TOGGLE_TEST_RUN_OFF( new LocTextKey("sebserver.exam.action.test.run.off"), - ImageIcon.ARCHIVE, + ImageIcon.TEST_RUN_ON, PageStateDefinitionImpl.EXAM_VIEW, ActionCategory.FORM), EXAM_ARCHIVE( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java index 3daeebbad..1f7e38162 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java @@ -22,6 +22,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ToggleTestRun; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.joda.time.DateTime; @@ -112,6 +113,7 @@ public class ExamForm implements TemplateComposer { private final static LocTextKey CONSISTENCY_MESSAGE_SEB_RESTRICTION_MISMATCH = new LocTextKey("sebserver.exam.consistencyseb-restriction-mismatch"); private final static LocTextKey AUTO_GEN_CONFIG_ERROR_TITLE = new LocTextKey("sebserver.exam.autogen.error.config.title"); private final static LocTextKey AUTO_GEN_CONFIG_ERROR_TEXT = new LocTextKey("sebserver.exam.autogen.error.config.text"); + private final static LocTextKey TEST_RUN_ENABLED_NOTE = new LocTextKey("sebserver.exam.test.run.enabled.note"); private final Map consistencyMessageMapping; private final PageService pageService; @@ -232,6 +234,9 @@ public void compose(final PageContext pageContext) { if (sebRestrictionMismatch || (warnings != null && !warnings.isEmpty())) { showConsistencyChecks(warnings, sebRestrictionMismatch, formContext.getParent()); } + if (exam.status == ExamStatus.TEST_RUN) { + showTestRunMessage(formContext.getParent()); + } } // the default page layout with title @@ -338,26 +343,24 @@ public void compose(final PageContext pageContext) { .withAttribute(ExamSEBRestrictionSettings.PAGE_CONTEXT_ATTR_LMS_ID, String.valueOf(exam.lmsSetupId)) .withAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY, String.valueOf(!modifyGrant || !editable)) .noEventPropagation() - .publishIf(() -> restrictionEnabled && sebRestrictionAvailable && readonly) + .publishIf(() -> restrictionEnabled && sebRestrictionAvailable) .newAction(ActionDefinition.EXAM_ENABLE_SEB_RESTRICTION) .withEntityKey(entityKey) .withExec(action -> this.examSEBRestrictionSettings.setSEBRestriction(action, true, this.restService)) - .publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData - && BooleanUtils.isFalse(isRestricted)) + .publishIf(() -> sebRestrictionAvailable && modifyGrant && !importFromQuizData && BooleanUtils.isFalse(isRestricted)) .newAction(ActionDefinition.EXAM_DISABLE_SEB_RESTRICTION) .withConfirm(() -> ACTION_MESSAGE_SEB_RESTRICTION_RELEASE) .withEntityKey(entityKey) .withExec(action -> this.examSEBRestrictionSettings.setSEBRestriction(action, false, this.restService)) - .publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData - && BooleanUtils.isTrue(isRestricted)) + .publishIf(() -> sebRestrictionAvailable && modifyGrant && !importFromQuizData && BooleanUtils.isTrue(isRestricted)) .newAction(ActionDefinition.EXAM_PROCTORING_ON) .withEntityKey(entityKey) .withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable)) .noEventPropagation() - .publishIf(() -> lpEnabled && !isLight && proctoringEnabled && readonly) + .publishIf(() -> lpEnabled && !isLight && proctoringEnabled) .newAction(ActionDefinition.EXAM_PROCTORING_OFF) .withEntityKey(entityKey) @@ -370,7 +373,7 @@ public void compose(final PageContext pageContext) { .withExec( this.screenProctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable)) .noEventPropagation() - .publishIf(() -> spsFeatureEnabled && screenProctoringEnabled && readonly) + .publishIf(() -> spsFeatureEnabled && screenProctoringEnabled) .newAction(ActionDefinition.SCREEN_PROCTORING_OFF) .withEntityKey(entityKey) @@ -773,6 +776,15 @@ private boolean hasSEBRestrictionAPI(final Exam exam) { return (lmsSetup.getLmsType().features.contains(LmsSetup.Features.SEB_RESTRICTION)); } + private void showTestRunMessage(final Composite parent) { + final Composite notePanel = this.widgetFactory.createWarningPanel(parent); + notePanel.setData(RWT.CUSTOM_VARIANT, CustomVariant.NOTE.key); + this.widgetFactory.labelLocalized( + notePanel, + CustomVariant.TITLE_LABEL, + TEST_RUN_ENABLED_NOTE); + } + private void showConsistencyChecks( final Collection result, final boolean sebRestrictionMismatch, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index 9b29fa597..e833ea0a4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -152,7 +152,9 @@ public enum ImageIcon { BACK("back.png"), SCREEN_PROC_ON("screen_proc_on.png"), SCREEN_PROC_OFF("screen_proc_off.png"), - ADD_EXAM("add_exam.png"); + ADD_EXAM("add_exam.png"), + TEST_RUN_ON("testRunOn.png"), + TEST_RUN_OFF("testRunOff.png"); public String fileName; private ImageData image = null; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java index 758cf210c..d2b0a1b06 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java @@ -190,14 +190,18 @@ public void notifyLmsSetupChange(final LmsSetupChangeEvent event) { if (event.activation == Activatable.ActivationAction.NONE) { if (!lmsSetup.integrationActive) { applyFullLmsIntegration(lmsSetup.id) - .onError(error -> log.warn("Failed to update LMS integration for: {} error {}", lmsSetup, error.getMessage())) - .onSuccess(data -> log.debug("Successfully updated LMS integration for: {} data: {}", lmsSetup, data)); + .onError(error -> log.warn( + "Failed to update LMS integration for: {} error {}", lmsSetup, error.getMessage())) + .onSuccess(data -> log.debug( + "Successfully updated LMS integration for: {} data: {}", lmsSetup, data)); } } else if (event.activation == Activatable.ActivationAction.ACTIVATE) { applyFullLmsIntegration(lmsSetup.id) .map(data -> reapplyExistingExams(data,lmsSetup)) - .onError(error -> log.warn("Failed to update LMS integration for: {} error {}", lmsSetup, error.getMessage())) - .onSuccess(data -> log.debug("Successfully updated LMS integration for: {} data: {}", lmsSetup, data)); + .onError(error -> log.warn( + "Failed to update LMS integration for: {} error {}", lmsSetup, error.getMessage())) + .onSuccess(data -> log.debug( + "Successfully updated LMS integration for: {} data: {}", lmsSetup, data)); } else if (event.activation == Activatable.ActivationAction.DEACTIVATE) { // remove all active exam data for involved exams before deactivate them this.examDAO @@ -235,7 +239,9 @@ public void notifyExamTemplateChange(final ExamTemplateChangeEvent event) { public void notifyConnectionConfigurationChange(final ConnectionConfigurationChangeEvent event) { lmsSetupDAO.idsOfActiveWithFullIntegration(event.institutionId) .flatMap(examDAO::allActiveForLMSSetup) - .onError(error -> log.error("Failed to notifyConnectionConfigurationChange: {}", error.getMessage())) + .onError(error -> log.error( + "Failed to notifyConnectionConfigurationChange: {}", + error.getMessage())) .getOr(Collections.emptyList()) .stream() .filter(exam -> this.needsConnectionConfigurationChange(exam, event.configId)) @@ -370,16 +376,22 @@ public Result streamConnectionConfiguration( .flatMap(this::findExam); if (examResult.hasError()) { - log.error("Failed to find exam for SEB Connection Configuration download: ", examResult.getError()); - throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("Exam not found")); + log.error( + "Failed to find exam for SEB Connection Configuration download: ", + examResult.getError()); + throw new APIMessage.APIMessageException( + APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("Exam not found")); } final Exam exam = examResult.get(); final String connectionConfigId = getConnectionConfigurationId(exam); if (StringUtils.isBlank(connectionConfigId)) { - log.error("Failed to verify SEB Connection Configuration id for exam: {}", exam.name); - throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found")); + log.error( + "Failed to verify SEB Connection Configuration id for exam: {}", + exam.name); + throw new APIMessage.APIMessageException( + APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found")); } this.connectionConfigurationService.exportSEBClientConfiguration( @@ -413,7 +425,8 @@ private String getConnectionConfigurationId(final Exam exam) { .map(all -> all.stream().filter(config -> config.configPurpose == SEBClientConfig.ConfigPurpose.START_EXAM) .findFirst() .orElseThrow(() -> new APIMessage.APIMessageException( - APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found")))) + APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of( + "No active Connection Configuration found")))) .map(SEBClientConfig::getModelId) .getOr(null); } @@ -487,7 +500,6 @@ private Function createExam( // check if the exam has already been imported, If so return the existing exam final Result existingExam = findExam(quizData); if (!existingExam.hasError()) { - // TODO do we need to check if ad-hoc account exists and if not, create one? return existingExam.get(); } @@ -526,7 +538,9 @@ private Exam checkDeletion(final Exam exam) { final Integer active = this.clientConnectionDAO .getAllActiveConnectionTokens(exam.id) .map(Collection::size) - .onError(error -> log.warn("Failed to get active access tokens for exam: {}", error.getMessage())) + .onError(error -> log.warn( + "Failed to get active access tokens for exam: {}", + error.getMessage())) .getOr(1); if (active == null || active == 0) { @@ -570,9 +584,13 @@ private Exam applyExamData(final Exam exam, final boolean deletion) { ? null : exam.examTemplateId != null ? String.valueOf(exam.examTemplateId) - : "0"; - final String quitPassword = deletion ? null : examConfigurationValueService.getQuitPassword(exam.id); - final String quitLink = deletion ? null : examConfigurationValueService.getQuitLink(exam.id); + : "0"; // no selection on Moodle site + final String quitPassword = deletion + ? null + : examConfigurationValueService.getQuitPassword(exam.id); + final String quitLink = deletion + ? null + : examConfigurationValueService.getQuitLink(exam.id); final ExamData examData = new ExamData( lmsUUID, @@ -620,6 +638,10 @@ private Exam applyQuitLinkToSEBConfig(final Exam exam, final boolean showQuitLin } } +// Note: We decided that Moodle gets the Connection Configuration from SEB Server instead of SEB Server +// sending the Connection Configuration to Moodle. Code on SEB Server site and Moodle function still +// remains here for the case its need in the future. +// // private Exam applyConnectionConfiguration(final Exam exam) { // return lmsAPITemplateCacheService // .getLmsAPITemplate(exam.lmsSetupId) @@ -689,14 +711,18 @@ private String getAutoLoginURL() { private Exam logExamCreated(final Exam exam) { this.userActivityLogDAO .logCreate(exam) - .onError(error -> log.warn("Failed to log exam creation from LMS: {}", error.getMessage())); + .onError(error -> log.warn( + "Failed to log exam creation from LMS: {}", + error.getMessage())); return exam; } private Exam logExamDeleted(final Exam exam) { this.userActivityLogDAO .logDelete(exam) - .onError(error -> log.warn("Failed to log exam deletion from LMS: {}", error.getMessage())); + .onError(error -> log.warn( + "Failed to log exam deletion from LMS: {}", + error.getMessage())); return exam; } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 997ce677a..8566d7666 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -926,7 +926,7 @@ sebserver.exam.sps.form.collect.strategy=Grouping Strategy sebserver.exam.sps.form.saveSettings=Save Settings sebserver.exam.sps.form.saveSettings.error=SEB Server is not able to connect to bundled Screen Proctoring service within the given Service URL.
This probably relies on a incorrect SEB Server setup.

Please make sure your SEB Server setup uses the correct URL mappings or contact a system administrator. - +sebserver.exam.test.run.enabled.note=Exam Test Run is currently enabled. To disable please use "Disable Test Run" sebserver.exam.signaturekey.action.edit=App Signature Key diff --git a/src/main/resources/static/images/testRunOff.png b/src/main/resources/static/images/testRunOff.png new file mode 100644 index 000000000..e6262e655 Binary files /dev/null and b/src/main/resources/static/images/testRunOff.png differ diff --git a/src/main/resources/static/images/testRunOn.png b/src/main/resources/static/images/testRunOn.png new file mode 100644 index 000000000..4ea98eb4f Binary files /dev/null and b/src/main/resources/static/images/testRunOn.png differ