diff --git a/app/jobs/inboxes/bulk_auto_assignment_job.rb b/app/jobs/inboxes/bulk_auto_assignment_job.rb deleted file mode 100644 index 9e808648b..000000000 --- a/app/jobs/inboxes/bulk_auto_assignment_job.rb +++ /dev/null @@ -1,47 +0,0 @@ -class Inboxes::BulkAutoAssignmentJob < ApplicationJob - queue_as :scheduled_jobs - include BillingHelper - - def perform - Account.feature_assignment_v2.find_each do |account| - if should_skip_auto_assignment?(account) - Rails.logger.info("Skipping auto assignment for account #{account.id}") - next - end - - account.inboxes.where(enable_auto_assignment: true).find_each do |inbox| - process_assignment(inbox) - end - end - end - - private - - def process_assignment(inbox) - allowed_agent_ids = inbox.member_ids_with_assignment_capacity - - if allowed_agent_ids.blank? - Rails.logger.info("No agents available to assign conversation to inbox #{inbox.id}") - return - end - - assign_conversations(inbox, allowed_agent_ids) - end - - def assign_conversations(inbox, allowed_agent_ids) - unassigned_conversations = inbox.conversations.unassigned.open.limit(Limits::AUTO_ASSIGNMENT_BULK_LIMIT) - unassigned_conversations.find_each do |conversation| - ::AutoAssignment::AgentAssignmentService.new( - conversation: conversation, - allowed_agent_ids: allowed_agent_ids - ).perform - Rails.logger.info("Assigned conversation #{conversation.id} to agent #{allowed_agent_ids.first}") - end - end - - def should_skip_auto_assignment?(account) - return false unless ChatwootApp.chatwoot_cloud? - - default_plan?(account) - end -end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 9511ae68e..7b78b466a 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -34,7 +34,18 @@ end # https://github.com/ondrejbartas/sidekiq-cron Rails.application.reloader.to_prepare do - # TODO: Switch to `load_from_hash!(..., source: 'schedule')` once we have a - # safe cleanup path for YAML-backed cron jobs already persisted in Redis. - Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file) if File.exist?(schedule_file) && Sidekiq.server? + # load_from_hash! upserts jobs from the YAML and removes any Redis-persisted + # jobs that share the same source tag but are no longer in the file. + # This ensures deleted schedule entries are cleaned up on deploy. + if File.exist?(schedule_file) && Sidekiq.server? + schedule = YAML.load_file(schedule_file) + + # Cron entries removed from schedule.yml but possibly still in Redis + # with source:'dynamic' (predating the source tag). load_from_hash! + # only cleans up source:'schedule' entries, so these need explicit removal. + # Remove names from this list once they've been through a deploy cycle. + %w[bulk_auto_assignment_job].each { |name| Sidekiq::Cron::Job.destroy(name) } + + Sidekiq::Cron::Job.load_from_hash!(schedule, source: 'schedule') + end end diff --git a/config/schedule.yml b/config/schedule.yml index 153724c25..f1054ad68 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -4,7 +4,6 @@ # executed daily at 0000 UTC # schedules daily deferred jobs at stable times for each installation -# keep the existing schedule key while the cron loader still uses load_from_hash internal_check_new_versions_job: cron: '0 0 * * *' class: 'Internal::TriggerDailyScheduledItemsJob' @@ -50,13 +49,6 @@ delete_accounts_job: class: 'Internal::DeleteAccountsJob' queue: scheduled_jobs -# executed every 15 minutes -# to assign unassigned conversations for all inboxes -bulk_auto_assignment_job: - cron: '*/15 * * * *' - class: 'Inboxes::BulkAutoAssignmentJob' - queue: scheduled_jobs - # executed every 30 minutes for assignment_v2 periodic_assignment_job: cron: '*/30 * * * *' diff --git a/spec/jobs/inboxes/bulk_auto_assignment_job_spec.rb b/spec/jobs/inboxes/bulk_auto_assignment_job_spec.rb deleted file mode 100644 index 5e7e3d7cc..000000000 --- a/spec/jobs/inboxes/bulk_auto_assignment_job_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'rails_helper' - -RSpec.describe Inboxes::BulkAutoAssignmentJob do - let(:account) { create(:account, custom_attributes: { 'plan_name' => 'Startups' }) } - let(:agent) { create(:user, account: account, role: :agent, auto_offline: false) } - let(:inbox) { create(:inbox, account: account) } - let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: nil, status: :open) } - let(:assignment_service) { double } - - describe '#perform' do - before do - allow(assignment_service).to receive(:perform) - end - - context 'when inbox has inbox members' do - before do - create(:inbox_member, user: agent, inbox: inbox) - account.enable_features!('assignment_v2') - inbox.update!(enable_auto_assignment: true) - end - - it 'assigns unassigned conversations in enabled inboxes' do - allow(AutoAssignment::AgentAssignmentService).to receive(:new).with( - conversation: conversation, - allowed_agent_ids: [agent.id] - ).and_return(assignment_service) - - described_class.perform_now - expect(AutoAssignment::AgentAssignmentService).to have_received(:new).with( - conversation: conversation, - allowed_agent_ids: [agent.id] - ) - end - - it 'skips inboxes with auto assignment disabled' do - inbox.update!(enable_auto_assignment: false) - allow(AutoAssignment::AgentAssignmentService).to receive(:new) - - described_class.perform_now - - expect(AutoAssignment::AgentAssignmentService).not_to have_received(:new).with( - conversation: conversation, - allowed_agent_ids: [agent.id] - ) - end - - context 'when account is on default plan in chatwoot cloud' do - before do - account.update!(custom_attributes: {}) - InstallationConfig.create(name: 'CHATWOOT_CLOUD_PLANS', value: [{ 'name' => 'default' }]) - allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(true) - end - - it 'skips auto assignment' do - allow(Rails.logger).to receive(:info) - expect(Rails.logger).to receive(:info).with("Skipping auto assignment for account #{account.id}") - - allow(AutoAssignment::AgentAssignmentService).to receive(:new) - expect(AutoAssignment::AgentAssignmentService).not_to receive(:new) - - described_class.perform_now - end - end - end - - context 'when inbox has no members' do - before do - account.enable_features!('assignment_v2') - inbox.update!(enable_auto_assignment: true) - end - - it 'does not assign conversations' do - allow(Rails.logger).to receive(:info) - expect(Rails.logger).to receive(:info).with("No agents available to assign conversation to inbox #{inbox.id}") - - described_class.perform_now - end - end - - context 'when assignment_v2 feature is disabled' do - before do - account.disable_features!('assignment_v2') - end - - it 'skips auto assignment' do - allow(AutoAssignment::AgentAssignmentService).to receive(:new) - expect(AutoAssignment::AgentAssignmentService).not_to receive(:new) - - described_class.perform_now - end - end - end -end