SEBSERV-91 implementation
This commit is contained in:
parent
422147dd7f
commit
f442b9885f
8 changed files with 152 additions and 15 deletions
|
@ -119,6 +119,7 @@ public final class API {
|
|||
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT = "/seb-restriction";
|
||||
public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction";
|
||||
public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported";
|
||||
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters";
|
||||
|
||||
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ public final class Chapters {
|
|||
public final String id;
|
||||
|
||||
@JsonCreator
|
||||
protected Chapter(
|
||||
public Chapter(
|
||||
@JsonProperty(ATTR_NAME) final String name,
|
||||
@JsonProperty(ATTR_ID) final String id) {
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -23,9 +24,11 @@ import org.eclipse.swt.widgets.Composite;
|
|||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSebRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
|
@ -40,6 +43,7 @@ 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.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.SaveSebRestriction;
|
||||
|
||||
|
@ -190,6 +194,13 @@ public class ExamSebRestrictionSettings {
|
|||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final Chapters chapters = restService
|
||||
.getBuilder(GetCourseChapters.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.onError(t -> t.printStackTrace())
|
||||
.getOr(null);
|
||||
|
||||
final PageContext formContext = this.pageContext
|
||||
.copyOf(content)
|
||||
.clearEntityKeys();
|
||||
|
@ -231,16 +242,7 @@ public class ExamSebRestrictionSettings {
|
|||
resourceService::sebRestrictionWhiteListResources))
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.multiCheckboxSelection(
|
||||
OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
|
||||
SEB_RESTRICTION_FORM_EDX_PERMISSIONS,
|
||||
sebRestriction.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS),
|
||||
resourceService::sebRestrictionPermissionResources))
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> chapters == null && lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.text(
|
||||
OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
|
||||
SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS,
|
||||
|
@ -250,6 +252,28 @@ public class ExamSebRestrictionSettings {
|
|||
.get(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS)))
|
||||
.asArea())
|
||||
|
||||
.addFieldIf(
|
||||
() -> chapters != null && lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.multiCheckboxSelection(
|
||||
OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS,
|
||||
SEB_RESTRICTION_FORM_EDX_BLACKLIST_CHAPTERS,
|
||||
sebRestriction
|
||||
.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_BLACKLIST_CHAPTERS),
|
||||
() -> chapters.chapters
|
||||
.stream()
|
||||
.map(chapter -> new Tuple<>(chapter.id, chapter.name))
|
||||
.collect(Collectors.toList())))
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.multiCheckboxSelection(
|
||||
OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
|
||||
SEB_RESTRICTION_FORM_EDX_PERMISSIONS,
|
||||
sebRestriction.getAdditionalProperties()
|
||||
.get(OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS),
|
||||
resourceService::sebRestrictionPermissionResources))
|
||||
|
||||
.addFieldIf(
|
||||
() -> lmsType == LmsType.OPEN_EDX,
|
||||
() -> FormBuilder.checkbox(
|
||||
|
|
|
@ -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.Chapters;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetCourseChapters extends RestCall<Chapters> {
|
||||
|
||||
public GetCourseChapters() {
|
||||
super(new TypeKey<>(
|
||||
CallType.UNDEFINED,
|
||||
EntityType.EXAM,
|
||||
new TypeReference<Chapters>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_ADMINISTRATION_ENDPOINT +
|
||||
API.MODEL_ID_VAR_PATH_SEGMENT +
|
||||
API.EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -109,6 +109,15 @@ public interface LmsAPITemplate {
|
|||
// examinee identifier received by on SEB-Client connection.
|
||||
//Result<ExamineeAccountDetails> getExamineeAccountDetails(String examineeUserId);
|
||||
|
||||
/** Used to get a list of chapters (display name and chapter-identifier) that can be used to
|
||||
* apply chapter-based SEB restriction for a specified course.
|
||||
*
|
||||
* The availability of this depends on the type of LMS and on installed plugins that supports this feature.
|
||||
* If this is not supported by the underling LMS a UnsupportedOperationException will be presented
|
||||
* within the Result.
|
||||
*
|
||||
* @param courseId The course identifier
|
||||
* @return Result referencing to the Chapters model for the given course or to an error when happened. */
|
||||
Result<Chapters> getCourseChapters(String courseId);
|
||||
|
||||
/** Get SEB restriction data form LMS within a SebRestrictionData instance if available
|
||||
|
|
|
@ -89,7 +89,7 @@ public abstract class CourseAccess {
|
|||
}
|
||||
|
||||
protected Result<Chapters> getCourseChapters(final String courseId) {
|
||||
return null;
|
||||
return this.chaptersRequest.protectedRun(getCourseChaptersSupplier(courseId));
|
||||
}
|
||||
|
||||
protected abstract Supplier<List<QuizData>> allQuizzesSupplier();
|
||||
|
|
|
@ -10,11 +10,11 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.edx;
|
|||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -30,6 +30,8 @@ import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
|||
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
|
@ -40,6 +42,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
|||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
|
||||
|
||||
|
@ -53,6 +56,7 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
private static final String OPEN_EDX_DEFAULT_COURSE_ENDPOINT = "/api/courses/v1/courses/";
|
||||
private static final String OPEN_EDX_DEFAULT_BLOCKS_ENDPOINT =
|
||||
"/api/courses/v1/blocks/?depth=1&all_blocks=true&course_id=";
|
||||
private static final String OPEN_EDX_DEFAULT_BLOCKS_TYPE_CHAPTER = "chapter";
|
||||
private static final String OPEN_EDX_DEFAULT_COURSE_START_URL_PREFIX = "/courses/";
|
||||
|
||||
private final LmsSetup lmsSetup;
|
||||
|
@ -116,7 +120,18 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
|
||||
@Override
|
||||
protected Supplier<Chapters> getCourseChaptersSupplier(final String courseId) {
|
||||
throw new UnsupportedOperationException("not available yet");
|
||||
return () -> {
|
||||
final String uri =
|
||||
this.lmsSetup.lmsApiUrl +
|
||||
OPEN_EDX_DEFAULT_BLOCKS_ENDPOINT +
|
||||
Utils.encodeFormURL_UTF_8(courseId);
|
||||
return new Chapters(getCourseBlocks(uri)
|
||||
.getBody().blocks.values()
|
||||
.stream()
|
||||
.filter(block -> OPEN_EDX_DEFAULT_BLOCKS_TYPE_CHAPTER.equals(block.type))
|
||||
.map(block -> new Chapters.Chapter(block.display_name, block.block_id))
|
||||
.collect(Collectors.toList()));
|
||||
};
|
||||
}
|
||||
|
||||
private ArrayList<QuizData> collectAllQuizzes(final OAuth2RestTemplate restTemplate) {
|
||||
|
@ -186,6 +201,17 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
EdXPage.class);
|
||||
}
|
||||
|
||||
private ResponseEntity<Blocks> getCourseBlocks(final String uri) {
|
||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
return getRestTemplateNoEncoding()
|
||||
.getOrThrow()
|
||||
.exchange(
|
||||
uri,
|
||||
HttpMethod.GET,
|
||||
new HttpEntity<>(httpHeaders),
|
||||
Blocks.class);
|
||||
}
|
||||
|
||||
private static QuizData quizDataOf(
|
||||
final LmsSetup lmsSetup,
|
||||
final CourseData courseData,
|
||||
|
@ -230,13 +256,14 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class Blocks {
|
||||
public String root;
|
||||
public Collection<Block> blocks;
|
||||
public Map<String, Block> blocks;
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class Block {
|
||||
public String block_id;
|
||||
public String display_name;
|
||||
public String type;
|
||||
}
|
||||
|
||||
private static final class EdxOAuth2RequestAuthenticator implements OAuth2RequestAuthenticator {
|
||||
|
@ -257,6 +284,17 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
|||
|
||||
}
|
||||
|
||||
private Result<OAuth2RestTemplate> getRestTemplateNoEncoding() {
|
||||
return this.openEdxRestTemplateFactory
|
||||
.createOAuthRestTemplate()
|
||||
.map(tempalte -> {
|
||||
final DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
|
||||
builderFactory.setEncodingMode(EncodingMode.NONE);
|
||||
tempalte.setUriTemplateHandler(builderFactory);
|
||||
return tempalte;
|
||||
});
|
||||
}
|
||||
|
||||
private Result<OAuth2RestTemplate> getRestTemplate() {
|
||||
if (this.restTemplate == null) {
|
||||
final Result<OAuth2RestTemplate> templateRequest = this.openEdxRestTemplateFactory
|
||||
|
|
|
@ -48,6 +48,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
|
|||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
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.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction;
|
||||
|
@ -351,6 +352,28 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
.getOrThrow();
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public Chapters getChapters(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@PathVariable(API.PARAM_MODEL_ID) final Long examlId) {
|
||||
|
||||
checkReadPrivilege(institutionId);
|
||||
return this.entityDAO.byPK(examlId)
|
||||
.flatMap(this.authorization::checkRead)
|
||||
.flatMap(exam -> this.lmsAPIService
|
||||
.getLmsAPITemplate(exam.lmsSetupId)
|
||||
.getOrThrow()
|
||||
.getCourseChapters(exam.externalId))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
// **** SEB Restriction
|
||||
// ****************************************************************************
|
||||
|
||||
|
|
Loading…
Reference in a new issue