SEBSERV-75 error handling for SEB Restriction

This commit is contained in:
anhefti 2020-08-05 11:14:39 +02:00
parent 017d191e31
commit 6f9daddf53
9 changed files with 122 additions and 21 deletions

View file

@ -46,7 +46,7 @@ public final class LmsSetup implements GrantEntity, Activatable {
public enum LmsType { public enum LmsType {
MOCKUP(Features.COURSE_API), MOCKUP(Features.COURSE_API),
OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION), OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
MOODLE(Features.COURSE_API); MOODLE(Features.COURSE_API, Features.SEB_RESTRICTION);
public final EnumSet<Features> features; public final EnumSet<Features> features;

View file

@ -43,7 +43,8 @@ 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.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; 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.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features; 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.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
@ -73,6 +74,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfi
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorPage; 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.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.GetLmsSetup;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData; 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.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;
@ -620,12 +622,17 @@ public class ExamForm implements TemplateComposer {
} }
private boolean testSEBRestrictionAPI(final Exam exam) { private boolean testSEBRestrictionAPI(final Exam exam) {
return this.restService.getBuilder(GetLmsSetup.class) // Call the testing endpoint with the specified data to test
final Result<LmsSetupTestResult> result = this.restService.getBuilder(TestLmsSetup.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.lmsSetupId)) .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(exam.lmsSetupId))
.call() .call();
.onError(t -> log.error("Failed to check SEB restriction API: ", t))
.map(lmsSetup -> lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) if (result.hasError()) {
.getOr(false); return false;
}
final LmsSetupTestResult lmsSetupTestResult = result.get();
return !lmsSetupTestResult.hasError(ErrorType.QUIZ_RESTRICTION_API_REQUEST);
} }
private void showConsistencyChecks(final Collection<APIMessage> result, final Composite parent) { private void showConsistencyChecks(final Collection<APIMessage> result, final Composite parent) {

View file

@ -200,12 +200,13 @@ public class ExamSEBRestrictionSettings {
.call() .call()
.getOrThrow(); .getOrThrow();
final Chapters chapters = restService final Chapters chapters = (lmsType == LmsType.OPEN_EDX)
.getBuilder(GetCourseChapters.class) ? restService.getBuilder(GetCourseChapters.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call() .call()
.onError(t -> t.printStackTrace()) .onError(t -> t.printStackTrace())
.getOr(null); .getOr(null)
: null;
final PageContext formContext = this.pageContext final PageContext formContext = this.pageContext
.copyOf(content) .copyOf(content)

View file

@ -457,9 +457,11 @@ public class LmsSetupForm implements TemplateComposer {
Utils.formatHTMLLinesForceEscaped(Utils.escapeHTML_XML_EcmaScript(error.message)))); Utils.formatHTMLLinesForceEscaped(Utils.escapeHTML_XML_EcmaScript(error.message))));
} }
case QUIZ_RESTRICTION_API_REQUEST: { case QUIZ_RESTRICTION_API_REQUEST: {
// NOTE: quiz restriction is not mandatory for functional LmsSetup
// so this error is ignored here throw new PageMessageException(new LocTextKey(
break; "sebserver.lmssetup.action.test.features.error",
"OK",
Utils.formatHTMLLinesForceEscaped(Utils.escapeHTML_XML_EcmaScript(error.message))));
} }
default: { default: {
throw new PageMessageException(new LocTextKey( throw new PageMessageException(new LocTextKey(

View file

@ -19,4 +19,8 @@ public class NoSEBRestrictionException extends RuntimeException {
super(cause); super(cause);
} }
public NoSEBRestrictionException(final String message) {
super(message);
}
} }

View file

@ -16,12 +16,16 @@ import org.slf4j.LoggerFactory;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.exam.MoodleSEBRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.MoodleSEBRestriction;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
/** GET: /** GET:
@ -80,8 +84,27 @@ public class MoodleCourseRestriction {
} }
LmsSetupTestResult initAPIAccess() { LmsSetupTestResult initAPIAccess() {
// TODO test availability // try to call the SEB Restrictions API
return LmsSetupTestResult.ofQuizRestrictionAPIError("not available yet"); try {
final MoodleAPIRestTemplate template = getRestTemplate()
.getOrThrow();
final String jsonResponse = template.callMoodleAPIFunction(
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION,
new LinkedMultiValueMap<>(),
null);
final Error checkError = this.checkError(jsonResponse);
if (checkError != null) {
return LmsSetupTestResult.ofQuizRestrictionAPIError(checkError.exception);
}
} catch (final Exception e) {
log.debug("Moodle SEB restriction API not available: ", e);
return LmsSetupTestResult.ofQuizRestrictionAPIError(e.getMessage());
}
return LmsSetupTestResult.ofOkay();
} }
Result<MoodleSEBRestriction> getSEBRestriction( Result<MoodleSEBRestriction> getSEBRestriction(
@ -125,6 +148,12 @@ public class MoodleCourseRestriction {
queryParams, queryParams,
null); null);
final Error error = this.checkError(resultJSON);
if (error != null) {
log.error("Failed to get SEB restriction: {}", error.toString());
throw new NoSEBRestrictionException("Failed to get SEB restriction: " + error.exception);
}
final MoodleSEBRestriction restrictiondata = this.jsonMapper.readValue( final MoodleSEBRestriction restrictiondata = this.jsonMapper.readValue(
resultJSON, resultJSON,
new TypeReference<MoodleSEBRestriction>() { new TypeReference<MoodleSEBRestriction>() {
@ -241,11 +270,17 @@ public class MoodleCourseRestriction {
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId); queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId);
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_QUIZ_ID, quizId); queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_QUIZ_ID, quizId);
template.callMoodleAPIFunction( final String jsonResponse = template.callMoodleAPIFunction(
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE, MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE,
queryParams, queryParams,
null); null);
final Error error = this.checkError(jsonResponse);
if (error != null) {
log.error("Failed to delete SEB restriction: {}", error.toString());
return false;
}
return true; return true;
}); });
} }
@ -291,6 +326,12 @@ public class MoodleCourseRestriction {
queryParams, queryParams,
queryAttributes); queryAttributes);
final Error error = this.checkError(resultJSON);
if (error != null) {
log.error("Failed to post SEB restriction: {}", error.toString());
throw new NoSEBRestrictionException("Failed to post SEB restriction: " + error.exception);
}
final MoodleSEBRestriction restrictiondata = this.jsonMapper.readValue( final MoodleSEBRestriction restrictiondata = this.jsonMapper.readValue(
resultJSON, resultJSON,
new TypeReference<MoodleSEBRestriction>() { new TypeReference<MoodleSEBRestriction>() {
@ -300,4 +341,50 @@ public class MoodleCourseRestriction {
}); });
} }
public Error checkError(final String jsonResponse) {
if (jsonResponse.contains("exception") || jsonResponse.contains("errorcode")) {
try {
return this.jsonMapper.readValue(
jsonResponse,
new TypeReference<Error>() {
});
} catch (final Exception e) {
log.error("Failed to parse error response: {} cause: ", jsonResponse, e);
return null;
}
}
return null;
}
@JsonIgnoreProperties(ignoreUnknown = true)
private static class Error {
public final String exception;
public final String errorcode;
public final String message;
@JsonCreator
Error(
@JsonProperty(value = "exception") final String exception,
@JsonProperty(value = "errorcode") final String errorcode,
@JsonProperty(value = "message") final String message) {
this.exception = exception;
this.errorcode = errorcode;
this.message = message;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Error [exception=");
builder.append(this.exception);
builder.append(", errorcode=");
builder.append(this.errorcode);
builder.append(", message=");
builder.append(this.message);
builder.append("]");
return builder.toString();
}
}
} }

View file

@ -57,7 +57,7 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
@Override @Override
public LmsSetupTestResult testCourseRestrictionAPI() { public LmsSetupTestResult testCourseRestrictionAPI() {
return LmsSetupTestResult.ofQuizRestrictionAPIError("Not available yet"); return this.moodleCourseRestriction.initAPIAccess();
} }
@Override @Override

View file

@ -307,6 +307,7 @@ sebserver.lmssetup.action.test.ok=Successfully connected to the course API
sebserver.lmssetup.action.test.tokenRequestError=The API access was denied:<br/>{0}<br/><br/>Please check the LMS connection details. sebserver.lmssetup.action.test.tokenRequestError=The API access was denied:<br/>{0}<br/><br/>Please check the LMS connection details.
sebserver.lmssetup.action.test.quizRequestError=Unable to request courses or exams from the course API of the LMS. {0} sebserver.lmssetup.action.test.quizRequestError=Unable to request courses or exams from the course API of the LMS. {0}
sebserver.lmssetup.action.test.quizRestrictionError=Unable to access course restriction API of the LMS. {0} sebserver.lmssetup.action.test.quizRestrictionError=Unable to access course restriction API of the LMS. {0}
sebserver.lmssetup.action.test.features.error=The API access was granted but there is some missing functionality:<br/><br/>- Course Access: {0}<br/>- SEB Restriction: {1}
sebserver.lmssetup.action.test.missingParameter=There is one or more missing connection parameter.<br/>Please check the connection parameter for this LMS Setup sebserver.lmssetup.action.test.missingParameter=There is one or more missing connection parameter.<br/>Please check the connection parameter for this LMS Setup
sebserver.lmssetup.action.test.unknownError=An unexpected error happened while trying to connect to the LMS course API. {0} sebserver.lmssetup.action.test.unknownError=An unexpected error happened while trying to connect to the LMS course API. {0}
sebserver.lmssetup.action.save=Save LMS Setup sebserver.lmssetup.action.save=Save LMS Setup

View file

@ -152,7 +152,6 @@ public class MoodleCourseAccessTest {
final MoodleRestTemplateFactory moodleRestTemplateFactory = mock(MoodleRestTemplateFactory.class); final MoodleRestTemplateFactory moodleRestTemplateFactory = mock(MoodleRestTemplateFactory.class);
final MoodleAPIRestTemplate moodleAPIRestTemplate = mock(MoodleAPIRestTemplate.class); final MoodleAPIRestTemplate moodleAPIRestTemplate = mock(MoodleAPIRestTemplate.class);
when(moodleRestTemplateFactory.createRestTemplate()).thenReturn(Result.of(moodleAPIRestTemplate)); when(moodleRestTemplateFactory.createRestTemplate()).thenReturn(Result.of(moodleAPIRestTemplate));
//doThrow(RuntimeException.class).when(moodleAPIRestTemplate).testAPIConnection(any());
when(moodleRestTemplateFactory.test()).thenReturn(LmsSetupTestResult.ofOkay()); when(moodleRestTemplateFactory.test()).thenReturn(LmsSetupTestResult.ofOkay());
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess( final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(