-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'staging' into textbooks
- Loading branch information
Showing
42 changed files
with
1,539 additions
and
588 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
name: image-build | ||
on: | ||
push: | ||
branches: | ||
- master | ||
jobs: | ||
build: | ||
name: opendsa-lti-image | ||
runs-on: self-hosted | ||
steps: | ||
- | ||
name: Checkout | ||
uses: actions/checkout@v3 | ||
- | ||
name: Set up QEMU | ||
uses: docker/setup-qemu-action@v3 | ||
- | ||
name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v3 | ||
- | ||
name: Login to Docker Hub | ||
uses: docker/login-action@v3 | ||
with: | ||
username: ${{ secrets.DOCKER_USERNAME }} | ||
password: ${{ secrets.DOCKER_TOKEN }} | ||
- | ||
name: Build and push | ||
uses: docker/build-push-action@v5 | ||
with: | ||
context: . | ||
push: true | ||
tags: opendsa/opendsa-lti:latest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,53 @@ | ||
class ExportController < ApplicationController | ||
# GET /export | ||
# gives an export of opendsa embeddable slideshows and exercises | ||
# with iframe and lti urls for SPLICE catalog | ||
def index | ||
|
||
host_url = request.base_url | ||
@exercises = InstExercise.joins(inst_book_section_exercises: { inst_section: { inst_chapter_module: :inst_chapter } }) | ||
.select("inst_exercises.*, inst_chapters.name AS chapter_name") | ||
inst_book = InstBook.first | ||
raise "InstBook instance not found" unless inst_book | ||
|
||
av_data = inst_book.extract_av_data_from_rst | ||
@exercises = InstExercise.all | ||
|
||
export_data = @exercises.map do |exercise| | ||
matching_chapter = nil | ||
matching_avmetadata = nil | ||
|
||
av_data.each do |chapter, data| | ||
if data[:inlineav]&.include?(exercise.short_name) || data[:avembed]&.include?(exercise.short_name) | ||
matching_avmetadata = data[:avmetadata] | ||
matching_chapter = chapter | ||
break | ||
end | ||
end | ||
|
||
# using array for keywords | ||
keywords = if matching_avmetadata && (matching_avmetadata[:satisfies] || matching_avmetadata[:topic] || matching_avmetadata[:keyword]) | ||
[matching_avmetadata[:satisfies], matching_avmetadata[:topic], matching_avmetadata[:keyword]].compact.flat_map { |k| k.split(/[,;]\s*/) } | ||
elsif matching_chapter | ||
[matching_chapter] # Use the chapter name if there are no specific keywords | ||
else | ||
[exercise.name] # Fallback to using the exercise's name as the keyword, if there are no specific keywords | ||
end | ||
|
||
{ | ||
"Platform_name": "OpenDSA", | ||
"URL": exercise.embed_url(host_url), | ||
"LTI_Instructions_URL": "https://opendsa-server.cs.vt.edu/guides/lti-instructions", | ||
"Exercise_type": exercise.ex_type, | ||
"License": "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)", | ||
"Description": exercise.description, | ||
"Author": "Shaffer", | ||
"Institution": "Virginia Tech", | ||
"Keywords": exercise.chapter_name, | ||
"Exercise_Name": exercise.name, | ||
"Iframe_URL": exercise.embed_url(host_url), | ||
"LTI_URL": "#{host_url}/lti/launch?custom_ex_short_name=#{exercise.short_name}" | ||
|
||
"catalog_type": "SLCItemCatalog", | ||
"platform_name": "OpenDSA", | ||
"url": exercise.embed_url(host_url), | ||
"lti_instructions_url": "https://opendsa-server.cs.vt.edu/guides/opendsa-canvas", | ||
"exercise_type": exercise.ex_type, | ||
"license": "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)", | ||
"description": exercise.description, | ||
"author": "Cliff Shaffer", | ||
"institution": "Virginia Tech", | ||
"keywords": keywords, | ||
"exercise_name": exercise.name, | ||
"iframe_url": exercise.embed_url(host_url), | ||
"lti_url": "#{host_url}/lti/launch?custom_ex_short_name=#{exercise.short_name}&custom_ex_settings=%7B%7D" | ||
} | ||
end | ||
end.compact | ||
|
||
render json: export_data | ||
end | ||
end | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,85 @@ | ||
class Lti13::DeepLinkLaunchesController < ApplicationController | ||
before_action :set_tool | ||
skip_before_action only: :create | ||
|
||
# POST lti/tools/#/deep_link_launches | ||
# Not much different than LTI launch endpoint inside a simple reference implementation but you | ||
# should have a diff endpoint for deeplinks than your LTI resource link request for single responsiblity | ||
def create | ||
if params[:id_token]&.present? | ||
@decoded_header = Jwt::Header.new(params[:id_token]).call | ||
kid = @decoded_header['kid'] | ||
|
||
@decoded_jwt = Lti13Service::DecodePlatformJwt.new(@tool, params[:id_token], kid).call | ||
@launch = @tool.launches.build(jwt: params[:id_token], decoded_jwt: @decoded_jwt ? @decoded_jwt.first : nil, state: params[:state]) | ||
end | ||
|
||
@launch ||= Launch.new | ||
respond_to do |format| | ||
if @launch.save | ||
format.html { redirect_to [:lti, @tool, @launch], notice: 'Successful Launch.' } | ||
format.json { render :show, status: :created, location: @launch } | ||
else | ||
format.html { render json: 'Invalid Launch', status: :unprocessable_entity } | ||
format.json { render json: @launch.errors, status: :unprocessable_entity } | ||
end | ||
end | ||
end | ||
|
||
# GET lti/tools/#/deep_link_launch/*launch_id* | ||
# page that allows user to select content | ||
def show | ||
@launch = Launch.find(params[:id]) | ||
before_action :set_tool | ||
skip_before_action only: :create | ||
after_action :allow_iframe, only: [:show, :launch, :content_selection, :content_selected] | ||
|
||
# POST lti/tools/#/deep_link_launches | ||
# Handles the creation of a deep link launch | ||
def create | ||
if params[:id_token]&.present? | ||
@decoded_header = Jwt::Header.new(params[:id_token]).call | ||
kid = @decoded_header['kid'] | ||
|
||
@decoded_jwt = Lti13Service::DecodePlatformJwt.new(@tool, params[:id_token], kid).call | ||
@launch = @tool.launches.build(jwt: params[:id_token], decoded_jwt: @decoded_jwt ? @decoded_jwt.first : nil, state: params[:state]) | ||
end | ||
|
||
# GET lti/tools/#/deep_link_launch/*launch_id*/launch | ||
# takes selected content and launches back to platform with JWT | ||
def launch | ||
@launch = Launch.find(params[:deep_link_launch_id]) | ||
@form_url = @launch.decoded_jwt[Rails.configuration.lti_claims_and_scopes['deep_linking_claim']]['deep_link_return_url'] | ||
@deep_link_jwt = Lti13Service::DeepLinkJwt.new(@launch, lti_tool_launches_url(@tool), params[:content_items]) | ||
end | ||
|
||
private | ||
def set_tool | ||
@tool = Tool.find_by_id(params[:tool_id]) | ||
render json: { error: 'Tool not found' }, status: :not_found unless @tool | ||
|
||
@launch ||= Launch.new | ||
respond_to do |format| | ||
if @launch.save | ||
format.html { redirect_to [:lti13, @tool, @launch], notice: 'Successful Launch.' } | ||
format.json { render :show, status: :created, location: @launch } | ||
else | ||
format.html { render json: 'Invalid Launch', status: :unprocessable_entity } | ||
format.json { render json: @launch.errors, status: :unprocessable_entity } | ||
end | ||
end | ||
end | ||
end | ||
|
||
# GET lti/tools/#/deep_link_launch/*launch_id* | ||
# allows user to select content | ||
def show | ||
@launch = Launch.find(params[:id]) | ||
end | ||
|
||
# GET lti/tools/#/deep_link_launch/*launch_id*/launch | ||
# takes selected content and launches back to platform with JWT | ||
def launch | ||
@launch = Launch.find(params[:deep_link_launch_id]) | ||
@form_url = @launch.decoded_jwt[Rails.configuration.lti_claims_and_scopes['deep_linking_claim']]['deep_link_return_url'] | ||
@deep_link_jwt = Lti13Service::DeepLinkJwt.new(@launch, lti_tool_launches_url(@tool), params[:content_items]) | ||
end | ||
|
||
# GET lti13/deep_linking/content_selection | ||
def content_selection | ||
@launch_url = request.protocol + request.host_with_port + "/lti13/launches" | ||
module_info = InstModule.get_current_versions_dict() | ||
@json = module_info.to_json | ||
|
||
Rails.logger.info "Launch URL: #{@launch_url}" | ||
Rails.logger.debug "Module Info JSON: #{@json.inspect}" | ||
render 'resource', layout: 'lti_resource' | ||
end | ||
|
||
# POST lti13/deep_linking/content_selected | ||
def content_selected | ||
@launch = Launch.find(params[:launch_id]) | ||
@form_url = @launch.decoded_jwt[Rails.configuration.lti_claims_and_scopes['deep_linking_claim']]['deep_link_return_url'] | ||
selected_content = params[:selected_content] | ||
Rails.logger.info "Selected Content: #{selected_content}" | ||
|
||
deep_link_jwt_service = Lti13Service::DeepLinkJwt.new(@launch, selected_content) | ||
deep_link_jwt = deep_link_jwt_service.call | ||
Rails.logger.info "Deep Link JWT: #{deep_link_jwt}" | ||
|
||
# Return the selected content to LMS | ||
redirect_to "#{@form_url}?JWT=#{deep_link_jwt}" | ||
end | ||
|
||
#~ Private methods .......................................................... | ||
|
||
private | ||
# ------------------------------------------------------------- | ||
|
||
def set_tool | ||
@tool = Tool.find_by_id(params[:tool_id]) | ||
render json: { error: 'Tool not found' }, status: :not_found unless @tool | ||
end | ||
|
||
def allow_iframe | ||
response.headers.except! 'X-Frame-Options' | ||
puts "Response headers after removing X-Frame-Options from deep_link_controller: #{response.headers.inspect}" | ||
response.headers['Content-Security-Policy'] = "frame-ancestors 'self' https://canvas.endeavour.cs.vt.edu" | ||
end | ||
|
||
end |
Oops, something went wrong.