Files
chatwoot/lib/tasks/move_inbox.rake

224 lines
8.5 KiB
Ruby

# Move Inbox Rake Task
#
# Interactively moves a single inbox (and all associated records) from one
# Chatwoot account to another using Inboxes::MoveInboxToAccountService.
#
# Usage:
# bundle exec rake inbox:move
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/ModuleLength
# rubocop:disable Style/FormatStringToken
module MoveInboxTasks
def self.prompt(message)
print "#{message}: "
$stdin.gets.chomp
end
def self.run
puts 'Move Inbox Between Accounts'
puts '=' * 40
source_account = collect_source_account
inbox = collect_inbox(source_account)
destination_account = collect_destination_account(source_account)
puts "\nRunning dry-run..."
result = dry_run(source_account, inbox, destination_account)
if result[:validation_errors].present?
puts "\nValidation errors:"
result[:validation_errors].each { |e| puts " - #{e}" }
abort "\nAborted due to validation errors."
end
print_preview(result)
confirmation = prompt("\nProceed with migration? (y/N)")
abort 'Migration cancelled.' unless confirmation.downcase == 'y'
puts "\nExecuting migration..."
logger = Logger.new($stdout)
logger.formatter = proc { |_severity, _time, _progname, msg| " #{msg}\n" }
result = execute(source_account, inbox, destination_account, logger)
if result[:status] == :failed
puts "\nMigration FAILED (all changes rolled back)"
puts " Error: #{result[:error][:class]}: #{result[:error][:message]}"
else
puts "\nMigration complete!"
end
print_result(result)
end
def self.collect_source_account
account_id = prompt("\nSource account ID")
abort 'Error: Account ID is required' if account_id.blank?
account = Account.find_by(id: account_id)
abort "Error: Account ##{account_id} not found" unless account
puts " Account: #{account.name} (##{account.id})"
account
end
def self.collect_inbox(account)
inboxes = account.inboxes.includes(:channel).order(:id)
abort "Error: Account ##{account.id} has no inboxes" if inboxes.empty?
puts "\nInboxes in account ##{account.id}:"
puts ' ID Name Channel Type Created'
puts " #{'-' * 80}"
inboxes.each do |inbox|
puts format(' %-6d %-30s %-25s %s', inbox.id, inbox.name.truncate(28), inbox.channel_type, inbox.created_at.to_date)
end
inbox_id = prompt("\nInbox ID to move")
abort 'Error: Inbox ID is required' if inbox_id.blank?
inbox = inboxes.find { |i| i.id == inbox_id.to_i }
abort "Error: Inbox ##{inbox_id} not found in account ##{account.id}" unless inbox
puts " Selected: #{inbox.name} (#{inbox.channel_type})"
inbox
end
def self.collect_destination_account(source_account)
account_id = prompt("\nDestination account ID")
abort 'Error: Account ID is required' if account_id.blank?
abort 'Error: Destination must be different from source account' if account_id.to_i == source_account.id
account = Account.find_by(id: account_id)
abort "Error: Account ##{account_id} not found" unless account
puts " Account: #{account.name} (##{account.id})"
account
end
def self.dry_run(source_account, inbox, destination_account)
require Rails.root.join('script/move_inbox')
Inboxes::MoveInboxToAccountService.call(
source_account_id: source_account.id,
source_inbox_id: inbox.id,
destination_account_id: destination_account.id,
dry_run: true
)
end
def self.execute(source_account, inbox, destination_account, logger)
Inboxes::MoveInboxToAccountService.call(
source_account_id: source_account.id,
source_inbox_id: inbox.id,
destination_account_id: destination_account.id,
dry_run: false,
logger: logger
)
end
def self.print_preview(result)
will = result[:will]
custom = result[:custom_attribute_definitions]
puts "\nDry-run summary"
puts '-' * 40
inbox_info = will[:inbox] || {}
puts "Inbox: #{inbox_info[:source_name]} (#{inbox_info[:channel_type]})"
puts "Destination: #{inbox_info[:destination_account_name]}"
puts "\nRecords to copy/migrate:"
puts format(' %-30s %s', 'Labels', will[:labels])
puts format(' %-30s %s', 'Account users', will[:account_users])
puts format(' %-30s %s', 'Conversations', will[:conversations])
puts format(' %-30s %s', 'Contacts', will[:contacts])
print_contacts_to_duplicate(will[:contacts_to_duplicate])
puts format(' %-30s %s', 'Contact inboxes', will[:contact_inboxes])
puts format(' %-30s %s', 'Messages', will[:messages])
puts format(' %-30s %s', 'Attachments', will[:attachments])
puts format(' %-30s %s', 'Notes', will[:notes])
puts format(' %-30s %s', 'CSAT responses', will[:csat_survey_responses])
puts format(' %-30s %s', 'Mentions', will[:mentions])
puts format(' %-30s %s', 'Reporting events', will[:reporting_events])
puts format(' %-30s %s', 'Conversation participants', will[:conversation_participants])
puts format(' %-30s %s', 'Inbox members', will[:inbox_members])
puts "\nRecords to clean up (delete):"
puts format(' %-30s %s', 'Notifications', will[:notifications])
puts format(' %-30s %s', 'Applied SLAs', will[:applied_slas])
puts format(' %-30s %s', 'SLA events', will[:sla_events])
puts format(' %-30s %s', 'Agent bot inboxes', will[:agent_bot_inboxes])
puts format(' %-30s %s', 'Inbox webhooks', will[:inbox_webhooks])
puts format(' %-30s %s', 'Inbox hooks', will[:inbox_hooks])
puts format(' %-30s %s', 'Campaigns', will[:campaigns])
return if custom[:source_total].zero?
puts "\nCustom attribute definitions:"
puts format(' %-30s %s', 'Source total', custom[:source_total])
puts format(' %-30s %s', 'To create', custom[:to_create])
puts format(' %-30s %s', 'Skipped (existing)', custom[:skipped_existing])
puts format(' %-30s %s', 'Skipped (reserved)', custom[:skipped_reserved])
end
def self.print_contacts_to_duplicate(contacts)
return if contacts.blank?
puts "\n Contacts with conversations in other inboxes (will be duplicated):"
puts ' ID Name Email Phone'
puts " #{'-' * 75}"
contacts.each do |c|
puts format(' %-8s %-25s %-30s %s', c[:id], c[:name].to_s.truncate(23), c[:email].to_s.truncate(28), c[:phone_number])
end
puts ''
end
def self.print_result(result)
moved = result[:moved]
puts "\nFinal results (status: #{result[:status]})"
puts '-' * 40
puts 'Records migrated:'
puts format(' %-30s %s', 'Labels copied', moved[:labels_copied])
puts format(' %-30s %s', 'Account users copied', moved[:account_users_copied])
puts format(' %-30s %s', 'Conversations', moved[:conversations])
puts format(' %-30s %s', 'Contacts', moved[:contacts])
puts format(' %-30s %s', 'Contacts duplicated', moved[:contacts_duplicated])
puts format(' %-30s %s', 'Contact inboxes', moved[:contact_inboxes])
puts format(' %-30s %s', 'Messages', moved[:messages])
puts format(' %-30s %s', 'Attachments', moved[:attachments])
puts format(' %-30s %s', 'Notes', moved[:notes])
puts format(' %-30s %s', 'CSAT responses', moved[:csat_survey_responses])
puts format(' %-30s %s', 'Mentions', moved[:mentions])
puts format(' %-30s %s', 'Reporting events', moved[:reporting_events])
puts format(' %-30s %s', 'Conversation participants', moved[:conversation_participants])
puts format(' %-30s %s', 'Inbox members', moved[:inbox_members])
puts "\nRecords cleaned up:"
puts format(' %-30s %s', 'Notifications deleted', moved[:notifications_deleted])
puts format(' %-30s %s', 'Applied SLAs deleted', moved[:applied_slas_deleted])
puts format(' %-30s %s', 'SLA events deleted', moved[:sla_events_deleted])
puts format(' %-30s %s', 'Agent bot inboxes deleted', moved[:agent_bot_inboxes_deleted])
puts format(' %-30s %s', 'Inbox webhooks deleted', moved[:inbox_webhooks_deleted])
puts format(' %-30s %s', 'Inbox hooks deleted', moved[:inbox_hooks_deleted])
puts format(' %-30s %s', 'Campaigns deleted', moved[:campaigns_deleted])
end
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/ModuleLength
# rubocop:enable Style/FormatStringToken
namespace :inbox do
desc 'Interactively move an inbox (and all associated records) between accounts'
task move: :environment do
require Rails.root.join('script/move_inbox')
MoveInboxTasks.run
end
end