expose launch url in AGS Line Item API
closes INTEROP-7267 flag=none why: * to facilitate 1.1 to 1.3 migration for tools that may only be able to link line items using the original resource link url test plan: * install the LTI 1.3 test tool * get an LTI token for that tool using `canvas.docker/api/lti/advantage_token?tool_id=1`, replacing 1 with the id of your 1.3 test tool * construct an API request for the AGS Line Items API for a course with at least one line item: `canvas.docker/api/lti/courses/1/line_items`, and add the LTI token to the Authorization header using `Bearer: <token>` * the request should return a list of line items in JSON format * add `?include[]=launch_url` to the request * each line item should now have a new canvas extension property that contains the launch url for that line item and its related assignment * change the request to request a specific line item (like add `/1` on the end), still with the query param * it should return one line item, with the new extension property * remove the query param or change the value to something else * it should not return the extension property Change-Id: I0caa494111fde4093f99fef1398b27799b07a6cf Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/287096 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Mysti Lilla <mysti@instructure.com> QA-Review: Mysti Lilla <mysti@instructure.com> Product-Review: Alexis Nast <alexis.nast@instructure.com>
This commit is contained in:
parent
6f60f41833
commit
0e3ddfd48b
|
@ -62,6 +62,11 @@ module Lti
|
|||
# "description": "The extension that defines the submission_type of the line_item. Only returns if set through the line_item create endpoint.",
|
||||
# "example": "{\n\t\"type\":\"external_tool\",\n\t\"external_tool_url\":\"https://my.launch.url\",\n}",
|
||||
# "type": "string"
|
||||
# },
|
||||
# "https://canvas.instructure.com/lti/launch_url": {
|
||||
# "description": "The launch url of the Line Item. Only returned if `include=launch_url` query parameter is passed, and only for Show and List actions.",
|
||||
# "example": "https://my.tool.url/launch",
|
||||
# "type": "string"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
@ -175,16 +180,22 @@ module Lti
|
|||
# @API Show a Line Item
|
||||
# Show existing Line Item
|
||||
#
|
||||
# @argument include[] [String, "launch_url"]
|
||||
# Array of additional information to include.
|
||||
#
|
||||
# "launch_url":: includes the launch URL for this line item using the "https\://canvas.instructure.com/lti/launch_url" extension
|
||||
#
|
||||
# @returns LineItem
|
||||
def show
|
||||
# the LineItem workflow_state still "active" even if the assignment is deleted
|
||||
head :not_found and return if line_item.assignment.deleted?
|
||||
|
||||
render json: LineItemsSerializer.new(line_item, line_item_id(line_item)),
|
||||
render json: LineItemsSerializer.new(line_item, line_item_id(line_item), include_launch_url?),
|
||||
content_type: MIME_TYPE
|
||||
end
|
||||
|
||||
# @API List line Items
|
||||
# List all Line Items for a course
|
||||
#
|
||||
# @argument tag [String]
|
||||
# If specified only Line Items with this tag will be included.
|
||||
|
@ -198,6 +209,11 @@ module Lti
|
|||
# @argument limit [String]
|
||||
# May be used to limit the number of Line Items returned in a page
|
||||
#
|
||||
# @argument include[] [String, "launch_url"]
|
||||
# Array of additional information to include.
|
||||
#
|
||||
# "launch_url":: includes the launch URL for each line item using the "https\://canvas.instructure.com/lti/launch_url" extension
|
||||
#
|
||||
# @returns LineItem
|
||||
def index
|
||||
line_items = Api.paginate(
|
||||
|
@ -223,6 +239,10 @@ module Lti
|
|||
|
||||
private
|
||||
|
||||
def include_launch_url?
|
||||
params[:include]&.include? "launch_url"
|
||||
end
|
||||
|
||||
def line_item_params
|
||||
@_line_item_params ||= params.permit(%i[resourceId resourceLinkId scoreMaximum label tag],
|
||||
Lti::LineItem::AGS_EXT_SUBMISSION_TYPE => [:type, :external_tool_url]).transform_keys do |k|
|
||||
|
@ -278,7 +298,7 @@ module Lti
|
|||
end
|
||||
|
||||
def line_item_collection(line_items)
|
||||
line_items.map { |li| LineItemsSerializer.new(li, line_item_id(li)) }
|
||||
line_items.map { |li| LineItemsSerializer.new(li, line_item_id(li), include_launch_url?) }
|
||||
end
|
||||
|
||||
def verify_valid_resource_link
|
||||
|
|
|
@ -47,12 +47,18 @@ class Lti::LineItem < ApplicationRecord
|
|||
before_destroy :destroy_resource_link, if: :assignment_line_item? # assignment will destroy all the other line_items of a resourceLink
|
||||
before_destroy :destroy_assignment
|
||||
|
||||
AGS_EXT_SUBMISSION_TYPE = "https://canvas.instructure.com/lti/submission_type"
|
||||
AGS_EXT_PREFIX = "https://canvas.instructure.com/lti/"
|
||||
AGS_EXT_SUBMISSION_TYPE = "#{AGS_EXT_PREFIX}submission_type"
|
||||
AGS_EXT_LAUNCH_URL = "#{AGS_EXT_PREFIX}launch_url"
|
||||
|
||||
def assignment_line_item?
|
||||
assignment.line_items.order(:created_at).first.id == id
|
||||
end
|
||||
|
||||
def launch_url_extension
|
||||
{ AGS_EXT_LAUNCH_URL => assignment.external_tool_tag&.url }
|
||||
end
|
||||
|
||||
def self.create_line_item!(assignment, context, tool, params)
|
||||
transaction do
|
||||
assignment_attr = {
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
module Lti::IMS
|
||||
class LineItemsSerializer
|
||||
def initialize(line_item, line_item_url)
|
||||
def initialize(line_item, line_item_url, include_launch_url = false)
|
||||
@line_item = line_item
|
||||
@line_item_url = line_item_url
|
||||
@include_launch_url = include_launch_url
|
||||
end
|
||||
|
||||
def as_json
|
||||
|
@ -31,8 +32,10 @@ module Lti::IMS
|
|||
label: @line_item.label,
|
||||
resourceId: @line_item.resource_id,
|
||||
tag: @line_item.tag,
|
||||
resourceLinkId: @line_item.resource_link&.resource_link_uuid
|
||||
}.merge(@line_item.extensions).compact
|
||||
resourceLinkId: @line_item.resource_link&.resource_link_uuid,
|
||||
}.merge(@line_item.extensions)
|
||||
.merge(@include_launch_url ? @line_item.launch_url_extension : {})
|
||||
.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -575,6 +575,22 @@ module Lti
|
|||
expect(response).to be_not_found
|
||||
end
|
||||
end
|
||||
|
||||
context "without include=launch_url parameter" do
|
||||
it "does not include launch url extension" do
|
||||
send_request
|
||||
expect(parsed_response_body).not_to have_key(Lti::LineItem::AGS_EXT_LAUNCH_URL)
|
||||
end
|
||||
end
|
||||
|
||||
context "with include[]=launch_url parameter" do
|
||||
let(:params_overrides) { super().merge({ "include[]" => "launch_url" }) }
|
||||
|
||||
it "includes launch url extension in line item json" do
|
||||
send_request
|
||||
expect(parsed_response_body).to include(Lti::LineItem::AGS_EXT_LAUNCH_URL => tool.url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#index" do
|
||||
|
@ -707,6 +723,22 @@ module Lti
|
|||
send_request
|
||||
expect(response.headers).to have_key("Link")
|
||||
end
|
||||
|
||||
context "without include=launch_url parameter" do
|
||||
it "does not include launch url extension" do
|
||||
send_request
|
||||
expect(parsed_response_body).not_to include(have_key(Lti::LineItem::AGS_EXT_LAUNCH_URL))
|
||||
end
|
||||
end
|
||||
|
||||
context "with include[]=launch_url parameter" do
|
||||
let(:params_overrides) { super().merge({ "include[]" => "launch_url" }) }
|
||||
|
||||
it "includes launch url extension in line item json" do
|
||||
send_request
|
||||
expect(parsed_response_body).to all(include(Lti::LineItem::AGS_EXT_LAUNCH_URL => tool.url))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "destroy" do
|
||||
|
|
|
@ -74,6 +74,25 @@ RSpec.describe Lti::LineItem, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#launch_url_extension" do
|
||||
let(:url) { "https://example.com/launch" }
|
||||
let(:assignment) do
|
||||
a = assignment_model
|
||||
a.external_tool_tag = ContentTag.create!(context: a, url: url)
|
||||
a.save!
|
||||
a
|
||||
end
|
||||
let(:line_item) { line_item_model(assignment: assignment) }
|
||||
|
||||
it "returns hash with extension key" do
|
||||
expect(line_item.launch_url_extension).to have_key(Lti::LineItem::AGS_EXT_LAUNCH_URL)
|
||||
end
|
||||
|
||||
it "returns launch url in hash" do
|
||||
expect(line_item.launch_url_extension[Lti::LineItem::AGS_EXT_LAUNCH_URL]).to eq url
|
||||
end
|
||||
end
|
||||
|
||||
context "with lti_link not matching assignment" do
|
||||
let(:resource_link) { resource_link_model }
|
||||
let(:line_item) { line_item_model resource_link: resource_link }
|
||||
|
|
|
@ -75,16 +75,37 @@ RSpec.describe Lti::IMS::LineItemsSerializer do
|
|||
)
|
||||
end
|
||||
|
||||
it "does not incude values that are nil" do
|
||||
line_item.update!(resource_link: nil, tag: nil)
|
||||
expect(described_class.new(line_item, line_item_id).as_json).to eq(
|
||||
{
|
||||
id: line_item_id,
|
||||
scoreMaximum: line_item.score_maximum,
|
||||
label: line_item.label,
|
||||
resourceId: line_item.resource_id
|
||||
}
|
||||
)
|
||||
context "with nil values" do
|
||||
before do
|
||||
line_item.update!(resource_link: nil, tag: nil)
|
||||
end
|
||||
|
||||
it "does not incude values that are nil" do
|
||||
expect(described_class.new(line_item, line_item_id).as_json).to eq(
|
||||
{
|
||||
id: line_item_id,
|
||||
scoreMaximum: line_item.score_maximum,
|
||||
label: line_item.label,
|
||||
resourceId: line_item.resource_id
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "with submission_type extensions" do
|
||||
before do
|
||||
line_item.update!(extensions: { Lti::LineItem::AGS_EXT_SUBMISSION_TYPE => "extension" })
|
||||
end
|
||||
|
||||
it "includes extension" do
|
||||
expect(described_class.new(line_item, line_item_id).as_json).to include(Lti::LineItem::AGS_EXT_SUBMISSION_TYPE => "extension")
|
||||
end
|
||||
end
|
||||
|
||||
context "with launch_url extensions" do
|
||||
it "includes extension" do
|
||||
expect(described_class.new(line_item, line_item_id, true).as_json).to include(Lti::LineItem::AGS_EXT_LAUNCH_URL => tool.url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue