2017-04-28 10:30:08 +08:00
|
|
|
#
|
|
|
|
# Copyright (C) 2012 - 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/>.
|
|
|
|
|
2012-09-08 07:57:12 +08:00
|
|
|
class ConversationBatch < ActiveRecord::Base
|
|
|
|
include SimpleTags
|
|
|
|
include Workflow
|
|
|
|
|
|
|
|
belongs_to :user
|
|
|
|
belongs_to :root_conversation_message, :class_name => 'ConversationMessage'
|
2016-02-17 05:51:39 +08:00
|
|
|
belongs_to :context, polymorphic: [:account, :course, { context_group: 'Group' }]
|
2012-09-08 07:57:12 +08:00
|
|
|
|
|
|
|
before_save :serialize_conversation_message_ids
|
|
|
|
after_create :queue_delivery
|
|
|
|
|
2013-08-08 06:19:48 +08:00
|
|
|
validates_presence_of :user_id, :workflow_state, :root_conversation_message_id
|
2017-02-09 01:24:04 +08:00
|
|
|
validates_length_of :subject, :maximum => maximum_string_length, :allow_nil => true
|
2013-08-08 06:19:48 +08:00
|
|
|
|
2014-07-02 03:38:26 +08:00
|
|
|
scope :in_progress, -> { where(:workflow_state => ['created', 'sending']) }
|
2012-09-08 07:57:12 +08:00
|
|
|
|
|
|
|
attr_accessor :mode
|
|
|
|
|
|
|
|
attr_reader :conversations
|
|
|
|
def deliver(update_progress = true)
|
|
|
|
chunk_size = 25
|
|
|
|
|
|
|
|
@conversations = []
|
2013-01-22 06:08:12 +08:00
|
|
|
self.user = user_map[user_id]
|
|
|
|
existing_conversations = Conversation.find_all_private_conversations(self.user, recipient_ids.map { |id| user_map[id] })
|
2012-09-08 07:57:12 +08:00
|
|
|
update_attribute :workflow_state, 'sending'
|
|
|
|
|
|
|
|
ModelCache.with_cache(:conversations => existing_conversations, :users => {:id => user_map}) do
|
2014-09-05 04:05:49 +08:00
|
|
|
|
|
|
|
should_cc_author = true
|
|
|
|
|
2012-09-08 07:57:12 +08:00
|
|
|
recipient_ids.each_slice(chunk_size) do |ids|
|
|
|
|
ids.each do |id|
|
2013-09-19 03:36:52 +08:00
|
|
|
is_group = self.group?
|
|
|
|
conversation = user.initiate_conversation([user_map[id]], !is_group,
|
|
|
|
subject: subject, context_type: context_type, context_id: context_id)
|
|
|
|
@conversations << conversation
|
2014-05-17 04:59:08 +08:00
|
|
|
message = root_conversation_message.clone
|
|
|
|
message.generate_user_note = self.generate_user_note
|
2014-09-05 04:05:49 +08:00
|
|
|
conversation.add_message(message, update_for_sender: false, tags: tags, cc_author: should_cc_author)
|
2012-09-08 07:57:12 +08:00
|
|
|
conversation_message_ids << message.id
|
2014-09-05 04:05:49 +08:00
|
|
|
|
|
|
|
should_cc_author = false
|
2012-09-08 07:57:12 +08:00
|
|
|
end
|
|
|
|
# update it in chunks, not on every message
|
|
|
|
save! if update_progress
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
update_attribute :workflow_state, 'sent'
|
2013-09-19 03:36:52 +08:00
|
|
|
rescue StandardError => e
|
2012-09-08 07:57:12 +08:00
|
|
|
self.workflow_state = 'error'
|
|
|
|
save!
|
|
|
|
end
|
|
|
|
|
|
|
|
def completion
|
|
|
|
# what fraction of the total time we think we'll be waiting for the
|
|
|
|
# job to start. this is a bit hand-wavy, but basically we guesstimate
|
|
|
|
# the average wait time as being equivalent to sending 20 messages
|
|
|
|
job_start_factor = 20.0 / (recipient_ids.size + 20)
|
|
|
|
|
|
|
|
case workflow_state
|
|
|
|
when 'sent'
|
|
|
|
1
|
|
|
|
when 'created'
|
|
|
|
# the first part of the progress bar is while we wait for the job
|
|
|
|
# to start. ideally this will just take a couple seconds. if jobs
|
|
|
|
# are backed up, we still want to make it seem like we are making
|
|
|
|
# headway. every minute we will advance half of the remainder of
|
|
|
|
# job_start_factor.
|
|
|
|
minutes = (Time.zone.now - created_at).to_i / 60.0
|
|
|
|
job_start_factor * (1 - (1 / 2**minutes))
|
|
|
|
else
|
|
|
|
# the rest of the progress bar is nice and linear
|
|
|
|
job_start_factor + ((1 - job_start_factor) * conversation_message_ids.size / recipient_ids.size)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
attr_writer :user_map
|
|
|
|
def user_map
|
2014-09-12 03:44:34 +08:00
|
|
|
@user_map ||= User.where(id: recipient_ids + [user_id]).index_by(&:id)
|
2012-09-08 07:57:12 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def recipient_ids
|
|
|
|
@recipient_ids ||= read_attribute(:recipient_ids).split(',').map(&:to_i)
|
|
|
|
end
|
|
|
|
|
|
|
|
def recipient_ids=(ids)
|
|
|
|
write_attribute(:recipient_ids, ids.join(','))
|
|
|
|
end
|
|
|
|
|
|
|
|
def recipient_count
|
|
|
|
recipient_ids.size
|
|
|
|
end
|
|
|
|
|
|
|
|
def conversation_message_ids
|
|
|
|
@conversation_message_ids ||= (read_attribute(:conversation_message_ids) || '').split(',').map(&:to_i)
|
|
|
|
end
|
|
|
|
|
|
|
|
def serialize_conversation_message_ids
|
|
|
|
write_attribute :conversation_message_ids, conversation_message_ids.join(',')
|
|
|
|
end
|
|
|
|
|
|
|
|
def queue_delivery
|
|
|
|
if mode == :async
|
|
|
|
send_later :deliver
|
|
|
|
else
|
|
|
|
deliver(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
workflow do
|
|
|
|
state :created
|
|
|
|
state :sending
|
|
|
|
state :sent
|
|
|
|
state :error
|
|
|
|
end
|
|
|
|
|
2013-01-24 05:34:36 +08:00
|
|
|
def local_tags
|
|
|
|
tags
|
|
|
|
end
|
|
|
|
|
2012-11-29 06:53:00 +08:00
|
|
|
def self.generate(root_message, recipients, mode = :async, options = {})
|
2012-09-08 07:57:12 +08:00
|
|
|
batch = new
|
|
|
|
batch.mode = mode
|
2013-08-08 06:19:48 +08:00
|
|
|
# normally the association would do this for us, but the validation
|
|
|
|
# fails beforehand
|
|
|
|
root_message.save! if root_message.new_record?
|
2012-09-08 07:57:12 +08:00
|
|
|
batch.root_conversation_message = root_message
|
|
|
|
batch.user_id = root_message.author_id
|
2012-11-29 06:53:00 +08:00
|
|
|
batch.recipient_ids = recipients.map(&:id)
|
2013-07-31 07:37:00 +08:00
|
|
|
batch.context_type = options[:context_type]
|
|
|
|
batch.context_id = options[:context_id]
|
2012-09-08 07:57:12 +08:00
|
|
|
batch.tags = options[:tags]
|
2013-07-31 01:12:35 +08:00
|
|
|
batch.subject = options[:subject]
|
2013-09-19 03:36:52 +08:00
|
|
|
batch.group = !!options[:group]
|
2012-11-29 06:53:00 +08:00
|
|
|
user_map = recipients.index_by(&:id)
|
|
|
|
user_map[batch.user_id] = batch.user
|
|
|
|
batch.user_map = user_map
|
2014-05-17 04:59:08 +08:00
|
|
|
batch.generate_user_note = root_message.generate_user_note
|
2012-09-08 07:57:12 +08:00
|
|
|
batch.save!
|
|
|
|
batch
|
|
|
|
end
|
2013-01-24 05:34:36 +08:00
|
|
|
end
|