Add submisson, attachment, and report id variable expansions

Closes PLAT-2688

Test Plan:
- Install an plagiarism detection tool that uses
  LTI launches for displaying originality reports.
  The message handler for these launches should have
  com.instructure.OriginalityReport.id,
  com.instructure.Submission.id,
  and com.instructure.File.id in it's enabled
  capability array.
- Create an originality report with the tool and
  launch the originality report.
- Verify that parameters for each of these three
  capabilities are send and set correctly.

Change-Id: I2cb246e3a48f5e63a60ff6a0d90a003aaf9c8d62
Reviewed-on: https://gerrit.instructure.com/116377
Reviewed-by: Andrew Butterfield <abutterfield@instructure.com>
Reviewed-by: Nathan Mills <nathanm@instructure.com>
Tested-by: Jenkins
QA-Review: August Thornton <august@instructure.com>
Product-Review: Weston Dransfield <wdransfield@instructure.com>
This commit is contained in:
wdransfield 2017-06-20 13:00:17 -06:00 committed by Weston Dransfield
parent bbe4a41738
commit 55fce28b93
10 changed files with 193 additions and 54 deletions

View File

@ -151,8 +151,9 @@ module Lti
tag = find_tag
custom_param_opts = prep_tool_settings(message_handler.parameters, tool_proxy, launch_params[:resource_link_id])
custom_param_opts[:content_tag] = tag if tag
variable_expander = create_variable_expander(custom_param_opts.merge(tool: tool_proxy))
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))
launch_params.merge! enabled_parameters(tool_proxy, message_handler, variable_expander)
message = IMS::LTI::Models::Messages::BasicLTILaunchRequest.new(launch_params)

View File

@ -152,22 +152,13 @@ module Lti
# @returns OriginalityReport
def create
render_unauthorized_action and return unless tool_proxy_associated?
report_attributes = params.require(:originality_report).permit(create_attributes).to_hash.merge(
{submission_id: params.require(:submission_id)}
)
@report = OriginalityReport.new(build_link_id!(report_attributes))
begin
successful_save = @report.save
rescue ActiveRecord::RecordNotUnique
@report.errors.add(:base, I18n.t('the specified file with file_id already has an originality report'))
end
if successful_save
render json: api_json(@report, @current_user, session), status: :created
else
render json: @report.errors, status: :bad_request
end
@report = OriginalityReport.create!(create_report_params)
render json: api_json(@report, @current_user, session), status: :created
rescue ActiveRecord::RecordInvalid
return render json: @report.errors, status: :bad_request
rescue ActiveRecord::RecordNotUnique
return render json: {error: {message: I18n.t('the specified file with file_id already has an originality report'),
type: 'RecordNotUnique'}}, status: :bad_request
end
# @API Edit an Originality Report
@ -205,7 +196,7 @@ module Lti
# @returns OriginalityReport
def update
render_unauthorized_action and return unless tool_proxy_associated?
if @report.update_attributes(build_link_id!(params.require(:originality_report).permit(update_attributes)))
if @report.update_attributes(update_report_params)
render json: api_json(@report, @current_user, session)
else
render json: @report.errors, status: :bad_request
@ -227,20 +218,18 @@ module Lti
private
def build_link_id!(report_hash)
resource_type_code = report_hash.dig('tool_setting', 'resource_type_code')
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)
link_id = resource_link_id(rh, report_hash.dig('tool_setting', 'resource_url'))
report_hash['link_id'] = link_id
resource_link_id(rh, tool_setting_params['resource_url'])
end
report_hash.delete 'tool_setting'
report_hash
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)
link_fragment: link_fragment,
context: attachment_association)
tool_setting.resource_link_id
end
@ -282,8 +271,36 @@ module Lti
@_submission ||= Submission.active.find(params[:submission_id])
end
def attachment
@_attachment ||= Attachment.find(params.require(:originality_report)[:file_id])
end
def attachment_association
@_attachment_association ||= begin
file = @report&.attachment || attachment
file.attachment_associations.find { |a| a.context == submission }
end
end
def create_report_params
@_create_report_params ||= begin
report_attributes = params.require(:originality_report).permit(create_attributes).to_hash.merge(
{submission_id: params.require(:submission_id)}
)
report_attributes['link_id'] = link_id(report_attributes.delete('tool_setting'))
report_attributes
end
end
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
end
end
def attachment_in_context
attachment = Attachment.find(params.require(:originality_report)[:file_id])
verify_submission_attachment(attachment, submission)
end

View File

@ -54,11 +54,9 @@ module Lti
Canvas.placements.postGrades
Security.splitSecret
Context.sourcedId
).concat(
CapabilitiesHelper::SUPPORTED_CAPABILITIES
).concat(
Lti::VariableExpander.expansion_keys
).uniq.freeze
).freeze
RESTRICTED_CAPABILITIES = [
'Canvas.placements.similarityDetection',

View File

@ -19,7 +19,7 @@
module Lti
class ToolSetting < ActiveRecord::Base
belongs_to :tool_proxy
belongs_to :context, polymorphic: [:course, :account]
belongs_to :context, polymorphic: [:course, :account, :attachment_association]
validates_presence_of :context, if: :has_resource_link_id?

View File

@ -108,6 +108,36 @@ particular placement:
```
# Supported Substitutions
## com.instructure.OriginalityReport.id
The Canvas id of the Originality Report associated
with the launch.
**Availability**: **
**Launch Parameter**: *com_instructure_originality_report_id*
```
23
```
## com.instructure.Submission.id
The Canvas id of the submission associated with the
launch.
**Availability**: **
**Launch Parameter**: *com_instructure_submission_id*
```
23
```
## com.instructure.File.id
The Canvas id of the file associated with the submission
in the launch.
**Availability**: **
**Launch Parameter**: *com_instructure_file_id*
```
23
```
## CourseOffering.sourcedId
the LIS identifier for the course offering.

View File

@ -17,27 +17,12 @@
module Lti
class CapabilitiesHelper
SUPPORTED_CAPABILITIES = %w(ToolConsumerInstance.guid
CourseSection.sourcedId
Membership.role
Person.email.primary
Person.name.given
Person.name.family
Person.name.full
Person.sourcedId
User.id
User.image
Message.documentTarget
Message.locale
Context.id
vnd.Canvas.root_account.uuid).freeze
def self.supported_capabilities
SUPPORTED_CAPABILITIES
VariableExpander.default_name_expansions
end
def self.filter_capabilities(enabled_capability)
enabled_capability & SUPPORTED_CAPABILITIES
enabled_capability & supported_capabilities
end
def self.capability_params_hash(enabled_capability, variable_expander)

View File

@ -44,6 +44,10 @@ module Lti
self.expansions.keys.map { |c| c.to_s[1..-1] }
end
def self.default_name_expansions
self.expansions.values.select { |v| v.default_name.present? }.map(&:name)
end
CONTROLLER_GUARD = -> { !!@controller }
COURSE_GUARD = -> { @context.is_a? Course }
TERM_START_DATE_GUARD = -> { @context.is_a?(Course) && @context.enrollment_term &&
@ -61,6 +65,7 @@ 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) }
def initialize(root_account, context, controller, opts = {})
@root_account = root_account
@ -107,6 +112,46 @@ module Lti
end
end
# The Canvas id of the Originality Report associated
# with the launch.
# @launch_parameter com_instructure_originality_report_id
# @example
# ```
# 23
# ```
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
end,
ATTACHMENT_ASSOCIATION_GUARD,
default_name: 'com_instructure_originality_report_id'
# The Canvas id of the submission associated with the
# launch.
# @launch_parameter com_instructure_submission_id
# @example
# ```
# 23
# ```
register_expansion 'com.instructure.Submission.id', [],
-> { @tool_setting.context.context_id },
ATTACHMENT_ASSOCIATION_GUARD,
default_name: 'com_instructure_submission_id'
# The Canvas id of the file associated with the submission
# in the launch.
# @launch_parameter com_instructure_file_id
# @example
# ```
# 23
# ```
register_expansion 'com.instructure.File.id', [],
-> { @tool_setting.context.attachment_id },
ATTACHMENT_ASSOCIATION_GUARD,
default_name: 'com_instructure_file_id'
# the LIS identifier for the course offering
# @launch_parameter lis_course_offering_sourcedid
# @example

View File

@ -217,7 +217,6 @@ module Lti
report_file.save!
put @endpoints[:update], {originality_report: {originality_report_file_id: report_file.id}}, request_headers
expect(response).to be_success
expect(OriginalityReport.find(@report.id).originality_report_file_id).to eq report_file.id
end
@ -241,7 +240,6 @@ module Lti
}
},
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"
@ -291,6 +289,26 @@ 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],
{
originality_report: {
file_id: @attachment.id,
originality_score: score,
tool_setting: {
resource_type_code: resource_handler.resource_type_code
}
}
},
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],
{
@ -394,7 +412,7 @@ module Lti
post @endpoints[:create], {originality_report: {file_id: @attachment.id, originality_score: 0.4}}, request_headers
expect(response.status).to eq 400
expect(JSON.parse(response.body)['errors'].key?('base')).to be_truthy
expect(JSON.parse(response.body)['error']['type']).to eq 'RecordNotUnique'
end
it "requires the plagiarism feature flag" do
@ -440,6 +458,24 @@ 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],
{
originality_report: {
file_id: @attachment.id,
tool_setting: {
resource_type_code: resource_handler.resource_type_code
}
}
},
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],
{

View File

@ -86,7 +86,15 @@ module Lti
Message.documentTarget
Message.locale
Context.id
vnd.Canvas.root_account.uuid)
CourseOffering.sourcedId
com.instructure.File.id
com.instructure.OriginalityReport.id
com.instructure.Submission.id
com.instructure.contextLabel
vnd.Canvas.root_account.uuid
vnd.Canvas.OriginalityReport.url
vnd.Canvas.submission.history.url
vnd.Canvas.submission.url)
}
describe '#supported_capabilities' do
it 'returns all supported capabilities asociated with launch params' do

View File

@ -68,8 +68,17 @@ module Lti
m.stubs(:view_context).returns(view_context_mock)
m
end
let(:variable_expander) { VariableExpander.new(root_account, account, controller, current_user: user, tool: tool) }
let(:attachment) { attachment_model }
let(:submission) { submission_model }
let(:resource_link_id) { SecureRandom.uuid }
let(:originality_report) do
OriginalityReport.create!(attachment: attachment,
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) }
it 'clears the lti_helper instance variable when you set the current_user' do
expect(variable_expander.lti_helper).not_to be nil
@ -131,6 +140,16 @@ module Lti
end
end
describe '#self.default_name_expansions' do
let(:expected_keys) do
VariableExpander.expansions.values.select { |v| v.default_name.present? }.map(&:name)
end
it 'includes all expansion keys that have default names' do
expect(VariableExpander.default_name_expansions).to eq expected_keys
end
end
describe '#enabled_capability_params' do
let(:enabled_capability) {
%w(TestCapability.Foo