mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	feat: Ability to lock to single conversation (#5881)
Adds the ability to lock conversation to a single thread for Whatsapp and Sms Inboxes when using outbound messages. demo: https://www.loom.com/share/c9e1e563c8914837a4139dfdd2503fef fixes: #4975 Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
This commit is contained in:
		| @@ -16,7 +16,6 @@ Metrics/ClassLength: | |||||||
|     - 'app/models/message.rb' |     - 'app/models/message.rb' | ||||||
|     - 'app/builders/messages/facebook/message_builder.rb' |     - 'app/builders/messages/facebook/message_builder.rb' | ||||||
|     - 'app/controllers/api/v1/accounts/contacts_controller.rb' |     - 'app/controllers/api/v1/accounts/contacts_controller.rb' | ||||||
|     - 'app/controllers/api/v1/accounts/conversations_controller.rb' |  | ||||||
|     - 'app/listeners/action_cable_listener.rb' |     - 'app/listeners/action_cable_listener.rb' | ||||||
|     - 'app/models/conversation.rb' |     - 'app/models/conversation.rb' | ||||||
| RSpec/ExampleLength: | RSpec/ExampleLength: | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								app/builders/conversation_builder.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/builders/conversation_builder.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | class ConversationBuilder | ||||||
|  |   pattr_initialize [:params!, :contact_inbox!] | ||||||
|  |  | ||||||
|  |   def perform | ||||||
|  |     look_up_exising_conversation || create_new_conversation | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def look_up_exising_conversation | ||||||
|  |     return unless @contact_inbox.inbox.lock_to_single_conversation? | ||||||
|  |  | ||||||
|  |     @contact_inbox.conversations.last | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def create_new_conversation | ||||||
|  |     ::Conversation.create!(conversation_params) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def conversation_params | ||||||
|  |     additional_attributes = params[:additional_attributes]&.permit! || {} | ||||||
|  |     custom_attributes = params[:custom_attributes]&.permit! || {} | ||||||
|  |     status = params[:status].present? ? { status: params[:status] } : {} | ||||||
|  |  | ||||||
|  |     # TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases | ||||||
|  |     # commenting this out to see if there are any errors, if not we can remove this in subsequent releases | ||||||
|  |     # status = { status: 'pending' } if status[:status] == 'bot' | ||||||
|  |     { | ||||||
|  |       account_id: @contact_inbox.inbox.account_id, | ||||||
|  |       inbox_id: @contact_inbox.inbox_id, | ||||||
|  |       contact_id: @contact_inbox.contact_id, | ||||||
|  |       contact_inbox_id: @contact_inbox.id, | ||||||
|  |       additional_attributes: additional_attributes, | ||||||
|  |       custom_attributes: custom_attributes, | ||||||
|  |       snoozed_until: params[:snoozed_until], | ||||||
|  |       assignee_id: params[:assignee_id], | ||||||
|  |       team_id: params[:team_id] | ||||||
|  |     }.merge(status) | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -24,7 +24,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro | |||||||
|  |  | ||||||
|   def create |   def create | ||||||
|     ActiveRecord::Base.transaction do |     ActiveRecord::Base.transaction do | ||||||
|       @conversation = ::Conversation.create!(conversation_params) |       @conversation = ConversationBuilder.new(params: params, contact_inbox: @contact_inbox).perform | ||||||
|       Messages::MessageBuilder.new(Current.user, @conversation, params[:message]).perform if params[:message].present? |       Messages::MessageBuilder.new(Current.user, @conversation, params[:message]).perform if params[:message].present? | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| @@ -99,8 +99,10 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   def set_conversation_status |   def set_conversation_status | ||||||
|     status = params[:status] == 'bot' ? 'pending' : params[:status] |     # TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases | ||||||
|     @conversation.status = status |     # commenting this out to see if there are any errors, if not we can remove this in subsequent releases | ||||||
|  |     # status = params[:status] == 'bot' ? 'pending' : params[:status] | ||||||
|  |     @conversation.status = params[:status] | ||||||
|     @conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until] |     @conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until] | ||||||
|   end |   end | ||||||
|  |  | ||||||
| @@ -152,26 +154,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro | |||||||
|     ).perform |     ).perform | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def conversation_params |  | ||||||
|     additional_attributes = params[:additional_attributes]&.permit! || {} |  | ||||||
|     custom_attributes = params[:custom_attributes]&.permit! || {} |  | ||||||
|     status = params[:status].present? ? { status: params[:status] } : {} |  | ||||||
|  |  | ||||||
|     # TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases |  | ||||||
|     status = { status: 'pending' } if status[:status] == 'bot' |  | ||||||
|     { |  | ||||||
|       account_id: Current.account.id, |  | ||||||
|       inbox_id: @contact_inbox.inbox_id, |  | ||||||
|       contact_id: @contact_inbox.contact_id, |  | ||||||
|       contact_inbox_id: @contact_inbox.id, |  | ||||||
|       additional_attributes: additional_attributes, |  | ||||||
|       custom_attributes: custom_attributes, |  | ||||||
|       snoozed_until: params[:snoozed_until], |  | ||||||
|       assignee_id: params[:assignee_id], |  | ||||||
|       team_id: params[:team_id] |  | ||||||
|     }.merge(status) |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def conversation_finder |   def conversation_finder | ||||||
|     @conversation_finder ||= ConversationFinder.new(Current.user, params) |     @conversation_finder ||= ConversationFinder.new(Current.user, params) | ||||||
|   end |   end | ||||||
|   | |||||||
| @@ -113,7 +113,8 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController | |||||||
|  |  | ||||||
|   def inbox_attributes |   def inbox_attributes | ||||||
|     [:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled, |     [:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled, | ||||||
|      :enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved] |      :enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved, | ||||||
|  |      :lock_to_single_conversation] | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def permitted_params(channel_attributes = []) |   def permitted_params(channel_attributes = []) | ||||||
|   | |||||||
| @@ -388,6 +388,10 @@ | |||||||
|         "ENABLED": "Enabled", |         "ENABLED": "Enabled", | ||||||
|         "DISABLED": "Disabled" |         "DISABLED": "Disabled" | ||||||
|       }, |       }, | ||||||
|  |       "LOCK_TO_SINGLE_CONVERSATION": { | ||||||
|  |         "ENABLED": "Enabled", | ||||||
|  |         "DISABLED": "Disabled" | ||||||
|  |       }, | ||||||
|       "ENABLE_HMAC": { |       "ENABLE_HMAC": { | ||||||
|         "LABEL": "Enable" |         "LABEL": "Enable" | ||||||
|       } |       } | ||||||
| @@ -441,6 +445,8 @@ | |||||||
|       "ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation", |       "ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation", | ||||||
|       "ENABLE_CONTINUITY_VIA_EMAIL": "Enable conversation continuity via email", |       "ENABLE_CONTINUITY_VIA_EMAIL": "Enable conversation continuity via email", | ||||||
|       "ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Conversations will continue over email if the contact email address is available.", |       "ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Conversations will continue over email if the contact email address is available.", | ||||||
|  |       "LOCK_TO_SINGLE_CONVERSATION": "Lock to single conversation", | ||||||
|  |       "LOCK_TO_SINGLE_CONVERSATION_SUB_TEXT": "Enable or disable multiple conversations for the same contact in this inbox", | ||||||
|       "INBOX_UPDATE_TITLE": "Inbox Settings", |       "INBOX_UPDATE_TITLE": "Inbox Settings", | ||||||
|       "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", |       "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", | ||||||
|       "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.", |       "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.", | ||||||
|   | |||||||
| @@ -258,6 +258,28 @@ | |||||||
|           </p> |           </p> | ||||||
|         </label> |         </label> | ||||||
|  |  | ||||||
|  |         <label | ||||||
|  |           v-if="canLocktoSingleConversation" | ||||||
|  |           class="medium-9 columns settings-item" | ||||||
|  |         > | ||||||
|  |           {{ $t('INBOX_MGMT.SETTINGS_POPUP.LOCK_TO_SINGLE_CONVERSATION') }} | ||||||
|  |           <select v-model="locktoSingleConversation"> | ||||||
|  |             <option :value="true"> | ||||||
|  |               {{ $t('INBOX_MGMT.EDIT.LOCK_TO_SINGLE_CONVERSATION.ENABLED') }} | ||||||
|  |             </option> | ||||||
|  |             <option :value="false"> | ||||||
|  |               {{ $t('INBOX_MGMT.EDIT.LOCK_TO_SINGLE_CONVERSATION.DISABLED') }} | ||||||
|  |             </option> | ||||||
|  |           </select> | ||||||
|  |           <p class="help-text"> | ||||||
|  |             {{ | ||||||
|  |               $t( | ||||||
|  |                 'INBOX_MGMT.SETTINGS_POPUP.LOCK_TO_SINGLE_CONVERSATION_SUB_TEXT' | ||||||
|  |               ) | ||||||
|  |             }} | ||||||
|  |           </p> | ||||||
|  |         </label> | ||||||
|  |  | ||||||
|         <label v-if="isAWebWidgetInbox"> |         <label v-if="isAWebWidgetInbox"> | ||||||
|           {{ $t('INBOX_MGMT.FEATURES.LABEL') }} |           {{ $t('INBOX_MGMT.FEATURES.LABEL') }} | ||||||
|         </label> |         </label> | ||||||
| @@ -380,6 +402,7 @@ export default { | |||||||
|       greetingMessage: '', |       greetingMessage: '', | ||||||
|       emailCollectEnabled: false, |       emailCollectEnabled: false, | ||||||
|       csatSurveyEnabled: false, |       csatSurveyEnabled: false, | ||||||
|  |       locktoSingleConversation: false, | ||||||
|       allowMessagesAfterResolved: true, |       allowMessagesAfterResolved: true, | ||||||
|       continuityViaEmail: true, |       continuityViaEmail: true, | ||||||
|       selectedInboxName: '', |       selectedInboxName: '', | ||||||
| @@ -496,6 +519,9 @@ export default { | |||||||
|       } |       } | ||||||
|       return this.inbox.name; |       return this.inbox.name; | ||||||
|     }, |     }, | ||||||
|  |     canLocktoSingleConversation() { | ||||||
|  |       return this.isASmsInbox || this.isAWhatsAppChannel; | ||||||
|  |     }, | ||||||
|     inboxNameLabel() { |     inboxNameLabel() { | ||||||
|       if (this.isAWebWidgetInbox) { |       if (this.isAWebWidgetInbox) { | ||||||
|         return this.$t('INBOX_MGMT.ADD.WEBSITE_NAME.LABEL'); |         return this.$t('INBOX_MGMT.ADD.WEBSITE_NAME.LABEL'); | ||||||
| @@ -567,6 +593,7 @@ export default { | |||||||
|         this.channelWelcomeTagline = this.inbox.welcome_tagline; |         this.channelWelcomeTagline = this.inbox.welcome_tagline; | ||||||
|         this.selectedFeatureFlags = this.inbox.selected_feature_flags || []; |         this.selectedFeatureFlags = this.inbox.selected_feature_flags || []; | ||||||
|         this.replyTime = this.inbox.reply_time; |         this.replyTime = this.inbox.reply_time; | ||||||
|  |         this.locktoSingleConversation = this.inbox.lock_to_single_conversation; | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|     async updateInbox() { |     async updateInbox() { | ||||||
| @@ -579,6 +606,7 @@ export default { | |||||||
|           allow_messages_after_resolved: this.allowMessagesAfterResolved, |           allow_messages_after_resolved: this.allowMessagesAfterResolved, | ||||||
|           greeting_enabled: this.greetingEnabled, |           greeting_enabled: this.greetingEnabled, | ||||||
|           greeting_message: this.greetingMessage || '', |           greeting_message: this.greetingMessage || '', | ||||||
|  |           lock_to_single_conversation: this.locktoSingleConversation, | ||||||
|           channel: { |           channel: { | ||||||
|             widget_color: this.inbox.widget_color, |             widget_color: this.inbox.widget_color, | ||||||
|             website_url: this.channelWebsiteUrl, |             website_url: this.channelWebsiteUrl, | ||||||
|   | |||||||
| @@ -89,7 +89,19 @@ export const mutations = { | |||||||
|   }, |   }, | ||||||
|   [types.default.ADD_CONTACT_CONVERSATION]: ($state, { id, data }) => { |   [types.default.ADD_CONTACT_CONVERSATION]: ($state, { id, data }) => { | ||||||
|     const conversations = $state.records[id] || []; |     const conversations = $state.records[id] || []; | ||||||
|     Vue.set($state.records, id, [...conversations, data]); |  | ||||||
|  |     const updatedConversations = [...conversations]; | ||||||
|  |     const index = conversations.findIndex( | ||||||
|  |       conversation => conversation.id === data.id | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     if (index !== -1) { | ||||||
|  |       updatedConversations[index] = { ...conversations[index], ...data }; | ||||||
|  |     } else { | ||||||
|  |       updatedConversations.push(data); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Vue.set($state.records, id, updatedConversations); | ||||||
|   }, |   }, | ||||||
|   [types.default.DELETE_CONTACT_CONVERSATION]: ($state, id) => { |   [types.default.DELETE_CONTACT_CONVERSATION]: ($state, id) => { | ||||||
|     Vue.delete($state.records, id); |     Vue.delete($state.records, id); | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ | |||||||
| #  enable_email_collect          :boolean          default(TRUE) | #  enable_email_collect          :boolean          default(TRUE) | ||||||
| #  greeting_enabled              :boolean          default(FALSE) | #  greeting_enabled              :boolean          default(FALSE) | ||||||
| #  greeting_message              :string | #  greeting_message              :string | ||||||
|  | #  lock_to_single_conversation   :boolean          default(FALSE), not null | ||||||
| #  name                          :string           not null | #  name                          :string           not null | ||||||
| #  out_of_office_message         :string | #  out_of_office_message         :string | ||||||
| #  timezone                      :string           default("UTC") | #  timezone                      :string           default("UTC") | ||||||
|   | |||||||
| @@ -15,12 +15,13 @@ json.working_hours resource.weekly_schedule | |||||||
| json.timezone resource.timezone | json.timezone resource.timezone | ||||||
| json.callback_webhook_url resource.callback_webhook_url | json.callback_webhook_url resource.callback_webhook_url | ||||||
| json.allow_messages_after_resolved resource.allow_messages_after_resolved | json.allow_messages_after_resolved resource.allow_messages_after_resolved | ||||||
|  | json.lock_to_single_conversation resource.lock_to_single_conversation | ||||||
| json.tweets_enabled resource.channel.try(:tweets_enabled) if resource.twitter? |  | ||||||
|  |  | ||||||
| ## Channel specific settings | ## Channel specific settings | ||||||
| ## TODO : Clean up and move the attributes into channel sub section | ## TODO : Clean up and move the attributes into channel sub section | ||||||
|  |  | ||||||
|  | json.tweets_enabled resource.channel.try(:tweets_enabled) if resource.twitter? | ||||||
|  |  | ||||||
| ## WebWidget Attributes | ## WebWidget Attributes | ||||||
| json.widget_color resource.channel.try(:widget_color) | json.widget_color resource.channel.try(:widget_color) | ||||||
| json.website_url resource.channel.try(:website_url) | json.website_url resource.channel.try(:website_url) | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | class AddLockConversationToSingleThread < ActiveRecord::Migration[6.1] | ||||||
|  |   def change | ||||||
|  |     add_column :inboxes, :lock_to_single_conversation, :boolean, null: false, default: false | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -535,6 +535,7 @@ ActiveRecord::Schema.define(version: 2022_11_16_000514) do | |||||||
|     t.boolean "csat_survey_enabled", default: false |     t.boolean "csat_survey_enabled", default: false | ||||||
|     t.boolean "allow_messages_after_resolved", default: true |     t.boolean "allow_messages_after_resolved", default: true | ||||||
|     t.jsonb "auto_assignment_config", default: {} |     t.jsonb "auto_assignment_config", default: {} | ||||||
|  |     t.boolean "lock_to_single_conversation", default: false, null: false | ||||||
|     t.index ["account_id"], name: "index_inboxes_on_account_id" |     t.index ["account_id"], name: "index_inboxes_on_account_id" | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										46
									
								
								spec/builders/conversation_builder_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								spec/builders/conversation_builder_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | require 'rails_helper' | ||||||
|  |  | ||||||
|  | describe ::ConversationBuilder do | ||||||
|  |   let(:account) { create(:account) } | ||||||
|  |   let!(:sms_channel) { create(:channel_sms, account: account) } | ||||||
|  |   let!(:sms_inbox) { create(:inbox, channel: sms_channel, account: account) } | ||||||
|  |   let(:contact) { create(:contact, account: account) } | ||||||
|  |   let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: sms_inbox) } | ||||||
|  |  | ||||||
|  |   describe '#perform' do | ||||||
|  |     it 'creates conversation' do | ||||||
|  |       conversation = described_class.new( | ||||||
|  |         contact_inbox: contact_inbox, | ||||||
|  |         params: {} | ||||||
|  |       ).perform | ||||||
|  |  | ||||||
|  |       expect(conversation.contact_inbox_id).to eq(contact_inbox.id) | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     context 'when lock_to_single_conversation is true for inbox' do | ||||||
|  |       before do | ||||||
|  |         sms_inbox.update!(lock_to_single_conversation: true) | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'creates conversation when existing conversation is not present' do | ||||||
|  |         conversation = described_class.new( | ||||||
|  |           contact_inbox: contact_inbox, | ||||||
|  |           params: {} | ||||||
|  |         ).perform | ||||||
|  |  | ||||||
|  |         expect(conversation.contact_inbox_id).to eq(contact_inbox.id) | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'returns last from existing conversations when existing conversation is not present' do | ||||||
|  |         create(:conversation, contact_inbox: contact_inbox) | ||||||
|  |         existing_conversation = create(:conversation, contact_inbox: contact_inbox) | ||||||
|  |         conversation = described_class.new( | ||||||
|  |           contact_inbox: contact_inbox, | ||||||
|  |           params: {} | ||||||
|  |         ).perform | ||||||
|  |  | ||||||
|  |         expect(conversation.id).to eq(existing_conversation.id) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -265,17 +265,18 @@ RSpec.describe 'Conversations API', type: :request do | |||||||
|  |  | ||||||
|         # TODO: remove this spec when we remove the condition check in controller |         # TODO: remove this spec when we remove the condition check in controller | ||||||
|         # Added for backwards compatibility for bot status |         # Added for backwards compatibility for bot status | ||||||
|         it 'creates a conversation as pending if status is specified as bot' do |         # remove this in subsequent release | ||||||
|           allow(Rails.configuration.dispatcher).to receive(:dispatch) |         # it 'creates a conversation as pending if status is specified as bot' do | ||||||
|           post "/api/v1/accounts/#{account.id}/conversations", |         #   allow(Rails.configuration.dispatcher).to receive(:dispatch) | ||||||
|                headers: agent.create_new_auth_token, |         #   post "/api/v1/accounts/#{account.id}/conversations", | ||||||
|                params: { source_id: contact_inbox.source_id, status: 'bot' }, |         #        headers: agent.create_new_auth_token, | ||||||
|                as: :json |         #        params: { source_id: contact_inbox.source_id, status: 'bot' }, | ||||||
|  |         #        as: :json | ||||||
|  |  | ||||||
|           expect(response).to have_http_status(:success) |         #   expect(response).to have_http_status(:success) | ||||||
|           response_data = JSON.parse(response.body, symbolize_names: true) |         #   response_data = JSON.parse(response.body, symbolize_names: true) | ||||||
|           expect(response_data[:status]).to eq('pending') |         #   expect(response_data[:status]).to eq('pending') | ||||||
|         end |         # end | ||||||
|  |  | ||||||
|         it 'creates a new conversation with message when message is passed' do |         it 'creates a new conversation with message when message is passed' do | ||||||
|           allow(Rails.configuration.dispatcher).to receive(:dispatch) |           allow(Rails.configuration.dispatcher).to receive(:dispatch) | ||||||
| @@ -408,17 +409,18 @@ RSpec.describe 'Conversations API', type: :request do | |||||||
|  |  | ||||||
|       # TODO: remove this spec when we remove the condition check in controller |       # TODO: remove this spec when we remove the condition check in controller | ||||||
|       # Added for backwards compatibility for bot status |       # Added for backwards compatibility for bot status | ||||||
|       it 'toggles the conversation status to pending status when parameter bot is passed' do |       # remove in next release | ||||||
|         expect(conversation.status).to eq('open') |       # it 'toggles the conversation status to pending status when parameter bot is passed' do | ||||||
|  |       #   expect(conversation.status).to eq('open') | ||||||
|  |  | ||||||
|         post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status", |       #   post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status", | ||||||
|              headers: agent.create_new_auth_token, |       #        headers: agent.create_new_auth_token, | ||||||
|              params: { status: 'bot' }, |       #        params: { status: 'bot' }, | ||||||
|              as: :json |       #        as: :json | ||||||
|  |  | ||||||
|         expect(response).to have_http_status(:success) |       #   expect(response).to have_http_status(:success) | ||||||
|         expect(conversation.reload.status).to eq('pending') |       #   expect(conversation.reload.status).to eq('pending') | ||||||
|       end |       # end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Sojan Jose
					Sojan Jose