mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	feat: bulk actions to update conversation objects (#3934)
Added the endpoints for bulk updating conversation objects Fixes: #3845 #3940 #3943
This commit is contained in:
		
							
								
								
									
										26
									
								
								app/controllers/api/v1/accounts/bulk_actions_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/controllers/api/v1/accounts/bulk_actions_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| class Api::V1::Accounts::BulkActionsController < Api::V1::Accounts::BaseController | ||||
|   before_action :type_matches? | ||||
|  | ||||
|   def create | ||||
|     if type_matches? | ||||
|       ::BulkActionsJob.perform_later( | ||||
|         account: @current_account, | ||||
|         user: current_user, | ||||
|         params: permitted_params | ||||
|       ) | ||||
|       head :ok | ||||
|     else | ||||
|       render json: { success: false }, status: :unprocessable_entity | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def type_matches? | ||||
|     ['Conversation'].include?(params[:type]) | ||||
|   end | ||||
|  | ||||
|   def permitted_params | ||||
|     params.permit(:type, ids: [], fields: [:status, :assignee_id, :team_id], labels: [add: [], remove: []]) | ||||
|   end | ||||
| end | ||||
							
								
								
									
										59
									
								
								app/jobs/bulk_actions_job.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/jobs/bulk_actions_job.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| class BulkActionsJob < ApplicationJob | ||||
|   queue_as :medium | ||||
|   attr_accessor :records | ||||
|  | ||||
|   MODEL_TYPE = ['Conversation'].freeze | ||||
|  | ||||
|   def perform(account:, params:, user:) | ||||
|     @account = account | ||||
|     Current.user = user | ||||
|     @params = params | ||||
|     @records = records_to_updated(params[:ids]) | ||||
|     bulk_update | ||||
|   ensure | ||||
|     Current.reset | ||||
|   end | ||||
|  | ||||
|   def bulk_update | ||||
|     bulk_remove_labels | ||||
|     bulk_conversation_update | ||||
|   end | ||||
|  | ||||
|   def bulk_conversation_update | ||||
|     params = available_params(@params) | ||||
|     records.each do |conversation| | ||||
|       bulk_add_labels(conversation) | ||||
|       conversation.update(params) if params | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def bulk_remove_labels | ||||
|     records.each do |conversation| | ||||
|       remove_labels(conversation) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def available_params(params) | ||||
|     return unless params[:fields] | ||||
|  | ||||
|     params[:fields].delete_if { |_k, v| v.nil? } | ||||
|   end | ||||
|  | ||||
|   def bulk_add_labels(conversation) | ||||
|     conversation.add_labels(@params[:labels][:add]) if @params[:labels] && @params[:labels][:add] | ||||
|   end | ||||
|  | ||||
|   def remove_labels(conversation) | ||||
|     return unless @params[:labels] && @params[:labels][:remove] | ||||
|  | ||||
|     labels = conversation.label_list - @params[:labels][:remove] | ||||
|     conversation.update(label_list: labels) | ||||
|   end | ||||
|  | ||||
|   def records_to_updated(ids) | ||||
|     current_model = @params[:type].camelcase | ||||
|     return unless MODEL_TYPE.include?(current_model) | ||||
|  | ||||
|     current_model.constantize&.where(account_id: @account.id, display_id: ids) | ||||
|   end | ||||
| end | ||||
| @@ -40,6 +40,7 @@ Rails.application.routes.draw do | ||||
|             resource :contact_merge, only: [:create] | ||||
|           end | ||||
|  | ||||
|           resource :bulk_actions, only: [:create] | ||||
|           resources :agents, only: [:index, :create, :update, :destroy] | ||||
|           resources :agent_bots, only: [:index, :create, :show, :update, :destroy] | ||||
|  | ||||
|   | ||||
							
								
								
									
										145
									
								
								spec/controllers/api/v1/accounts/bulk_actions_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								spec/controllers/api/v1/accounts/bulk_actions_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| require 'rails_helper' | ||||
|  | ||||
| RSpec.describe 'Api::V1::Accounts::BulkActionsController', type: :request do | ||||
|   include ActiveJob::TestHelper | ||||
|   let(:account) { create(:account) } | ||||
|   let(:agent_1) { create(:user, account: account, role: :agent) } | ||||
|   let(:agent_2) { create(:user, account: account, role: :agent) } | ||||
|  | ||||
|   before do | ||||
|     create(:conversation, account_id: account.id, status: :open) | ||||
|     create(:conversation, account_id: account.id, status: :open) | ||||
|     create(:conversation, account_id: account.id, status: :open) | ||||
|     create(:conversation, account_id: account.id, status: :open) | ||||
|   end | ||||
|  | ||||
|   describe 'POST /api/v1/accounts/{account.id}/bulk_action' do | ||||
|     context 'when it is an unauthenticated user' do | ||||
|       let(:agent) { create(:user) } | ||||
|  | ||||
|       it 'returns unauthorized' do | ||||
|         post "/api/v1/accounts/#{account.id}/bulk_actions", | ||||
|              headers: agent.create_new_auth_token, | ||||
|              params: { type: 'Conversation', fields: { status: 'open' }, ids: [1, 2, 3] } | ||||
|  | ||||
|         expect(response).to have_http_status(:unauthorized) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when it is an authenticated user' do | ||||
|       let(:agent) { create(:user, account: account, role: :agent) } | ||||
|  | ||||
|       it 'Ignores bulk_actions for wrong type' do | ||||
|         post "/api/v1/accounts/#{account.id}/bulk_actions", | ||||
|              headers: agent.create_new_auth_token, | ||||
|              params: { type: 'Test', fields: { status: 'snoozed' }, ids: %w[1 2 3] } | ||||
|  | ||||
|         expect(response).to have_http_status(:unprocessable_entity) | ||||
|       end | ||||
|  | ||||
|       it 'Bulk update conversation status' do | ||||
|         expect(Conversation.first.status).to eq('open') | ||||
|         expect(Conversation.last.status).to eq('open') | ||||
|         expect(Conversation.first.assignee_id).to eq(nil) | ||||
|  | ||||
|         perform_enqueued_jobs do | ||||
|           post "/api/v1/accounts/#{account.id}/bulk_actions", | ||||
|                headers: agent.create_new_auth_token, | ||||
|                params: { type: 'Conversation', fields: { status: 'snoozed' }, ids: Conversation.first(3).pluck(:display_id) } | ||||
|  | ||||
|           expect(response).to have_http_status(:success) | ||||
|         end | ||||
|  | ||||
|         expect(Conversation.first.status).to eq('snoozed') | ||||
|         expect(Conversation.last.status).to eq('open') | ||||
|         expect(Conversation.first.assignee_id).to eq(nil) | ||||
|       end | ||||
|  | ||||
|       it 'Bulk update conversation assignee id' do | ||||
|         params = { type: 'Conversation', fields: { assignee_id: agent_1.id }, ids: Conversation.first(3).pluck(:display_id) } | ||||
|  | ||||
|         expect(Conversation.first.status).to eq('open') | ||||
|         expect(Conversation.first.assignee_id).to eq(nil) | ||||
|         expect(Conversation.second.assignee_id).to eq(nil) | ||||
|  | ||||
|         perform_enqueued_jobs do | ||||
|           post "/api/v1/accounts/#{account.id}/bulk_actions", | ||||
|                headers: agent.create_new_auth_token, | ||||
|                params: params | ||||
|  | ||||
|           expect(response).to have_http_status(:success) | ||||
|         end | ||||
|  | ||||
|         expect(Conversation.first.assignee_id).to eq(agent_1.id) | ||||
|         expect(Conversation.second.assignee_id).to eq(agent_1.id) | ||||
|         expect(Conversation.first.status).to eq('open') | ||||
|       end | ||||
|  | ||||
|       it 'Bulk update conversation status and assignee id' do | ||||
|         params = { type: 'Conversation', fields: { assignee_id: agent_1.id, status: 'snoozed' }, ids: Conversation.first(3).pluck(:display_id) } | ||||
|  | ||||
|         expect(Conversation.first.status).to eq('open') | ||||
|         expect(Conversation.second.assignee_id).to eq(nil) | ||||
|  | ||||
|         perform_enqueued_jobs do | ||||
|           post "/api/v1/accounts/#{account.id}/bulk_actions", | ||||
|                headers: agent.create_new_auth_token, | ||||
|                params: params | ||||
|  | ||||
|           expect(response).to have_http_status(:success) | ||||
|         end | ||||
|  | ||||
|         expect(Conversation.first.assignee_id).to eq(agent_1.id) | ||||
|         expect(Conversation.second.assignee_id).to eq(agent_1.id) | ||||
|         expect(Conversation.first.status).to eq('snoozed') | ||||
|         expect(Conversation.second.status).to eq('snoozed') | ||||
|       end | ||||
|  | ||||
|       it 'Bulk update conversation labels' do | ||||
|         params = { type: 'Conversation', ids: Conversation.first(3).pluck(:display_id), labels: { add: %w[support priority_customer] } } | ||||
|  | ||||
|         expect(Conversation.first.labels).to eq([]) | ||||
|         expect(Conversation.second.labels).to eq([]) | ||||
|  | ||||
|         perform_enqueued_jobs do | ||||
|           post "/api/v1/accounts/#{account.id}/bulk_actions", | ||||
|                headers: agent.create_new_auth_token, | ||||
|                params: params | ||||
|  | ||||
|           expect(response).to have_http_status(:success) | ||||
|         end | ||||
|  | ||||
|         expect(Conversation.first.label_list).to eq(%w[support priority_customer]) | ||||
|         expect(Conversation.second.label_list).to eq(%w[support priority_customer]) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe 'POST /api/v1/accounts/{account.id}/bulk_actions' do | ||||
|     context 'when it is an authenticated user' do | ||||
|       let(:agent) { create(:user, account: account, role: :agent) } | ||||
|  | ||||
|       it 'Bulk delete conversation labels' do | ||||
|         Conversation.first.add_labels(%w[support priority_customer]) | ||||
|         Conversation.second.add_labels(%w[support priority_customer]) | ||||
|         Conversation.third.add_labels(%w[support priority_customer]) | ||||
|  | ||||
|         params = { type: 'Conversation', ids: Conversation.first(3).pluck(:display_id), labels: { remove: %w[support] } } | ||||
|  | ||||
|         expect(Conversation.first.label_list).to eq(%w[support priority_customer]) | ||||
|         expect(Conversation.second.label_list).to eq(%w[support priority_customer]) | ||||
|  | ||||
|         perform_enqueued_jobs do | ||||
|           post "/api/v1/accounts/#{account.id}/bulk_actions", | ||||
|                headers: agent.create_new_auth_token, | ||||
|                params: params | ||||
|  | ||||
|           expect(response).to have_http_status(:success) | ||||
|         end | ||||
|  | ||||
|         expect(Conversation.first.label_list).to eq(['priority_customer']) | ||||
|         expect(Conversation.second.label_list).to eq(['priority_customer']) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
							
								
								
									
										63
									
								
								spec/jobs/bulk_actions_job_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								spec/jobs/bulk_actions_job_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| require 'rails_helper' | ||||
|  | ||||
| RSpec.describe BulkActionsJob, type: :job do | ||||
|   params = { | ||||
|     type: 'Conversation', | ||||
|     fields: { status: 'snoozed' }, | ||||
|     ids: Conversation.first(3).pluck(:display_id) | ||||
|   } | ||||
|  | ||||
|   subject(:job) { described_class.perform_later(account: account, params: params, user: agent) } | ||||
|  | ||||
|   let(:account) { create(:account) } | ||||
|   let!(:agent) { create(:user, account: account, role: :agent) } | ||||
|   let!(:conversation_1) { create(:conversation, account_id: account.id, status: :open) } | ||||
|   let!(:conversation_2) { create(:conversation, account_id: account.id, status: :open) } | ||||
|   let!(:conversation_3) { create(:conversation, account_id: account.id, status: :open) } | ||||
|  | ||||
|   it 'enqueues the job' do | ||||
|     expect { job }.to have_enqueued_job(described_class) | ||||
|       .with(account: account, params: params, user: agent) | ||||
|       .on_queue('medium') | ||||
|   end | ||||
|  | ||||
|   context 'when job is triggered' do | ||||
|     let(:bulk_action_job) { double } | ||||
|  | ||||
|     before do | ||||
|       allow(bulk_action_job).to receive(:perform) | ||||
|     end | ||||
|  | ||||
|     it 'bulk updates the status' do | ||||
|       params = { | ||||
|         type: 'Conversation', | ||||
|         fields: { status: 'snoozed', assignee_id: agent.id }, | ||||
|         ids: Conversation.first(3).pluck(:display_id) | ||||
|       } | ||||
|  | ||||
|       expect(Conversation.first.status).to eq('open') | ||||
|  | ||||
|       described_class.perform_now(account: account, params: params, user: agent) | ||||
|  | ||||
|       expect(conversation_1.reload.status).to eq('snoozed') | ||||
|       expect(conversation_2.reload.status).to eq('snoozed') | ||||
|       expect(conversation_3.reload.status).to eq('snoozed') | ||||
|     end | ||||
|  | ||||
|     it 'bulk updates the assignee_id' do | ||||
|       params = { | ||||
|         type: 'Conversation', | ||||
|         fields: { status: 'snoozed', assignee_id: agent.id }, | ||||
|         ids: Conversation.first(3).pluck(:display_id) | ||||
|       } | ||||
|  | ||||
|       expect(Conversation.first.assignee_id).to eq(nil) | ||||
|  | ||||
|       described_class.perform_now(account: account, params: params, user: agent) | ||||
|  | ||||
|       expect(Conversation.first.assignee_id).to eq(agent.id) | ||||
|       expect(Conversation.second.assignee_id).to eq(agent.id) | ||||
|       expect(Conversation.third.assignee_id).to eq(agent.id) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @@ -162,7 +162,7 @@ describe ::Contacts::FilterService do | ||||
|         expected_count = Contact.where("created_at < ? AND custom_attributes->>'customer_type' = ?", Date.tomorrow, 'platinum').count | ||||
|  | ||||
|         expect(result[:contacts].length).to be expected_count | ||||
|         expect(result[:contacts].first.id).to eq(el_contact.id) | ||||
|         expect(result[:contacts].pluck(:id)).to include(el_contact.id) | ||||
|       end | ||||
|  | ||||
|       context 'with x_days_before filter' do | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Tejaswini Chile
					Tejaswini Chile