Add Lti Link model for LTI 2 launches
refs PLAT-2724 Test plan: * Regression test plagiarism platform Change-Id: I98fd4efc5f259bd73747337d07a7e2ec2508dbb0 Reviewed-on: https://gerrit.instructure.com/122906 Reviewed-by: Jeremy Neander <jneander@instructure.com> Reviewed-by: Nathan Mills <nathanm@instructure.com> Reviewed-by: Weston Dransfield <wdransfield@instructure.com> Tested-by: Jenkins Reviewed-by: Cody Cutrer <cody@instructure.com> Reviewed-by: Keith T. Garner <kgarner@instructure.com> QA-Review: August Thornton <august@instructure.com> Product-Review: Andrew Butterfield <abutterfield@instructure.com>
This commit is contained in:
parent
1be4796345
commit
d29aaaf6a1
|
@ -81,9 +81,9 @@ module Lti
|
|||
private :reregistration_message
|
||||
|
||||
def resource
|
||||
tool_setting = ToolSetting.find_by(resource_link_id: params[:resource_link_id])
|
||||
return not_found if tool_setting.blank?
|
||||
basic_launch_by_tool_setting(tool_setting)
|
||||
lti_link = Link.find_by(resource_link_id: params[:resource_link_id])
|
||||
return not_found if lti_link.blank?
|
||||
basic_launch_by_lti_link(lti_link)
|
||||
end
|
||||
|
||||
def basic_lti_launch_request
|
||||
|
@ -133,26 +133,26 @@ module Lti
|
|||
message_handler.launch_path
|
||||
end
|
||||
|
||||
def basic_launch_by_tool_setting(tool_setting)
|
||||
message_handler = tool_setting.message_handler(@context)
|
||||
def basic_launch_by_lti_link(lti_link)
|
||||
message_handler = lti_link.message_handler(@context)
|
||||
if message_handler.present?
|
||||
return lti2_basic_launch(message_handler, tool_setting.resource_url, tool_setting.resource_link_id)
|
||||
return lti2_basic_launch(message_handler, lti_link)
|
||||
end
|
||||
not_found
|
||||
rescue InvalidDomain => e
|
||||
return render json: {errors: {invalid_launch_url: {message: e.message}}}, status: 400
|
||||
end
|
||||
|
||||
def lti2_basic_launch(message_handler, resource_url = nil, resource_link_id = nil)
|
||||
def lti2_basic_launch(message_handler, lti_link = nil)
|
||||
resource_handler = message_handler.resource_handler
|
||||
tool_proxy = resource_handler.tool_proxy
|
||||
# TODO: create scope for query
|
||||
if tool_proxy.workflow_state == 'active'
|
||||
launch_attrs = {
|
||||
launch_url: launch_url(resource_url, message_handler),
|
||||
launch_url: launch_url(lti_link&.resource_url, message_handler),
|
||||
oauth_consumer_key: tool_proxy.guid,
|
||||
lti_version: IMS::LTI::Models::LTIModel::LTI_VERSION_2P0,
|
||||
resource_link_id: resource_link_id || message_handler.build_resource_link_id(context: @context,
|
||||
resource_link_id: lti_link&.resource_link_id || message_handler.build_resource_link_id(context: @context,
|
||||
link_fragment: params[:resource_link_fragment]),
|
||||
}
|
||||
|
||||
|
@ -164,9 +164,8 @@ module Lti
|
|||
custom_param_opts = prep_tool_settings(message_handler.parameters, tool_proxy, launch_attrs[:resource_link_id])
|
||||
custom_param_opts[:content_tag] = tag if tag
|
||||
custom_param_opts[:secure_params] = params[:secure_params] if params[:secure_params].present?
|
||||
tool_setting = ToolSetting.find_by(resource_link_id: resource_link_id)
|
||||
variable_expander = create_variable_expander(custom_param_opts.merge(tool: tool_proxy,
|
||||
tool_setting: tool_setting,
|
||||
originality_report: lti_link&.originality_report,
|
||||
launch: @lti_launch))
|
||||
launch_attrs.merge! enabled_parameters(tool_proxy, message_handler, variable_expander)
|
||||
|
||||
|
|
|
@ -109,15 +109,12 @@ module Lti
|
|||
}.freeze
|
||||
].freeze
|
||||
|
||||
rescue_from Lti::Errors::UnauthorizedToolError do |e|
|
||||
Lti::Errors::ErrorLogger.log_error(e)
|
||||
render_unauthorized_action
|
||||
end
|
||||
|
||||
skip_before_action :load_user
|
||||
before_action :authorized_lti2_tool, :plagiarism_feature_flag_enabled
|
||||
before_action :attachment_in_context, only: [:create]
|
||||
before_action :report_in_context, only: [:show]
|
||||
before_action :find_originality_report
|
||||
before_action :report_in_context, only: [:show, :update]
|
||||
before_action :ensure_tool_proxy_associated
|
||||
|
||||
# @API Create an Originality Report
|
||||
# Create a new OriginalityReport for the specified file
|
||||
|
@ -156,8 +153,8 @@ module Lti
|
|||
#
|
||||
# @returns OriginalityReport
|
||||
def create
|
||||
raise Lti::Errors::UnauthorizedToolError unless tool_proxy_associated?
|
||||
if originality_report.present?
|
||||
begin
|
||||
if @report.present?
|
||||
update
|
||||
else
|
||||
@report = OriginalityReport.new(create_report_params)
|
||||
|
@ -167,6 +164,9 @@ module Lti
|
|||
render json: @report.errors, status: :bad_request
|
||||
end
|
||||
end
|
||||
rescue StandError => e
|
||||
puts e.message
|
||||
end
|
||||
end
|
||||
|
||||
# @API Edit an Originality Report
|
||||
|
@ -204,12 +204,10 @@ module Lti
|
|||
#
|
||||
# @returns OriginalityReport
|
||||
def update
|
||||
report_in_context
|
||||
raise Lti::Errors::UnauthorizedToolError unless tool_proxy_associated?
|
||||
if originality_report.update_attributes(update_report_params)
|
||||
render json: api_json(originality_report, @current_user, session)
|
||||
if @report.update_attributes(update_report_params)
|
||||
render json: api_json(@report, @current_user, session)
|
||||
else
|
||||
render json: originality_report.errors, status: :bad_request
|
||||
render json: @report.errors, status: :bad_request
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -218,8 +216,7 @@ module Lti
|
|||
#
|
||||
# @returns OriginalityReport
|
||||
def show
|
||||
raise Lti::Errors::UnauthorizedToolError unless tool_proxy_associated?
|
||||
render json: api_json(originality_report, @current_user, session)
|
||||
render json: api_json(@report, @current_user, session)
|
||||
end
|
||||
|
||||
def lti2_service_name
|
||||
|
@ -228,19 +225,8 @@ module Lti
|
|||
|
||||
private
|
||||
|
||||
def link_id(tool_setting_params)
|
||||
resource_type_code = tool_setting_params&.dig('resource_type_code')
|
||||
if resource_type_code.present?
|
||||
rh = tool_proxy.resources.find_by(resource_type_code: resource_type_code)
|
||||
resource_link_id(rh, tool_setting_params['resource_url'])
|
||||
end
|
||||
end
|
||||
|
||||
def resource_link_id(resource_handler, lti_url = nil)
|
||||
tool_setting = resource_handler.find_or_create_tool_setting(resource_url: lti_url,
|
||||
link_fragment: link_fragment,
|
||||
context: attachment_association)
|
||||
tool_setting.resource_link_id
|
||||
def ensure_tool_proxy_associated
|
||||
render_unauthorized_action unless tool_proxy_associated?
|
||||
end
|
||||
|
||||
def link_fragment
|
||||
|
@ -260,16 +246,18 @@ module Lti
|
|||
:file_id,
|
||||
:originality_report_file_id,
|
||||
:originality_report_url,
|
||||
:workflow_state,
|
||||
tool_setting: %i(resource_url resource_type_code)].freeze
|
||||
:workflow_state].freeze
|
||||
end
|
||||
|
||||
def update_attributes
|
||||
[:originality_report_file_id,
|
||||
:originality_report_url,
|
||||
:originality_score,
|
||||
:workflow_state,
|
||||
tool_setting: %i(resource_url resource_type_code)].freeze
|
||||
:workflow_state].freeze
|
||||
end
|
||||
|
||||
def lti_link_attributes
|
||||
[tool_setting: %i(resource_url resource_type_code)].freeze
|
||||
end
|
||||
|
||||
def assignment
|
||||
|
@ -308,7 +296,7 @@ module Lti
|
|||
report_attributes = params.require(:originality_report).permit(create_attributes).to_unsafe_h.merge(
|
||||
{submission_id: params.require(:submission_id)}
|
||||
)
|
||||
report_attributes['link_id'] = link_id(report_attributes.delete('tool_setting'))
|
||||
report_attributes[:lti_link_attributes] = lti_link_params
|
||||
report_attributes
|
||||
end
|
||||
end
|
||||
|
@ -316,32 +304,46 @@ module Lti
|
|||
def update_report_params
|
||||
@_update_report_params ||= begin
|
||||
report_attributes = params.require(:originality_report).permit(update_attributes)
|
||||
report_attributes['link_id'] = link_id(report_attributes.delete('tool_setting'))
|
||||
report_attributes[:lti_link_attributes] = lti_link_params
|
||||
report_attributes
|
||||
end
|
||||
end
|
||||
|
||||
def lti_link_params
|
||||
@_lti_link_params ||= begin
|
||||
tool_settings = params.require(:originality_report).permit(lti_link_attributes).to_unsafe_h
|
||||
|
||||
if tool_settings&.dig('tool_setting', 'resource_type_code')
|
||||
tool_settings['tool_setting'].merge({
|
||||
id: @report&.lti_link&.id,
|
||||
product_code: tool_proxy.product_family.product_code,
|
||||
vendor_code: tool_proxy.product_family.vendor_code
|
||||
})
|
||||
else
|
||||
{
|
||||
id: @report&.lti_link&.id,
|
||||
_destroy: true
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def attachment_in_context
|
||||
verify_submission_attachment(attachment, submission)
|
||||
end
|
||||
|
||||
def originality_report
|
||||
@_originality_report ||= begin
|
||||
OriginalityReport.find_by(id: params[:id]) ||
|
||||
OriginalityReport.find_by(attachment_id: attachment&.id)
|
||||
end
|
||||
def find_originality_report
|
||||
@report = OriginalityReport.find_by(id: params[:id]) || OriginalityReport.find_by(attachment_id: attachment&.id)
|
||||
end
|
||||
|
||||
def report_in_context
|
||||
raise ActiveRecord::RecordNotFound if originality_report.blank?
|
||||
verify_submission_attachment(originality_report.attachment, submission)
|
||||
raise ActiveRecord::RecordNotFound if @report.blank?
|
||||
verify_submission_attachment(@report.attachment, submission)
|
||||
end
|
||||
|
||||
def verify_submission_attachment(attachment, submission)
|
||||
raise ActiveRecord::RecordNotFound unless attachment.present? && submission.present?
|
||||
unless submission.assignment == assignment && submission.attachments.include?(attachment)
|
||||
raise Lti::Errors::UnauthorizedToolError
|
||||
end
|
||||
render_unauthorized_action unless submission.assignment == assignment && submission.attachments.include?(attachment)
|
||||
end
|
||||
|
||||
# @!appendix Originality Report UI Locations
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
module Lti
|
||||
class Link < ApplicationRecord
|
||||
belongs_to :linkable, polymorphic: [:originality_report]
|
||||
validates :vendor_code, :product_code, :resource_type_code, :resource_link_id, presence: true
|
||||
validates :resource_link_id, uniqueness: true
|
||||
|
||||
before_validation :generate_resource_link_id, on: :create
|
||||
|
||||
serialize :custom_parameters
|
||||
|
||||
def message_handler(context)
|
||||
MessageHandler.by_resource_codes(vendor_code: vendor_code,
|
||||
product_code: product_code,
|
||||
resource_type_code: resource_type_code,
|
||||
context: context)
|
||||
end
|
||||
|
||||
def originality_report
|
||||
if linkable.is_a?(OriginalityReport)
|
||||
linkable
|
||||
end
|
||||
end
|
||||
|
||||
def generate_resource_link_id
|
||||
self.resource_link_id ||= SecureRandom.uuid
|
||||
end
|
||||
end
|
||||
end
|
|
@ -49,21 +49,5 @@ module Lti
|
|||
possible_handlers = ResourceHandler.by_product_family(product_families, context)
|
||||
possible_handlers.select { |rh| rh.resource_type_code == resource_type_code}
|
||||
end
|
||||
|
||||
def find_or_create_tool_setting(context: nil, resource_url: nil, link_fragment: nil)
|
||||
context ||= tool_proxy.context
|
||||
mh = message_handlers.find_by(message_type: MessageHandler::BASIC_LTI_LAUNCH_REQUEST)
|
||||
resource_link_id = mh.build_resource_link_id(context: context, link_fragment: link_fragment)
|
||||
|
||||
tool_setting = Lti::ToolSetting.find_by(resource_link_id: resource_link_id)
|
||||
tool_setting ||= ToolSetting.new
|
||||
tool_setting.update_attributes(resource_link_id: resource_link_id,
|
||||
context: context,
|
||||
product_code: tool_proxy.product_family.product_code,
|
||||
vendor_code: tool_proxy.product_family.vendor_code,
|
||||
resource_type_code: resource_type_code,
|
||||
resource_url: resource_url)
|
||||
tool_setting
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,10 @@ class OriginalityReport < ActiveRecord::Base
|
|||
belongs_to :submission
|
||||
belongs_to :attachment
|
||||
belongs_to :originality_report_attachment, class_name: "Attachment"
|
||||
|
||||
has_one :lti_link, class_name: Lti::Link, as: :linkable, inverse_of: :linkable, dependent: :destroy
|
||||
accepts_nested_attributes_for :lti_link, allow_destroy: true
|
||||
|
||||
validates :attachment, :submission, presence: true
|
||||
validates :workflow_state, inclusion: { in: ['scored', 'error', 'pending'] }
|
||||
validates :originality_score, inclusion: { in: 0..100, message: 'score must be between 0 and 100' }, allow_nil: true
|
||||
|
@ -42,19 +46,18 @@ class OriginalityReport < ActiveRecord::Base
|
|||
super(options).tap do |h|
|
||||
h[:file_id] = h.delete :attachment_id
|
||||
h[:originality_report_file_id] = h.delete :originality_report_attachment_id
|
||||
if h[:link_id].present?
|
||||
tool_setting = Lti::ToolSetting.find_by(resource_link_id: h.delete(:link_id))
|
||||
h[:tool_setting] = { resource_url: tool_setting.resource_url,
|
||||
resource_type_code:tool_setting.resource_type_code }
|
||||
if lti_link.present?
|
||||
h[:tool_setting] = { resource_url: lti_link.resource_url,
|
||||
resource_type_code:lti_link.resource_type_code }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def report_launch_url
|
||||
if link_id.present?
|
||||
if lti_link.present?
|
||||
course_assignment_resource_link_id_url(course_id: assignment.context_id,
|
||||
assignment_id: assignment.id,
|
||||
resource_link_id: link_id,
|
||||
resource_link_id: lti_link.resource_link_id,
|
||||
host: HostUrl.context_host(assignment.context),
|
||||
display: 'borderless')
|
||||
else
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
class CreateLtiLinks < ActiveRecord::Migration[5.0]
|
||||
tag :predeploy
|
||||
|
||||
def change
|
||||
create_table :lti_links do |t|
|
||||
t.string :resource_link_id, null: false
|
||||
t.string :vendor_code, null: false
|
||||
t.string :product_code, null: false
|
||||
t.string :resource_type_code, null: false
|
||||
t.integer :linkable_id, limit: 8
|
||||
t.string :linkable_type
|
||||
t.text :custom_parameters
|
||||
t.text :resource_url
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :lti_links, [:linkable_id, :linkable_type]
|
||||
add_index :lti_links, :resource_link_id, unique: true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
class CreateLtiLinksForLegacyLtiToolSettings < ActiveRecord::Migration[5.0]
|
||||
tag :postdeploy
|
||||
|
||||
def up
|
||||
DataFixup::CreateLtiLinksForLegacyLtiToolSettings.send_later_if_production_enqueue_args(:run,
|
||||
priority: Delayed::LOW_PRIORITY,
|
||||
max_attempts: 1,
|
||||
n_strand: 'long_datafixups'
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
module DataFixup::CreateLtiLinksForLegacyLtiToolSettings
|
||||
|
||||
def self.run
|
||||
Lti::ToolSetting.where.not(product_code: nil, vendor_code: nil, resource_type_code: nil, resource_link_id: nil).find_each do |tool_setting|
|
||||
Lti::Link.transaction do
|
||||
originality_report = OriginalityReport.find_by(link_id: tool_setting.resource_link_id)
|
||||
link = Lti::Link.create_with({
|
||||
product_code: tool_setting.product_code,
|
||||
vendor_code: tool_setting.vendor_code,
|
||||
resource_type_code: tool_setting.resource_type_code,
|
||||
custom_parameters: tool_setting.custom_parameters,
|
||||
resource_url: tool_setting.resource_url,
|
||||
linkable: originality_report
|
||||
}).find_or_create_by!(resource_link_id: tool_setting.resource_link_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -65,9 +65,9 @@ module Lti
|
|||
MEDIA_OBJECT_ID_GUARD = -> {@attachment && (@attachment.media_object || @attachment.media_entry_id )}
|
||||
LTI1_GUARD = -> { @tool.is_a?(ContextExternalTool) }
|
||||
MASQUERADING_GUARD = -> { !!@controller && @controller.logged_in_user != @current_user }
|
||||
ATTACHMENT_ASSOCIATION_GUARD = -> { @tool_setting&.context&.is_a?(AttachmentAssociation) }
|
||||
LTI_ASSIGN_ID = -> { @assignment.present? || @tool_setting&.context&.is_a?(AttachmentAssociation) || @secure_params.present? }
|
||||
MESSAGE_TOKEN_GUARD = -> { @post_message_token.present? || @launch&.instance_of?(Lti::Launch) }
|
||||
ORIGINALITY_REPORT_GUARD = -> { @originality_report.present? }
|
||||
LTI_ASSIGN_ID = -> { @assignment.present? || @originality_report.present? || @secure_params.present? }
|
||||
|
||||
def initialize(root_account, context, controller, opts = {})
|
||||
@root_account = root_account
|
||||
|
@ -147,8 +147,8 @@ module Lti
|
|||
-> do
|
||||
if @assignment
|
||||
@assignment.lti_context_id
|
||||
elsif @tool_setting&.context&.is_a?(AttachmentAssociation)
|
||||
@tool_setting.context.context.assignment.lti_context_id
|
||||
elsif @originality_report
|
||||
@originality_report.submission.assignment.lti_context_id
|
||||
elsif @secure_params.present?
|
||||
Lti::Security.decoded_lti_assignment_id(@secure_params)
|
||||
end
|
||||
|
@ -165,11 +165,9 @@ module Lti
|
|||
# ```
|
||||
register_expansion 'com.instructure.OriginalityReport.id', [],
|
||||
-> do
|
||||
@tool_setting.context.context.originality_reports.find do |r|
|
||||
r.attachment_id == @tool_setting.context.attachment_id
|
||||
end.id
|
||||
@originality_report.id
|
||||
end,
|
||||
ATTACHMENT_ASSOCIATION_GUARD,
|
||||
ORIGINALITY_REPORT_GUARD,
|
||||
default_name: 'com_instructure_originality_report_id'
|
||||
|
||||
# The Canvas id of the submission associated with the
|
||||
|
@ -180,8 +178,8 @@ module Lti
|
|||
# 23
|
||||
# ```
|
||||
register_expansion 'com.instructure.Submission.id', [],
|
||||
-> { @tool_setting.context.context_id },
|
||||
ATTACHMENT_ASSOCIATION_GUARD,
|
||||
-> { @originality_report.submission.id },
|
||||
ORIGINALITY_REPORT_GUARD,
|
||||
default_name: 'com_instructure_submission_id'
|
||||
|
||||
# The Canvas id of the file associated with the submission
|
||||
|
@ -192,8 +190,8 @@ module Lti
|
|||
# 23
|
||||
# ```
|
||||
register_expansion 'com.instructure.File.id', [],
|
||||
-> { @tool_setting.context.attachment_id },
|
||||
ATTACHMENT_ASSOCIATION_GUARD,
|
||||
-> { @originality_report.attachment.id },
|
||||
ORIGINALITY_REPORT_GUARD,
|
||||
default_name: 'com_instructure_file_id'
|
||||
|
||||
# the LIS identifier for the course offering
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/lti2_api_spec_helper')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../sharding_spec_helper')
|
||||
require_dependency "lti/ims/access_token_helper"
|
||||
|
||||
module Lti
|
||||
describe 'Originality Reports API', type: :request do
|
||||
specs_require_sharding
|
||||
|
@ -28,8 +29,8 @@ module Lti
|
|||
let(:aud) { host }
|
||||
before(:once) { attachment_model }
|
||||
before :each do
|
||||
message_handler.update_attributes(message_type: 'basic-lti-launch-request')
|
||||
course_factory(active_all: true)
|
||||
message_handler.update_attributes(message_type: 'basic-lti-launch-request')
|
||||
student_in_course active_all: true
|
||||
teacher_in_course active_all: true
|
||||
|
||||
|
@ -343,10 +344,38 @@ module Lti
|
|||
},
|
||||
headers: request_headers
|
||||
expect(response).to be_success
|
||||
tool_setting = Lti::ToolSetting.find_by(resource_link_id: OriginalityReport.find(@report.id).link_id)
|
||||
tool_setting = OriginalityReport.find(@report.id).lti_link
|
||||
expect(tool_setting.resource_url).to eq "http://www.lti-test.com"
|
||||
end
|
||||
|
||||
it "removes the lti link when tool_setting is not supplied" do
|
||||
put @endpoints[:update],
|
||||
params: {
|
||||
originality_report: {
|
||||
originality_score: 5,
|
||||
tool_setting: {
|
||||
resource_url: 'http://www.lti-test.com',
|
||||
resource_type_code: 'code'
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: request_headers
|
||||
expect(response).to be_success
|
||||
lti_link_id = OriginalityReport.find(@report.id).lti_link.id
|
||||
put @endpoints[:update],
|
||||
params: {
|
||||
originality_report: {
|
||||
originality_score: nil
|
||||
}
|
||||
},
|
||||
headers: request_headers
|
||||
expect(response).to be_success
|
||||
expect(Lti::Link.find_by(id: lti_link_id)).to be_nil
|
||||
report = OriginalityReport.find(@report.id)
|
||||
expect(report.lti_link).to be_nil
|
||||
expect(report.originality_score).to be_nil
|
||||
end
|
||||
|
||||
it "requires the plagiarism feature flag" do
|
||||
allow_any_instance_of(Account).to receive(:feature_enabled?).with(:plagiarism_detection_platform).and_return(false)
|
||||
|
||||
|
@ -389,26 +418,6 @@ module Lti
|
|||
expect(response_body['tool_setting']['resource_type_code']).to eq resource_handler.resource_type_code
|
||||
end
|
||||
|
||||
it 'sets the context for the associated tool setting' do
|
||||
score = 0.25
|
||||
put @endpoints[:update],
|
||||
params: {
|
||||
originality_report: {
|
||||
file_id: @attachment.id,
|
||||
originality_score: score,
|
||||
tool_setting: {
|
||||
resource_type_code: resource_handler.resource_type_code
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: request_headers
|
||||
response_body = JSON.parse(response.body)
|
||||
report = OriginalityReport.find(response_body['id'])
|
||||
tool_setting = Lti::ToolSetting.find_by(resource_link_id: report.link_id)
|
||||
attachment_association = report.attachment.attachment_associations.first
|
||||
expect(tool_setting.context).to eq attachment_association
|
||||
end
|
||||
|
||||
it 'sets the workflow state' do
|
||||
put @endpoints[:update],
|
||||
params: {
|
||||
|
@ -508,8 +517,8 @@ module Lti
|
|||
},
|
||||
headers: request_headers
|
||||
expect(response).to be_success
|
||||
tool_setting = Lti::ToolSetting.find_by(resource_link_id: OriginalityReport.find(@report.id).link_id)
|
||||
expect(tool_setting.resource_url).to eq "http://www.lti-test.com"
|
||||
lti_link = OriginalityReport.find(@report.id).lti_link
|
||||
expect(lti_link.resource_url).to eq "http://www.lti-test.com"
|
||||
end
|
||||
|
||||
it "requires the plagiarism feature flag" do
|
||||
|
@ -552,26 +561,6 @@ module Lti
|
|||
expect(response_body['tool_setting']['resource_type_code']).to eq resource_handler.resource_type_code
|
||||
end
|
||||
|
||||
it 'sets the context for the associated tool setting' do
|
||||
score = 0.25
|
||||
put @endpoints[:update_alt],
|
||||
params: {
|
||||
originality_report: {
|
||||
file_id: @attachment.id,
|
||||
originality_score: score,
|
||||
tool_setting: {
|
||||
resource_type_code: resource_handler.resource_type_code
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: request_headers
|
||||
response_body = JSON.parse(response.body)
|
||||
report = OriginalityReport.find(response_body['id'])
|
||||
tool_setting = Lti::ToolSetting.find_by(resource_link_id: report.link_id)
|
||||
attachment_association = AttachmentAssociation.where(submission: @submission).last
|
||||
expect(tool_setting.context).to eq attachment_association
|
||||
end
|
||||
|
||||
it 'sets the workflow state' do
|
||||
put @endpoints[:update_alt],
|
||||
params: {
|
||||
|
@ -713,24 +702,6 @@ module Lti
|
|||
expect(response_body['tool_setting']['resource_type_code']).to eq resource_handler.resource_type_code
|
||||
end
|
||||
|
||||
it 'sets the context for the associated tool setting' do
|
||||
post @endpoints[:create],
|
||||
params: {
|
||||
originality_report: {
|
||||
file_id: @attachment.id,
|
||||
tool_setting: {
|
||||
resource_type_code: resource_handler.resource_type_code
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: request_headers
|
||||
response_body = JSON.parse(response.body)
|
||||
report = OriginalityReport.find(response_body['id'])
|
||||
tool_setting = Lti::ToolSetting.find_by(resource_link_id: report.link_id)
|
||||
attachment_association = report.attachment.attachment_associations.first
|
||||
expect(tool_setting.context).to eq attachment_association
|
||||
end
|
||||
|
||||
it 'sets the workflow state' do
|
||||
post @endpoints[:create],
|
||||
params: {
|
||||
|
|
|
@ -208,20 +208,18 @@ module Lti
|
|||
|
||||
let(:link_id) {SecureRandom.uuid}
|
||||
|
||||
let(:tool_setting) do
|
||||
ToolSetting.new(tool_proxy: tool_proxy,
|
||||
context: course,
|
||||
resource_link_id: link_id,
|
||||
vendor_code: product_family.vendor_code,
|
||||
product_code: product_family.product_code,
|
||||
resource_type_code: resource_handler.resource_type_code)
|
||||
let(:lti_link) do
|
||||
Link.new(resource_link_id: link_id,
|
||||
vendor_code: product_family.vendor_code,
|
||||
product_code: product_family.product_code,
|
||||
resource_type_code: resource_handler.resource_type_code)
|
||||
end
|
||||
|
||||
before do
|
||||
message_handler.update_attributes(message_type: MessageHandler::BASIC_LTI_LAUNCH_REQUEST)
|
||||
resource_handler.message_handlers = [message_handler]
|
||||
resource_handler.save!
|
||||
tool_setting.save!
|
||||
lti_link.save!
|
||||
user_session(account_admin_user)
|
||||
end
|
||||
|
||||
|
@ -245,14 +243,12 @@ module Lti
|
|||
context 'resource_url' do
|
||||
let(:custom_url) {'http://www.samplelaunch.com/custom-resource-url'}
|
||||
let(:link_id) {SecureRandom.uuid}
|
||||
let(:tool_setting) do
|
||||
ToolSetting.create!(tool_proxy: tool_proxy,
|
||||
context: course,
|
||||
resource_link_id: link_id,
|
||||
vendor_code: product_family.vendor_code,
|
||||
product_code: product_family.product_code,
|
||||
resource_type_code: resource_handler.resource_type_code,
|
||||
resource_url: custom_url)
|
||||
let(:lti_link) do
|
||||
Link.create!(resource_link_id: link_id,
|
||||
vendor_code: product_family.vendor_code,
|
||||
product_code: product_family.product_code,
|
||||
resource_type_code: resource_handler.resource_type_code,
|
||||
resource_url: custom_url)
|
||||
end
|
||||
|
||||
it "uses the 'resource_url' if provided in the 'link_id'" do
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require "spec_helper"
|
||||
require File.expand_path(File.dirname(__FILE__) + '../../../lti2_course_spec_helper')
|
||||
|
||||
describe DataFixup::CreateLtiLinksForLegacyLtiToolSettings do
|
||||
include_context 'lti2_course_spec_helper'
|
||||
|
||||
let(:link_tool_setting) do
|
||||
Lti::ToolSetting.create!(
|
||||
tool_proxy: tool_proxy,
|
||||
context: course,
|
||||
resource_link_id: SecureRandom.uuid,
|
||||
vendor_code: product_family.vendor_code,
|
||||
product_code: product_family.product_code,
|
||||
resource_type_code: resource_handler.resource_type_code,
|
||||
resource_url: 'http://example.com/resource',
|
||||
custom_parameters: { foo: 'bar' }
|
||||
)
|
||||
end
|
||||
|
||||
let(:proxy_tool_setting) do
|
||||
Lti::ToolSetting.create!(
|
||||
tool_proxy: tool_proxy,
|
||||
custom: { param: 42 }
|
||||
)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
link_tool_setting
|
||||
proxy_tool_setting
|
||||
end
|
||||
|
||||
it 'creates lti links from link tool settings' do
|
||||
DataFixup::CreateLtiLinksForLegacyLtiToolSettings.run
|
||||
|
||||
lti_links = Lti::Link.all
|
||||
expect(lti_links.count).to eq 1
|
||||
lti_link = lti_links.first
|
||||
expect(lti_link.vendor_code).to eq link_tool_setting.vendor_code
|
||||
expect(lti_link.product_code).to eq link_tool_setting.product_code
|
||||
expect(lti_link.resource_type_code).to eq link_tool_setting.resource_type_code
|
||||
expect(lti_link.resource_url).to eq link_tool_setting.resource_url
|
||||
expect(lti_link.resource_link_id).to eq link_tool_setting.resource_link_id
|
||||
expect(lti_link.custom_parameters).to eq link_tool_setting.custom_parameters
|
||||
end
|
||||
end
|
|
@ -78,9 +78,7 @@ module Lti
|
|||
submission: submission,
|
||||
link_id: resource_link_id)
|
||||
end
|
||||
let(:attachment_association) { AttachmentAssociation.create!(context: submission, attachment: attachment) }
|
||||
let(:tool_setting) { Lti::ToolSetting.create!(context: attachment_association, resource_link_id: resource_link_id) }
|
||||
let(:variable_expander) { VariableExpander.new(root_account, account, controller, current_user: user, tool: tool, tool_setting: tool_setting) }
|
||||
let(:variable_expander) { VariableExpander.new(root_account, account, controller, current_user: user, tool: tool, originality_report: originality_report) }
|
||||
|
||||
it 'clears the lti_helper instance variable when you set the current_user' do
|
||||
expect(variable_expander.lti_helper).not_to be nil
|
||||
|
@ -279,7 +277,7 @@ module Lti
|
|||
it 'has a substitution for com.instructure.Assignment.lti.id' do
|
||||
exp_hash = {test: '$com.instructure.Assignment.lti.id'}
|
||||
variable_expander.expand_variables!(exp_hash)
|
||||
expect(exp_hash[:test]).to eq tool_setting.context.context.assignment.lti_context_id
|
||||
expect(exp_hash[:test]).to eq originality_report.submission.assignment.lti_context_id
|
||||
end
|
||||
|
||||
it 'has a substitution for com.instructure.Assignment.lti.id when there is no tool setting' do
|
||||
|
@ -348,6 +346,24 @@ module Lti
|
|||
expect(exp_hash[:test]).to eq 'api/lti/assignments/{assignment_id}/submissions/{submission_id}/originality_report'
|
||||
end
|
||||
|
||||
it 'has substitution for com.instructure.OriginalityReport.id' do
|
||||
exp_hash = {test: '$com.instructure.OriginalityReport.id'}
|
||||
variable_expander.expand_variables!(exp_hash)
|
||||
expect(exp_hash[:test]).to eq originality_report.id
|
||||
end
|
||||
|
||||
it 'has substitution for com.instructure.Submission.id' do
|
||||
exp_hash = {test: '$com.instructure.Submission.id'}
|
||||
variable_expander.expand_variables!(exp_hash)
|
||||
expect(exp_hash[:test]).to eq originality_report.submission.id
|
||||
end
|
||||
|
||||
it 'has substitution for com.instructure.File.id' do
|
||||
exp_hash = {test: '$com.instructure.File.id'}
|
||||
variable_expander.expand_variables!(exp_hash)
|
||||
expect(exp_hash[:test]).to eq originality_report.attachment.id
|
||||
end
|
||||
|
||||
it 'has substitution for vnd.Canvas.submission.url' do
|
||||
exp_hash = {test: '$vnd.Canvas.submission.url'}
|
||||
variable_expander.expand_variables!(exp_hash)
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../lti2_spec_helper.rb')
|
||||
module Lti
|
||||
RSpec.describe Link, type: :model do
|
||||
include_context 'lti2_spec_helper'
|
||||
|
||||
describe "validations" do
|
||||
let(:params) do
|
||||
{
|
||||
vendor_code: 'vendor_code',
|
||||
product_code: 'product_code',
|
||||
resource_type_code: 'resource_type_code',
|
||||
resource_link_id: 'resource_link_id'
|
||||
}
|
||||
end
|
||||
it "requires vendor_code" do
|
||||
params.delete :vendor_code
|
||||
link = Lti::Link.new(params)
|
||||
expect(link.valid?).to eq false
|
||||
end
|
||||
|
||||
it "requires product_code" do
|
||||
params.delete :product_code
|
||||
link = Lti::Link.new(params)
|
||||
expect(link.valid?).to eq false
|
||||
end
|
||||
|
||||
it "requires resource_type_code" do
|
||||
params.delete :resource_type_code
|
||||
link = Lti::Link.new(params)
|
||||
expect(link.valid?).to eq false
|
||||
end
|
||||
|
||||
it "populates resource_link_id if not present" do
|
||||
params.delete :resource_link_id
|
||||
link = Lti::Link.new(params)
|
||||
expect(link.valid?).to eq true
|
||||
end
|
||||
|
||||
it "requires resource_link_id to be unique" do
|
||||
Lti::Link.create!(params)
|
||||
link = Lti::Link.new(params)
|
||||
expect(link.valid?).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#message_handler' do
|
||||
let(:lti_link) { subject }
|
||||
|
||||
before do
|
||||
message_handler.update_attributes(message_type: MessageHandler::BASIC_LTI_LAUNCH_REQUEST)
|
||||
resource_handler.message_handlers = [message_handler]
|
||||
resource_handler.save!
|
||||
|
||||
lti_link.update_attributes(resource_type_code: resource_handler.resource_type_code,
|
||||
product_code: product_family.product_code,
|
||||
vendor_code: product_family.vendor_code)
|
||||
end
|
||||
|
||||
it 'looks up the message handler identified by the codes' do
|
||||
expect(lti_link.message_handler(account)).to eq message_handler
|
||||
end
|
||||
end
|
||||
|
||||
describe '#originality_report' do
|
||||
it 'returns an originality_report if linkable is an OriginalityReport' do
|
||||
report = OriginalityReport.new
|
||||
lti_link = Lti::Link.new(linkable: report)
|
||||
expect(lti_link.originality_report).to eq report
|
||||
end
|
||||
|
||||
it 'returns nil if linkable is not an OriginalityReport ' do
|
||||
lti_link = Lti::Link.new
|
||||
expect(lti_link.originality_report).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_resource_link_id' do
|
||||
it 'sets the resource_link_id' do
|
||||
lti_link = Lti::Link.new
|
||||
expect(lti_link.resource_link_id).to be_nil
|
||||
lti_link.generate_resource_link_id
|
||||
expect(lti_link.resource_link_id).not_to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -138,32 +138,5 @@ module Lti
|
|||
expect(resource_handlers).to be_blank
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_or_create_tool_setting' do
|
||||
before do
|
||||
message_handler.update_attributes(message_type: MessageHandler::BASIC_LTI_LAUNCH_REQUEST)
|
||||
resource_handler.message_handlers = [message_handler]
|
||||
resource_handler.save!
|
||||
user_session(account_admin_user)
|
||||
end
|
||||
|
||||
it 'creates a new tool setting if one with the existing resource_link_id does not exist' do
|
||||
expected_id = message_handler.build_resource_link_id(context: tool_proxy.context)
|
||||
expect(resource_handler.find_or_create_tool_setting.resource_link_id).to eq expected_id
|
||||
end
|
||||
|
||||
it 'reuses a tool setting if one with the same resource_link_id exists' do
|
||||
tool_setting = resource_handler.find_or_create_tool_setting
|
||||
expect(resource_handler.find_or_create_tool_setting).to eq tool_setting
|
||||
end
|
||||
|
||||
it 'allows changing the settings of an originality report without affecting others' do
|
||||
link_fragment = SecureRandom.uuid
|
||||
resource_handler.find_or_create_tool_setting
|
||||
setting_two = resource_handler.find_or_create_tool_setting(link_fragment: link_fragment)
|
||||
setting_two.update_attributes(resource_url: 'http://www.test.com')
|
||||
expect(resource_handler.find_or_create_tool_setting.resource_url).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -100,6 +100,52 @@ describe OriginalityReport do
|
|||
expect(subject.state).to eq 'acceptable'
|
||||
end
|
||||
|
||||
describe 'accepts nested attributes for lti link' do
|
||||
let(:lti_link_attributes) do
|
||||
{
|
||||
product_code: 'product',
|
||||
vendor_code: 'vendor',
|
||||
resource_type_code: 'resource'
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates an lti link' do
|
||||
report = OriginalityReport.create!(
|
||||
attachment: attachment,
|
||||
originality_score: '1',
|
||||
submission: submission,
|
||||
workflow_state: 'pending',
|
||||
lti_link_attributes: lti_link_attributes
|
||||
)
|
||||
expect(report.lti_link.product_code).to eq 'product'
|
||||
end
|
||||
|
||||
it 'updates an lti link' do
|
||||
report = OriginalityReport.create!(
|
||||
attachment: attachment,
|
||||
originality_score: '1',
|
||||
submission: submission,
|
||||
workflow_state: 'pending',
|
||||
lti_link_attributes: lti_link_attributes
|
||||
)
|
||||
report.update_attributes(lti_link_attributes: { id: report.lti_link.id, resource_url: 'http://example.com' })
|
||||
expect(report.lti_link.resource_url).to eq 'http://example.com'
|
||||
end
|
||||
|
||||
it 'destroys an lti link' do
|
||||
report = OriginalityReport.create!(
|
||||
attachment: attachment,
|
||||
originality_score: '1',
|
||||
submission: submission,
|
||||
workflow_state: 'pending',
|
||||
lti_link_attributes: lti_link_attributes
|
||||
)
|
||||
link_id = report.lti_link.id
|
||||
report.update_attributes!(lti_link_attributes: { id: link_id, _destroy: true })
|
||||
expect(Lti::Link.find_by(id: link_id)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'workflow_state transitions' do
|
||||
let(:report_no_score){ OriginalityReport.new(attachment: attachment, submission: submission) }
|
||||
let(:report_with_score){ OriginalityReport.new(attachment: attachment, submission: submission, originality_score: 23.2) }
|
||||
|
@ -135,23 +181,20 @@ describe OriginalityReport do
|
|||
|
||||
describe '#report_launch_url' do
|
||||
include_context 'lti2_spec_helper'
|
||||
let(:link_id) { SecureRandom.uuid }
|
||||
|
||||
let(:tool_setting) do
|
||||
ToolSetting.new(tool_proxy: tool_proxy,
|
||||
context: course,
|
||||
resource_link_id: link_id,
|
||||
vendor_code: product_family.vendor_code,
|
||||
product_code: product_family.product_code,
|
||||
resource_type_code: resource_handler.resource_type_code)
|
||||
let(:lti_link) do
|
||||
Lti::Link.new(resource_link_id: SecureRandom.uuid,
|
||||
vendor_code: product_family.vendor_code,
|
||||
product_code: product_family.product_code,
|
||||
resource_type_code: resource_handler.resource_type_code,
|
||||
linkable: report)
|
||||
end
|
||||
let(:report) { subject }
|
||||
|
||||
it 'creates an LTI launch URL if a link_id is present' do
|
||||
report.update_attributes(link_id: link_id)
|
||||
it 'creates an LTI launch URL if a lti_link is present' do
|
||||
report.update_attributes(lti_link: lti_link)
|
||||
expected_url = "http://localhost/courses/"\
|
||||
"#{submission.assignment.course.id}/assignments/"\
|
||||
"#{submission.assignment.id}/lti/resource/#{link_id}?display=borderless"
|
||||
"#{submission.assignment.id}/lti/resource/#{lti_link.resource_link_id}?display=borderless"
|
||||
expect(report.report_launch_url).to eq expected_url
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue