diff --git a/db/migrate/20200706193951_populate_root_account_ids_on_conversations_tables.rb b/db/migrate/20200706193951_populate_root_account_ids_on_conversations_tables.rb new file mode 100644 index 00000000000..418e8d3301c --- /dev/null +++ b/db/migrate/20200706193951_populate_root_account_ids_on_conversations_tables.rb @@ -0,0 +1,32 @@ +# +# Copyright (C) 2020 - 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 PopulateRootAccountIdsOnConversationsTables < ActiveRecord::Migration[5.2] + tag :postdeploy + + def up + Conversation.find_ids_in_ranges(batch_size: 100_000) do |min, max| + DataFixup::PopulateRootAccountIdsOnConversationsTables.send_later_if_production_enqueue_args( + :run, + {:priority => Delayed::LOWER_PRIORITY, :n_strand => ["root_account_id_backfill_strand", Shard.current.database_server.id]}, + min, max + ) + end + end + + def down; end +end diff --git a/lib/data_fixup/populate_root_account_ids_on_conversations_tables.rb b/lib/data_fixup/populate_root_account_ids_on_conversations_tables.rb new file mode 100644 index 00000000000..d49d9f9ab97 --- /dev/null +++ b/lib/data_fixup/populate_root_account_ids_on_conversations_tables.rb @@ -0,0 +1,34 @@ +# +# Copyright (C) 2020 - 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 . + +module DataFixup::PopulateRootAccountIdsOnConversationsTables + def self.run(min, max) + Conversation.find_ids_in_ranges(start_at: min, end_at: max) do |batch_min, batch_max| + ConversationParticipant.joins(:conversation). + where(conversation: batch_min..batch_max). + update_all("root_account_ids=conversations.root_account_ids") + + messages = ConversationMessage.joins(:conversation).where(conversation: batch_min..batch_max) + messages.update_all("root_account_ids=conversations.root_account_ids") + + # only has FK to ConversationMessage and ConversationParticipant, not Conversation + ConversationMessageParticipant.joins(:conversation_message). + where(conversation_message: messages). + update_all("root_account_ids=conversation_messages.root_account_ids") + end + end +end \ No newline at end of file diff --git a/spec/lib/data_fixup/populate_root_account_ids_on_conversations_tables_spec.rb b/spec/lib/data_fixup/populate_root_account_ids_on_conversations_tables_spec.rb new file mode 100644 index 00000000000..ee7b6a360a3 --- /dev/null +++ b/spec/lib/data_fixup/populate_root_account_ids_on_conversations_tables_spec.rb @@ -0,0 +1,130 @@ +# +# Copyright (C) 2020 - 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 . + +require 'spec_helper' + +describe DataFixup::PopulateRootAccountIdsOnConversationsTables do + + def ids_to_string(*ids) + ids.sort.join(',') + end + + def reset_root_account_ids(*models) + models.each { |m| m.update_column(:root_account_ids, nil) } + end + + def check_root_account_ids(expected, *models) + models.each { |m| expect(m.reload.root_account_ids).to eq expected} + end + + before :once do + @root_account1 = account_model + @root_account2 = account_model + @user1 = user_model + @user2 = user_model + end + + def check_conversation_messages(conversation, ids) + cm1 = ConversationMessage.create!(conversation: conversation) + cm2 = ConversationMessage.create!(conversation: conversation) + reset_root_account_ids(cm1, cm2) + DataFixup::PopulateRootAccountIdsOnConversationsTables.run(conversation.id, conversation.id) + check_root_account_ids(ids, cm1, cm2) + end + + def check_conversation_participants(conversation, ids) + cp1 = ConversationParticipant.create!(conversation: conversation, user: @user1) + cp2 = ConversationParticipant.create!(conversation: conversation, user: @user2) + reset_root_account_ids(cp1, cp2) + DataFixup::PopulateRootAccountIdsOnConversationsTables.run(conversation.id, conversation.id) + check_root_account_ids(ids, cp1, cp2) + end + + def check_conversation_message_participants(conversation, ids) + cm1 = ConversationMessage.create!(conversation: conversation) + cm2 = ConversationMessage.create!(conversation: conversation) + cmp1 = ConversationMessageParticipant.create!(conversation_message: cm1, user: @user1) + cmp2 = ConversationMessageParticipant.create!(conversation_message: cm2, user: @user2) + reset_root_account_ids(cm1, cm2, cmp1, cmp2) + DataFixup::PopulateRootAccountIdsOnConversationsTables.run(conversation.id, conversation.id) + check_root_account_ids(ids, cmp1, cmp2) + end + + context 'single-account Conversation' do + before :once do + @ids = [@root_account1.id] + @c = Conversation.create!(root_account_ids: ids_to_string(@root_account1.id)) + end + + it 'sets root account id on all associated ConversationMessages' do + check_conversation_messages(@c, @ids) + end + + it 'sets root account id on all associated ConversationParticipants' do + check_conversation_participants(@c, @ids) + end + + it 'sets root account id on all ConversationMessageParticipants through ConversationMessage' do + check_conversation_message_participants(@c, @ids) + end + end + + context 'multiple-account Conversation' do + before :once do + @ids = [@root_account1.id, @root_account2.id] + @c = Conversation.create!(root_account_ids: ids_to_string(@root_account1.id, @root_account2.id)) + end + + it 'sets root account ids on all associated ConversationMessages' do + check_conversation_messages(@c, @ids) + end + + it 'sets root account ids on all associated ConversationParticipants' do + check_conversation_participants(@c, @ids) + end + + it 'sets root account ids on all ConversationMessageParticipants through ConversationMessage' do + check_conversation_message_participants(@c, @ids) + end + end + + context 'cross-shard Conversation' do + specs_require_sharding + + before :once do + cross_shard_account = @shard1.activate do + account_model + end + + @ids = [@root_account1.id, cross_shard_account.id] + @c = Conversation.create!(root_account_ids: ids_to_string(@root_account1.id, cross_shard_account.id)) + end + + it 'sets root account ids on all associated ConversationMessages' do + check_conversation_messages(@c, @ids) + end + + it 'sets root account ids on all associated ConversationParticipants' do + check_conversation_participants(@c, @ids) + end + + it 'sets root account ids on all ConversationMessageParticipants through ConversationMessage' do + check_conversation_message_participants(@c, @ids) + end + end + +end \ No newline at end of file