mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 11:08:04 +00:00 
			
		
		
		
	feat: Add job to remove stale contact inboxes (#8096)
This commit is contained in:
		| @@ -253,3 +253,9 @@ AZURE_APP_SECRET= | ||||
|  | ||||
| # Sentiment analysis model file path | ||||
| SENTIMENT_FILE_PATH= | ||||
|  | ||||
|  | ||||
| # Housekeeping/Performance related configurations | ||||
| # Set to true if you want to remove stale contact inboxes | ||||
| # contact_inboxes with no conversation older than 90 days will be removed | ||||
| # REMOVE_STALE_CONTACT_INBOX_JOB_STATUS=false | ||||
|   | ||||
							
								
								
									
										11
									
								
								app/jobs/internal/remove_stale_contact_inboxes_job.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/jobs/internal/remove_stale_contact_inboxes_job.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| # housekeeping | ||||
| # remove contact inboxes that does not have any conversations | ||||
| # and are older than 3 months | ||||
|  | ||||
| class Internal::RemoveStaleContactInboxesJob < ApplicationJob | ||||
|   queue_as :scheduled_jobs | ||||
|  | ||||
|   def perform | ||||
|     Internal::RemoveStaleContactInboxesService.new.perform | ||||
|   end | ||||
| end | ||||
| @@ -33,6 +33,13 @@ class ContactInbox < ApplicationRecord | ||||
|  | ||||
|   has_many :conversations, dependent: :destroy_async | ||||
|  | ||||
|   # contact_inboxes that are not associated with any conversation | ||||
|   scope :stale_without_conversations, lambda { |time_period| | ||||
|     left_joins(:conversations) | ||||
|       .where('contact_inboxes.created_at < ?', time_period) | ||||
|       .where(conversations: { contact_id: nil }) | ||||
|   } | ||||
|  | ||||
|   def webhook_data | ||||
|     { | ||||
|       id: id, | ||||
|   | ||||
| @@ -0,0 +1,43 @@ | ||||
| class Internal::RemoveStaleContactInboxesService | ||||
|   def perform | ||||
|     return unless remove_stale_contact_inbox_job_enabled? | ||||
|  | ||||
|     time_period = 90.days.ago | ||||
|     contact_inboxes_to_delete = stale_contact_inboxes(time_period) | ||||
|  | ||||
|     log_stale_contact_inboxes_deletion(contact_inboxes_to_delete, time_period) | ||||
|  | ||||
|     # Since the number of records to delete is very high, | ||||
|     # delete_all would be faster than destroy_all since it operates at database level | ||||
|     # and avoid loading all the records in memory | ||||
|     # Transaction and batching is used to avoid deadlock and memory issues | ||||
|     ContactInbox.transaction do | ||||
|       contact_inboxes_to_delete | ||||
|         .find_in_batches(batch_size: 10_000) do |group| | ||||
|           ContactInbox.where(id: group.map(&:id)).delete_all | ||||
|         end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def remove_stale_contact_inbox_job_enabled? | ||||
|     job_status = ENV.fetch('REMOVE_STALE_CONTACT_INBOX_JOB_STATUS', false) | ||||
|     return false unless ActiveModel::Type::Boolean.new.cast(job_status) | ||||
|  | ||||
|     true | ||||
|   end | ||||
|  | ||||
|   def stale_contact_inboxes(time_period) | ||||
|     ContactInbox.stale_without_conversations(time_period) | ||||
|   end | ||||
|  | ||||
|   def log_stale_contact_inboxes_deletion(contact_inboxes, time_period) | ||||
|     count = contact_inboxes.count | ||||
|     Rails.logger.info "Deleting #{count} stale contact inboxes older than #{time_period}" | ||||
|  | ||||
|     # Log the SQL query without executing it | ||||
|     sql_query = contact_inboxes.to_sql | ||||
|     Rails.logger.info("SQL Query: #{sql_query}") | ||||
|   end | ||||
| end | ||||
| @@ -19,3 +19,10 @@ trigger_imap_email_inboxes_job: | ||||
|   cron: '*/1 * * * *' | ||||
|   class: 'Inboxes::FetchImapEmailInboxesJob' | ||||
|   queue: scheduled_jobs | ||||
|  | ||||
| # executed daily at 2230 UTC | ||||
| # which is our lowest traffic time | ||||
| remove_stale_contact_inboxes_job.rb: | ||||
|   cron: '30 22 * * *' | ||||
|   class: 'Internal::RemoveStaleContactInboxesJob' | ||||
|   queue: scheduled_jobs | ||||
|   | ||||
| @@ -0,0 +1,32 @@ | ||||
| # spec/services/remove_stale_contact_inboxes_service_spec.rb | ||||
|  | ||||
| require 'rails_helper' | ||||
|  | ||||
| RSpec.describe Internal::RemoveStaleContactInboxesService do | ||||
|   describe '#perform' do | ||||
|     it 'does not delete stale contact inboxes if REMOVE_STALE_CONTACT_INBOX_JOB_STATUS is false' do | ||||
|       # default value of REMOVE_STALE_CONTACT_INBOX_JOB_STATUS is false | ||||
|       create(:contact_inbox, created_at: 3.days.ago) | ||||
|       create(:contact_inbox, created_at: 91.days.ago) | ||||
|       create(:contact_inbox, created_at: 92.days.ago) | ||||
|       create(:contact_inbox, created_at: 93.days.ago) | ||||
|       create(:contact_inbox, created_at: 94.days.ago) | ||||
|  | ||||
|       service = described_class.new | ||||
|       expect { service.perform }.not_to change(ContactInbox, :count) | ||||
|     end | ||||
|  | ||||
|     it 'deletes stale contact inboxes' do | ||||
|       with_modified_env REMOVE_STALE_CONTACT_INBOX_JOB_STATUS: 'true' do | ||||
|         create(:contact_inbox, created_at: 3.days.ago) | ||||
|         create(:contact_inbox, created_at: 91.days.ago) | ||||
|         create(:contact_inbox, created_at: 92.days.ago) | ||||
|         create(:contact_inbox, created_at: 93.days.ago) | ||||
|         create(:contact_inbox, created_at: 94.days.ago) | ||||
|  | ||||
|         service = described_class.new | ||||
|         expect { service.perform }.to change(ContactInbox, :count).by(-4) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
		Reference in New Issue
	
	Block a user
	 Vishnu Narayanan
					Vishnu Narayanan