Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Extend lti content selection table #9981

Draft
wants to merge 54 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
134b7b8
Mock test
raffifasaro Dec 9, 2024
2f27e4b
Mock test 2
raffifasaro Dec 9, 2024
afdf7f0
mock table test
raffifasaro Dec 9, 2024
9e8c5f1
mock table test fix
raffifasaro Dec 9, 2024
d19bc5d
mock table fix
raffifasaro Dec 9, 2024
358d5c7
mock table comment out dates
raffifasaro Dec 9, 2024
2bd644d
table fix
raffifasaro Dec 9, 2024
5927c0a
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Dec 14, 2024
8f2887d
expand table
raffifasaro Dec 14, 2024
2909b48
find lectures
raffifasaro Dec 14, 2024
31a2fcb
fix
raffifasaro Dec 14, 2024
2d0d385
Add working requests
raffifasaro Dec 15, 2024
916a378
fix
raffifasaro Dec 15, 2024
59ca782
Add translation support
raffifasaro Dec 15, 2024
a51cd68
fix
raffifasaro Dec 15, 2024
469a78b
Iris selector + info text for competencies
raffifasaro Dec 17, 2024
23476f5
info text complete
raffifasaro Dec 18, 2024
8c885d1
Fix selection
raffifasaro Dec 18, 2024
2816925
Update sendDeepLinkRequest() for use with new features
raffifasaro Dec 18, 2024
fa28ef2
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Dec 18, 2024
2774b1e
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Dec 19, 2024
0844708
Fix competency checkbox header
raffifasaro Dec 19, 2024
010c1bc
Merge remote-tracking branch 'origin/chore/lti/extend-content-selecti…
raffifasaro Dec 19, 2024
e7f94ef
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Dec 30, 2024
46ea417
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Jan 2, 2025
e36102e
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Jan 3, 2025
0ccd8d3
add competency support test
raffifasaro Jan 3, 2025
fc7b481
fix competency selection frontend
raffifasaro Jan 3, 2025
1812260
fix parameter requirements
raffifasaro Jan 3, 2025
3b802f8
fix exercise Id check
raffifasaro Jan 3, 2025
6c28428
launch routes
raffifasaro Jan 3, 2025
22d6f7e
log request body
raffifasaro Jan 3, 2025
f5685da
Merge remote-tracking branch 'origin/chore/lti/extend-content-selecti…
raffifasaro Jan 3, 2025
4e72689
log data
raffifasaro Jan 3, 2025
38e0fa3
log error detailed
raffifasaro Jan 4, 2025
d2d7984
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Jan 4, 2025
3f22b69
log error detailed 2
raffifasaro Jan 5, 2025
1f680e6
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Jan 5, 2025
803f7ad
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Jan 5, 2025
50282d5
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Jan 5, 2025
534328f
remove log
raffifasaro Jan 6, 2025
1cd6605
Merge remote-tracking branch 'origin/chore/lti/extend-content-selecti…
raffifasaro Jan 6, 2025
3594b05
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Jan 6, 2025
5d9635a
force manual exercise test
raffifasaro Jan 7, 2025
62e060b
test
raffifasaro Jan 7, 2025
69d5979
logs
raffifasaro Jan 8, 2025
e60a076
remove logging
raffifasaro Jan 9, 2025
1feacee
change test course auth
raffifasaro Jan 9, 2025
1dd8a6e
Reqrite auth / perform launch
raffifasaro Jan 10, 2025
7c818bd
Merge branch 'develop' into chore/lti/extend-content-selection-table
raffifasaro Jan 10, 2025
1c8a9a3
course config log
raffifasaro Jan 10, 2025
0b3b89f
course config log
raffifasaro Jan 10, 2025
922a94b
fix error logs
raffifasaro Jan 10, 2025
da1f046
fox online course config
raffifasaro Jan 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,25 @@ public ResponseEntity<Course> getCourseWithExercises(@PathVariable Long courseId
return ResponseEntity.ok(course);
}

/**
* GET /courses/:courseId : get the "id" course.
*
* @param courseId the id of the course to retrieve
* @return the ResponseEntity with status 200 (OK) and with body the course, or with status 404 (Not Found)
*/
@GetMapping("courses/{courseId}/with-exercises-lectures-competencies")
@EnforceAtLeastTutor
public ResponseEntity<Course> getCourseWithExercisesAndLecturesAndCompetencies(@PathVariable Long courseId) {
log.debug("REST request to get course {} for tutors", courseId);
Optional<Course> courseOptional = courseRepository.findWithEagerExercisesAndLecturesAndLectureUnitsAndCompetenciesById(courseId);
if (courseOptional.isEmpty()) {
return ResponseEntity.notFound().build();
}
Course course = courseOptional.get();
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.TEACHING_ASSISTANT, course, null);
return ResponseEntity.ok(course);
}

/**
* GET /courses/:courseId/with-organizations Get a course by id with eagerly loaded organizations
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public class LtiResourceLaunch extends DomainObject {
@ManyToOne
private User user;

@NotNull
@ManyToOne
private Exercise exercise;

Expand Down
101 changes: 88 additions & 13 deletions src/main/java/de/tum/cit/aet/artemis/lti/service/Lti13Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@

private static final String EXERCISE_PATH_PATTERN = "/courses/{courseId}/exercises/{exerciseId}";

private static final String COMPETENCY_PATH_PATTERN = "/courses/{courseId}/competencies";

private static final String COURSE_PATH_PATTERN = "/courses/{courseId}";

private static final Logger log = LoggerFactory.getLogger(Lti13Service.class);

private final UserRepository userRepository;
Expand Down Expand Up @@ -112,17 +116,18 @@
public void performLaunch(OidcIdToken ltiIdToken, String clientRegistrationId) {
String targetLinkUrl = ltiIdToken.getClaim(Claims.TARGET_LINK_URI);
Optional<Exercise> targetExercise = getExerciseFromTargetLink(targetLinkUrl);
if (targetExercise.isEmpty()) {
String message = "No exercise to launch at " + targetLinkUrl;
Optional<Course> targetCourse = getCourseFromTargetLink(targetLinkUrl);

if (targetCourse.isEmpty()) {
String message = "No course to launch at " + targetLinkUrl;
log.error(message);
throw new BadRequestAlertException("Exercise not found", "LTI", "ltiExerciseNotFound");
throw new BadRequestAlertException("Course not found", "LTI", "ltiCourseNotFound");

Check failure on line 124 in src/main/java/de/tum/cit/aet/artemis/lti/service/Lti13Service.java

View workflow job for this annotation

GitHub Actions / H2 Tests

de.tum.cit.aet.artemis.lti.Lti13ServiceTest ► performLaunch_invalidToken()

Failed test found in: build/test-results/test/TEST-de.tum.cit.aet.artemis.lti.Lti13ServiceTest.xml Error: java.lang.AssertionError:
Raw output
java.lang.AssertionError: 
Expecting actual throwable to be an instance of:
  java.lang.IllegalArgumentException
but was:
  https://www.jhipster.tech/problem/problem-with-message{400, Course not found, message=error.ltiCourseNotFound, params=LTI}
	at de.tum.cit.aet.artemis.lti.service.Lti13Service.performLaunch(Lti13Service.java:124)
	at de.tum.cit.aet.artemis.lti.Lti13ServiceTest.lambda$performLaunch_invalidToken$0(Lti13ServiceTest.java:165)
	at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:63)
	...(61 remaining lines not displayed - this can be changed with Assertions.setMaxStackTraceElementsDisplayed)
	at de.tum.cit.aet.artemis.lti.Lti13ServiceTest.performLaunch_invalidToken(Lti13ServiceTest.java:165)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)

Check failure on line 124 in src/main/java/de/tum/cit/aet/artemis/lti/service/Lti13Service.java

View workflow job for this annotation

GitHub Actions / H2 Tests

de.tum.cit.aet.artemis.lti.Lti13ServiceTest ► performLaunch_exerciseFound()

Failed test found in: build/test-results/test/TEST-de.tum.cit.aet.artemis.lti.Lti13ServiceTest.xml Error: https://www.jhipster.tech/problem/problem-with-message{400, Course not found, message=error.ltiCourseNotFound, params=LTI}
Raw output
https://www.jhipster.tech/problem/problem-with-message{400, Course not found, message=error.ltiCourseNotFound, params=LTI}
	at app//de.tum.cit.aet.artemis.lti.service.Lti13Service.performLaunch(Lti13Service.java:124)
	at app//de.tum.cit.aet.artemis.lti.Lti13ServiceTest.performLaunch_exerciseFound(Lti13ServiceTest.java:154)
	at [email protected]/java.lang.reflect.Method.invoke(Method.java:580)
	at [email protected]/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
	at [email protected]/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
	at [email protected]/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
	at [email protected]/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
	at [email protected]/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)

Check failure on line 124 in src/main/java/de/tum/cit/aet/artemis/lti/service/Lti13Service.java

View workflow job for this annotation

GitHub Actions / H2 Tests

de.tum.cit.aet.artemis.lti.Lti13ServiceTest ► performLaunch_courseNotFound()

Failed test found in: build/test-results/test/TEST-de.tum.cit.aet.artemis.lti.Lti13ServiceTest.xml Error: java.lang.AssertionError:
Raw output
java.lang.AssertionError: 
Expecting actual throwable to be an instance of:
  de.tum.cit.aet.artemis.core.exception.EntityNotFoundException
but was:
  https://www.jhipster.tech/problem/problem-with-message{400, Course not found, message=error.ltiCourseNotFound, params=LTI}
	at de.tum.cit.aet.artemis.lti.service.Lti13Service.performLaunch(Lti13Service.java:124)
	at de.tum.cit.aet.artemis.lti.Lti13ServiceTest.lambda$performLaunch_courseNotFound$5(Lti13ServiceTest.java:226)
	at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:63)
	...(61 remaining lines not displayed - this can be changed with Assertions.setMaxStackTraceElementsDisplayed)
	at de.tum.cit.aet.artemis.lti.Lti13ServiceTest.performLaunch_courseNotFound(Lti13ServiceTest.java:226)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)
}
Exercise exercise = targetExercise.get();

Course course = courseRepository.findByIdWithEagerOnlineCourseConfigurationElseThrow(exercise.getCourseViaExerciseGroupOrCourseMember().getId());
OnlineCourseConfiguration onlineCourseConfiguration = course.getOnlineCourseConfiguration();
Course course = targetCourse.get();
OnlineCourseConfiguration onlineCourseConfiguration = courseRepository.findWithEagerOnlineCourseConfigurationById(course.getId()).getOnlineCourseConfiguration();
if (onlineCourseConfiguration == null) {
String message = "Exercise is not related to course for target link url: " + targetLinkUrl;
String message = "LTI is not configured for course with target link URL: " + targetLinkUrl;
log.error(message);
throw new BadRequestAlertException("LTI is not configured for this course", "LTI", "ltiNotConfigured");
}
Expand All @@ -133,18 +138,24 @@
if (!onlineCourseConfiguration.isRequireExistingUser() && optionalUsername.isEmpty()) {
SecurityContextHolder.getContext().setAuthentication(ltiService.createNewUserFromLaunchRequest(ltiIdToken.getEmail(),
createUsernameFromLaunchRequest(ltiIdToken, onlineCourseConfiguration), ltiIdToken.getGivenName(), ltiIdToken.getFamilyName()));

}

String username = optionalUsername.orElseGet(() -> createUsernameFromLaunchRequest(ltiIdToken, onlineCourseConfiguration));
User user = userRepository.findOneWithGroupsAndAuthoritiesByLogin(username).orElseThrow();
ltiService.onSuccessfulLtiAuthentication(user, targetExercise.get());
Lti13LaunchRequest launchRequest = launchRequestFrom(ltiIdToken, clientRegistrationId);

createOrUpdateResourceLaunch(launchRequest, user, targetExercise.get());

ltiService.authenticateLtiUser(ltiIdToken.getEmail(), createUsernameFromLaunchRequest(ltiIdToken, onlineCourseConfiguration), ltiIdToken.getGivenName(),
ltiIdToken.getFamilyName(), onlineCourseConfiguration.isRequireExistingUser());
if (targetExercise.isPresent()) {
Exercise exercise = targetExercise.get();
handleLaunchRequest(launchRequest, user, exercise, ltiIdToken, onlineCourseConfiguration);
}
else if (getCompetencyFromTargetLink(targetLinkUrl)) {
handleLaunchRequest(launchRequest, user, null, ltiIdToken, onlineCourseConfiguration);
}
else {
String message = "No exercise or competency to launch at " + targetLinkUrl;
log.error(message);
throw new BadRequestAlertException("Exercise not found", "LTI", "ltiExerciseNotFound");
}
}

/**
Expand Down Expand Up @@ -311,6 +322,59 @@
return exerciseOpt;
}

private Optional<Course> getCourseFromTargetLink(String targetLinkUrl) {
AntPathMatcher matcher = new AntPathMatcher();

String targetLinkPath;
try {
targetLinkPath = new URI(targetLinkUrl).getPath();
}
catch (URISyntaxException ex) {
log.info("Malformed target link url: {}", targetLinkUrl);
return Optional.empty();
}

Map<String, String> pathVariables = null;
if (matcher.match(COURSE_PATH_PATTERN, targetLinkPath)) {
pathVariables = matcher.extractUriTemplateVariables(COURSE_PATH_PATTERN, targetLinkPath);
}
else if (matcher.match(COMPETENCY_PATH_PATTERN, targetLinkPath)) {
pathVariables = matcher.extractUriTemplateVariables(COMPETENCY_PATH_PATTERN, targetLinkPath);
}
else if (matcher.match(EXERCISE_PATH_PATTERN, targetLinkPath)) {
pathVariables = matcher.extractUriTemplateVariables(EXERCISE_PATH_PATTERN, targetLinkPath);
}

if (pathVariables == null || !pathVariables.containsKey("courseId")) {
log.info("Could not extract courseId from target link: {}", targetLinkUrl);
return Optional.empty();
}

String courseId = pathVariables.get("courseId");
return courseRepository.findById(Long.valueOf(courseId));
}

private boolean getCompetencyFromTargetLink(String targetLinkUrl) {
AntPathMatcher matcher = new AntPathMatcher();

String targetLinkPath;
try {
targetLinkPath = new URI(targetLinkUrl).getPath();
}
catch (URISyntaxException ex) {
log.info("Malformed target link url: {}", targetLinkUrl);
return false;
}

if (!matcher.match(COMPETENCY_PATH_PATTERN, targetLinkPath)) {
log.info("Could not extract competency from target link: {}", targetLinkUrl);
return false;
}

log.info("Competency link detected: {}", targetLinkUrl);
return true;
}

private void createOrUpdateResourceLaunch(Lti13LaunchRequest launchRequest, User user, Exercise exercise) {
Optional<LtiResourceLaunch> launchOpt = launchRepository.findByIssAndSubAndDeploymentIdAndResourceLinkId(launchRequest.iss(), launchRequest.sub(),
launchRequest.deploymentId(), launchRequest.resourceLinkId());
Expand All @@ -332,6 +396,17 @@
launchRepository.save(launch);
}

private void handleLaunchRequest(Lti13LaunchRequest launchRequest, User user, Exercise exercise, OidcIdToken ltiIdToken, OnlineCourseConfiguration onlineCourseConfiguration) {
createOrUpdateResourceLaunch(launchRequest, user, exercise);

ltiService.authenticateLtiUser(ltiIdToken.getEmail(), createUsernameFromLaunchRequest(ltiIdToken, onlineCourseConfiguration), ltiIdToken.getGivenName(),
ltiIdToken.getFamilyName(), onlineCourseConfiguration.isRequireExistingUser());

if (exercise != null) {
ltiService.onSuccessfulLtiAuthentication(user, exercise);
}
}

/**
* Build the response for the LTI launch.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,31 @@ public LtiDeepLinkingService(ExerciseRepository exerciseRepository, Lti13TokenRe
* @return Constructed deep linking response URL.
* @throws BadRequestAlertException if there are issues with the OIDC ID token claims.
*/
public String performDeepLinking(OidcIdToken ltiIdToken, String clientRegistrationId, Long courseId, Set<Long> exerciseIds) {
public String performExerciseDeepLinking(OidcIdToken ltiIdToken, String clientRegistrationId, Long courseId, Set<Long> exerciseIds) {
// Initialize DeepLinkingResponse
Lti13DeepLinkingResponse lti13DeepLinkingResponse = Lti13DeepLinkingResponse.from(ltiIdToken, clientRegistrationId);
// Fill selected exercise link into content items
ArrayList<Map<String, Object>> contentItems = this.populateContentItems(String.valueOf(courseId), exerciseIds);
ArrayList<Map<String, Object>> contentItems = this.populateContentItems(String.valueOf(courseId), exerciseIds, false);
lti13DeepLinkingResponse = lti13DeepLinkingResponse.setContentItems(contentItems);

// Prepare return url with jwt and id parameters
return this.buildLtiDeepLinkResponse(clientRegistrationId, lti13DeepLinkingResponse);
}

/**
* Constructs an LTI Deep Linking response URL with JWT for the competencies of the course.
*
* @param ltiIdToken OIDC ID token with the user's authentication claims.
* @param clientRegistrationId Client registration ID for the LTI tool.
* @param courseId ID of the course for deep linking.
* @return Constructed deep linking response URL.
* @throws BadRequestAlertException if there are issues with the OIDC ID token claims.
*/
public String performCompetencyDeepLinking(OidcIdToken ltiIdToken, String clientRegistrationId, Long courseId) {
// Initialize DeepLinkingResponse
Lti13DeepLinkingResponse lti13DeepLinkingResponse = Lti13DeepLinkingResponse.from(ltiIdToken, clientRegistrationId);
// Fill selected exercise link into content items
ArrayList<Map<String, Object>> contentItems = this.populateContentItems(String.valueOf(courseId), null, true);
lti13DeepLinkingResponse = lti13DeepLinkingResponse.setContentItems(contentItems);

// Prepare return url with jwt and id parameters
Expand Down Expand Up @@ -98,23 +118,34 @@ private String buildLtiDeepLinkResponse(String clientRegistrationId, Lti13DeepLi
* @param courseId The course ID.
* @param exerciseIds The set of exercise IDs.
*/
private ArrayList<Map<String, Object>> populateContentItems(String courseId, Set<Long> exerciseIds) {
private ArrayList<Map<String, Object>> populateContentItems(String courseId, Set<Long> exerciseIds, boolean competency) {
ArrayList<Map<String, Object>> contentItems = new ArrayList<>();
for (Long exerciseId : exerciseIds) {
Map<String, Object> item = setContentItem(courseId, String.valueOf(exerciseId));
contentItems.add(item);
if (!(exerciseIds == null)) {
for (Long exerciseId : exerciseIds) {
Map<String, Object> item = setExerciseContentItem(courseId, String.valueOf(exerciseId));
contentItems.add(item);
}
}
else if (competency) {
contentItems.add(setCompetencyContentItem(courseId));
}

return contentItems;
}

private Map<String, Object> setContentItem(String courseId, String exerciseId) {
private Map<String, Object> setExerciseContentItem(String courseId, String exerciseId) {
Optional<Exercise> exerciseOpt = exerciseRepository.findById(Long.valueOf(exerciseId));
String launchUrl = String.format(artemisServerUrl + "/courses/%s/exercises/%s", courseId, exerciseId);
return exerciseOpt.map(exercise -> createContentItem(exerciseOpt.get(), launchUrl)).orElse(null);
return exerciseOpt.map(exercise -> createExerciseContentItem(exerciseOpt.get(), launchUrl)).orElse(null);
}

private Map<String, Object> createContentItem(Exercise exercise, String url) {
private Map<String, Object> setCompetencyContentItem(String courseId) {
// TODO competency optional
String launchUrl = String.format(artemisServerUrl + "/courses/%s/competencies", courseId);
return createCompetencyContentItem(launchUrl);
}

private Map<String, Object> createExerciseContentItem(Exercise exercise, String url) {

Map<String, Object> item = new HashMap<>();
item.put("type", "ltiResourceLink");
Expand All @@ -125,6 +156,16 @@ private Map<String, Object> createContentItem(Exercise exercise, String url) {
return item;
}

private Map<String, Object> createCompetencyContentItem(String url) {

Map<String, Object> item = new HashMap<>();
item.put("type", "ltiResourceLink");
item.put("title", "competency");
item.put("url", url);

return item;
}

private void validateDeepLinkingResponseSettings(String returnURL, String jwt, String deploymentId) {
if (isEmptyString(jwt)) {
throw new BadRequestAlertException("Deep linking response cannot be created", "LTI", "deepLinkingResponseFailed");
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/de/tum/cit/aet/artemis/lti/web/LtiResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,14 @@ public ResponseEntity<OnlineCourseConfiguration> updateOnlineCourseConfiguration
* @param clientRegistrationId The identifier online of the course configuration.
* @return A ResponseEntity containing a JSON object with the 'targetLinkUri' property set to the deep linking response target link.
*/
// TODO Deep Linking 1
@PostMapping("lti13/deep-linking/{courseId}")
@EnforceAtLeastInstructor
public ResponseEntity<String> lti13DeepLinking(@PathVariable Long courseId, @RequestParam(name = "exerciseIds") Set<Long> exerciseIds,
public ResponseEntity<String> lti13DeepLinking(@PathVariable Long courseId, @RequestParam(name = "exerciseIds", required = false) Set<Long> exerciseIds,
@RequestParam(name = "lectureIds", required = false) Set<Long> lectureIds, @RequestParam(name = "competency", required = false) boolean competency,
@RequestParam(name = "learningPath", required = false) boolean learningPath, @RequestParam(name = "iris", required = false) boolean iris,
@RequestParam(name = "ltiIdToken") String ltiIdToken, @RequestParam(name = "clientRegistrationId") String clientRegistrationId) throws ParseException {
// TODO update message
log.info("LTI 1.3 Deep Linking request received for course {} with exercises {} for registrationId {}", courseId, exerciseIds, clientRegistrationId);

Course course = courseRepository.findByIdWithEagerOnlineCourseConfigurationElseThrow(courseId);
Expand All @@ -146,7 +150,14 @@ public ResponseEntity<String> lti13DeepLinking(@PathVariable Long courseId, @Req

OidcIdToken idToken = new OidcIdToken(ltiIdToken, null, null, SignedJWT.parse(ltiIdToken).getJWTClaimsSet().getClaims());

String targetLink = ltiDeepLinkingService.performDeepLinking(idToken, clientRegistrationId, courseId, exerciseIds);
String targetLink = "";

if (exerciseIds != null) {
targetLink = ltiDeepLinkingService.performExerciseDeepLinking(idToken, clientRegistrationId, courseId, exerciseIds);
}
else if (competency) {
targetLink = ltiDeepLinkingService.performCompetencyDeepLinking(idToken, clientRegistrationId, courseId);
}

ObjectNode json = new ObjectMapper().createObjectNode();
json.put("targetLinkUri", targetLink);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public class PublicLtiResource {
@EnforceNothing
public ResponseEntity<Void> lti13LaunchRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
String state = request.getParameter("state");

if (state == null) {
errorOnMissingParameter(response, "state");
return ResponseEntity.ok().build();
Expand Down
10 changes: 10 additions & 0 deletions src/main/webapp/app/course/manage/course-management.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ export class CourseManagementService {
.pipe(map((res: EntityResponseType) => this.processCourseEntityResponseType(res)));
}

/**
* finds the course with the provided unique identifier together with its exercises
* @param courseId - the id of the course to be found
*/
findWithExercisesAndLecturesAndCompetencies(courseId: number): Observable<EntityResponseType> {
return this.http
.get<Course>(`${this.resourceUrl}/${courseId}/with-exercises-lectures-competencies`, { observe: 'response' })
.pipe(map((res: EntityResponseType) => this.processCourseEntityResponseType(res)));
}

/**
* finds a course with the given id and eagerly loaded organizations
* @param courseId the id of the course to be found
Expand Down
9 changes: 9 additions & 0 deletions src/main/webapp/app/lti/lti.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ export const ltiLaunchRoutes: Routes = [
},
canActivate: [UserRouteAccessService],
},
{
path: 'competencies',
component: Lti13DeepLinkingComponent,
data: {
authorities: [Authority.INSTRUCTOR, Authority.ADMIN],
pageTitle: 'artemisApp.lti13.deepLinking.title',
},
canActivate: [UserRouteAccessService],
},
];

const LTI_LAUNCH_ROUTES = [...ltiLaunchRoutes];
Expand Down
Loading
Loading