diff --git a/app/models/assignment.rb b/app/models/assignment.rb index d251c080eed..565860efbdf 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -72,6 +72,9 @@ class Assignment < ActiveRecord::Base belongs_to :grading_standard belongs_to :group_category + belongs_to :duplicate_of, class_name: 'Assignment', optional: true, inverse_of: :duplicates + has_many :duplicates, class_name: 'Assignment', inverse_of: :duplicate_of, foreign_key: 'duplicate_of_id' + has_many :assignment_configuration_tool_lookups, dependent: :delete_all has_many :tool_settings_context_external_tools, through: :assignment_configuration_tool_lookups, source: :tool, source_type: 'ContextExternalTool' has_many :line_items, inverse_of: :assignment, class_name: 'Lti::LineItem', dependent: :destroy @@ -199,6 +202,13 @@ class Assignment < ActiveRecord::Base # Learning outcome alignments seem to get copied magically, possibly # through the rubric result.rubric_association = self.rubric_association.clone + + # Link the duplicated assignment to this assignment + result.duplicate_of = self + + # If this assignment uses an external tool, duplicate that too + result.external_tool_tag = self.external_tool_tag&.dup + result end @@ -2580,6 +2590,11 @@ class Assignment < ActiveRecord::Base @skip_due_date_validation = true end + def lti_resource_link_id + return nil if external_tool_tag.blank? + ContextExternalTool.opaque_identifier_for(external_tool_tag, shard) + end + private def due_date_ok? diff --git a/app/models/exporters/quizzes2_exporter.rb b/app/models/exporters/quizzes2_exporter.rb index b57877b29df..f2622baa814 100644 --- a/app/models/exporters/quizzes2_exporter.rb +++ b/app/models/exporters/quizzes2_exporter.rb @@ -35,12 +35,9 @@ module Exporters end def build_assignment_payload - external_tool_tag = @assignment.external_tool_tag { assignment: { - resource_link_id: ContextExternalTool.opaque_identifier_for( - external_tool_tag, @assignment.shard - ), + resource_link_id: @assignment.lti_resource_link_id, title: @quiz.title, context_title: @quiz.context.name, course_uuid: @course.uuid diff --git a/db/migrate/20180213235146_add_duplicate_of_to_assignments.rb b/db/migrate/20180213235146_add_duplicate_of_to_assignments.rb new file mode 100644 index 00000000000..43a71cc4fd1 --- /dev/null +++ b/db/migrate/20180213235146_add_duplicate_of_to_assignments.rb @@ -0,0 +1,27 @@ +# +# Copyright (C) 2018 - 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 . +# + +class AddDuplicateOfToAssignments < ActiveRecord::Migration[5.0] + tag :predeploy + disable_ddl_transaction! + + def change + add_reference :assignments, :duplicate_of, type: :bigint, foreign_key: { to_table: :assignments }, index: false + add_index :assignments, :duplicate_of_id, where: 'duplicate_of_id IS NOT NULL', algorithm: :concurrently + end +end diff --git a/lib/api/v1/assignment.rb b/lib/api/v1/assignment.rb index e3cbf1e4381..42f44fdde5d 100644 --- a/lib/api/v1/assignment.rb +++ b/lib/api/v1/assignment.rb @@ -192,7 +192,7 @@ module Api::V1::Assignment hash['external_tool_tag_attributes'] = { 'url' => external_tool_tag.url, 'new_tab' => external_tool_tag.new_tab, - 'resource_link_id' => ContextExternalTool.opaque_identifier_for(external_tool_tag, assignment.shard) + 'resource_link_id' => assignment.lti_resource_link_id } hash['url'] = sessionless_launch_url(@context, :launch_type => 'assessment', diff --git a/lib/canvas/live_events.rb b/lib/canvas/live_events.rb index 90375e5b0aa..d1a055186e1 100644 --- a/lib/canvas/live_events.rb +++ b/lib/canvas/live_events.rb @@ -172,7 +172,9 @@ module Canvas::LiveEvents lock_at: assignment.lock_at, updated_at: assignment.updated_at, points_possible: assignment.points_possible, - lti_assignment_id: assignment.lti_context_id + lti_assignment_id: assignment.lti_context_id, + lti_resource_link_id: assignment.lti_resource_link_id, + lti_resource_link_id_duplicated_from: assignment.duplicate_of&.lti_resource_link_id } end diff --git a/spec/lib/canvas/live_events_spec.rb b/spec/lib/canvas/live_events_spec.rb index 168d547a529..63d2cc8b324 100644 --- a/spec/lib/canvas/live_events_spec.rb +++ b/spec/lib/canvas/live_events_spec.rb @@ -502,7 +502,9 @@ describe Canvas::LiveEvents do unlock_at: assignment.unlock_at, lock_at: assignment.lock_at, points_possible: assignment.points_possible, - lti_assignment_id: assignment.lti_context_id + lti_assignment_id: assignment.lti_context_id, + lti_resource_link_id: assignment.lti_resource_link_id, + lti_resource_link_id_duplicated_from: assignment.duplicate_of&.lti_resource_link_id })).once Canvas::LiveEvents.assignment_created(assignment) @@ -526,7 +528,9 @@ describe Canvas::LiveEvents do unlock_at: assignment.unlock_at, lock_at: assignment.lock_at, points_possible: assignment.points_possible, - lti_assignment_id: assignment.lti_context_id + lti_assignment_id: assignment.lti_context_id, + lti_resource_link_id: assignment.lti_resource_link_id, + lti_resource_link_id_duplicated_from: assignment.duplicate_of&.lti_resource_link_id })).once Canvas::LiveEvents.assignment_updated(assignment) diff --git a/spec/models/assignment_spec.rb b/spec/models/assignment_spec.rb index 0e398bee7e7..abb5b3c78c3 100644 --- a/spec/models/assignment_spec.rb +++ b/spec/models/assignment_spec.rb @@ -405,16 +405,29 @@ describe Assignment do expect(new_assignment.rubric_association).not_to be_nil expect(new_assignment.title).to eq "Wiki Assignment Copy" expect(new_assignment.wiki_page.title).to eq "Wiki Assignment Copy" + expect(new_assignment.duplicate_of).to eq assignment new_assignment.save! new_assignment2 = assignment.duplicate expect(new_assignment2.title).to eq "Wiki Assignment Copy 2" new_assignment2.save! + expect(assignment.duplicates).to match_array [new_assignment, new_assignment2] # Go back to the first new assignment to test something just ending in # "Copy" new_assignment3 = new_assignment.duplicate expect(new_assignment3.title).to eq "Wiki Assignment Copy 3" end + it "duplicates an assignment's external_tool_tag" do + assignment = @course.assignments.create!( + submission_types: 'external_tool', + external_tool_tag_attributes: { url: 'http://example.com/launch' }, + **assignment_valid_attributes + ) + new_assignment = assignment.duplicate + expect(new_assignment.external_tool_tag).to be_present + expect(new_assignment.external_tool_tag.content).to eq(assignment.external_tool_tag.content) + end + describe "#representatives" do context "individual students" do it "sorts by sortable_name" do @@ -4881,6 +4894,35 @@ describe Assignment do end end + describe '#lti_resource_link_id' do + subject { assignment.lti_resource_link_id } + + context 'without external tool tag' do + let(:assignment) do + @course.assignments.create!(assignment_valid_attributes) + end + + it { is_expected.to be_nil } + end + + context 'with external tool tag' do + let(:assignment) do + @course.assignments.create!(submission_types: 'external_tool', + external_tool_tag_attributes: { url: 'http://example.com/launch' }, + **assignment_valid_attributes) + end + + it 'calls ContextExternalTool.opaque_identifier_for with the external tool tag and assignment shard' do + lti_resource_link_id = SecureRandom.hex + expect(ContextExternalTool).to receive(:opaque_identifier_for).with( + assignment.external_tool_tag, + assignment.shard + ).and_return(lti_resource_link_id) + expect(assignment.lti_resource_link_id).to eq(lti_resource_link_id) + end + end + end + def setup_assignment_with_group assignment_model(:group_category => "Study Groups", :course => @course) @group = @a.context.groups.create!(:name => "Study Group 1", :group_category => @a.group_category)