mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	feat: Add webhook events for contact created, updated (#6415)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
		| @@ -163,7 +163,7 @@ RSpec/NamedSubject: | ||||
|   Enabled: false | ||||
| # we should bring this down | ||||
| RSpec/MultipleMemoizedHelpers: | ||||
|   Max: 12 | ||||
|   Max: 14 | ||||
|  | ||||
| AllCops: | ||||
|   NewCops: enable | ||||
|   | ||||
| @@ -14,7 +14,9 @@ | ||||
|             "CONVERSATION_UPDATED": "Conversation Updated", | ||||
|             "MESSAGE_CREATED": "Message created", | ||||
|             "MESSAGE_UPDATED": "Message updated", | ||||
|             "WEBWIDGET_TRIGGERED": "Live chat widget opened by the user" | ||||
|             "WEBWIDGET_TRIGGERED": "Live chat widget opened by the user", | ||||
|             "CONTACT_CREATED": "Contact created", | ||||
|             "CONTACT_UPDATED": "Contact update" | ||||
|           } | ||||
|         }, | ||||
|         "END_POINT": { | ||||
|   | ||||
| @@ -61,6 +61,8 @@ const SUPPORTED_WEBHOOK_EVENTS = [ | ||||
|   'message_created', | ||||
|   'message_updated', | ||||
|   'webwidget_triggered', | ||||
|   'contact_created', | ||||
|   'contact_updated', | ||||
| ]; | ||||
|  | ||||
| export default { | ||||
|   | ||||
| @@ -51,10 +51,25 @@ class WebhookListener < BaseListener | ||||
|     deliver_webhook_payloads(payload, inbox) | ||||
|   end | ||||
|  | ||||
|   def contact_created(event) | ||||
|     contact, account = extract_contact_and_account(event) | ||||
|     payload = contact.webhook_data.merge(event: __method__.to_s) | ||||
|     deliver_account_webhooks(payload, account) | ||||
|   end | ||||
|  | ||||
|   def contact_updated(event) | ||||
|     contact, account = extract_contact_and_account(event) | ||||
|     changed_attributes = extract_changed_attributes(event) | ||||
|     return if changed_attributes.blank? | ||||
|  | ||||
|     payload = contact.webhook_data.merge(event: __method__.to_s, changed_attributes: changed_attributes) | ||||
|     deliver_account_webhooks(payload, account) | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def deliver_account_webhooks(payload, inbox) | ||||
|     inbox.account.webhooks.account_type.each do |webhook| | ||||
|   def deliver_account_webhooks(payload, account) | ||||
|     account.webhooks.account_type.each do |webhook| | ||||
|       next unless webhook.subscriptions.include?(payload[:event]) | ||||
|  | ||||
|       WebhookJob.perform_later(webhook.url, payload) | ||||
| @@ -69,7 +84,7 @@ class WebhookListener < BaseListener | ||||
|   end | ||||
|  | ||||
|   def deliver_webhook_payloads(payload, inbox) | ||||
|     deliver_account_webhooks(payload, inbox) | ||||
|     deliver_account_webhooks(payload, inbox.account) | ||||
|     deliver_api_inbox_webhooks(payload, inbox) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -121,11 +121,16 @@ class Contact < ApplicationRecord | ||||
|  | ||||
|   def webhook_data | ||||
|     { | ||||
|       id: id, | ||||
|       name: name, | ||||
|       account: account.webhook_data, | ||||
|       additional_attributes: additional_attributes, | ||||
|       avatar: avatar_url, | ||||
|       type: 'contact', | ||||
|       account: account.webhook_data | ||||
|       custom_attributes: custom_attributes, | ||||
|       email: email, | ||||
|       id: id, | ||||
|       identifier: identifier, | ||||
|       name: name, | ||||
|       phone_number: phone_number, | ||||
|       thumbnail: avatar_url | ||||
|     } | ||||
|   end | ||||
|  | ||||
| @@ -180,7 +185,7 @@ class Contact < ApplicationRecord | ||||
|   end | ||||
|  | ||||
|   def dispatch_update_event | ||||
|     Rails.configuration.dispatcher.dispatch(CONTACT_UPDATED, Time.zone.now, contact: self) | ||||
|     Rails.configuration.dispatcher.dispatch(CONTACT_UPDATED, Time.zone.now, contact: self, changed_attributes: previous_changes) | ||||
|   end | ||||
|  | ||||
|   def dispatch_destroy_event | ||||
|   | ||||
| @@ -25,8 +25,8 @@ class Webhook < ApplicationRecord | ||||
|   validate :validate_webhook_subscriptions | ||||
|   enum webhook_type: { account_type: 0, inbox_type: 1 } | ||||
|  | ||||
|   ALLOWED_WEBHOOK_EVENTS = %w[conversation_status_changed conversation_updated conversation_created message_created message_updated | ||||
|                               webwidget_triggered].freeze | ||||
|   ALLOWED_WEBHOOK_EVENTS = %w[conversation_status_changed conversation_updated conversation_created contact_created contact_updated | ||||
|                               message_created message_updated webwidget_triggered].freeze | ||||
|  | ||||
|   private | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,9 @@ | ||||
| class UpdateDefaultOnSubscriptionsWebhooks < ActiveRecord::Migration[6.1] | ||||
|   def change | ||||
|     change_column_default :webhooks, :subscriptions, | ||||
|                           from: %w[conversation_status_changed conversation_updated conversation_created | ||||
|                                    message_created message_updated webwidget_triggered], | ||||
|                           to: %w[conversation_status_changed conversation_updated conversation_created | ||||
|                                  contact_created contact_updated message_created message_updated webwidget_triggered] | ||||
|   end | ||||
| end | ||||
| @@ -10,7 +10,7 @@ | ||||
| # | ||||
| # It's strongly recommended that you check this file into your version control system. | ||||
|  | ||||
| ActiveRecord::Schema.define(version: 2022_12_30_113108) do | ||||
| ActiveRecord::Schema.define(version: 2023_02_09_033203) do | ||||
|  | ||||
|   # These are extensions that must be enabled in order to support this database | ||||
|   enable_extension "pg_stat_statements" | ||||
| @@ -851,7 +851,7 @@ ActiveRecord::Schema.define(version: 2022_12_30_113108) do | ||||
|     t.datetime "created_at", precision: 6, null: false | ||||
|     t.datetime "updated_at", precision: 6, null: false | ||||
|     t.integer "webhook_type", default: 0 | ||||
|     t.jsonb "subscriptions", default: ["conversation_status_changed", "conversation_updated", "conversation_created", "message_created", "message_updated", "webwidget_triggered"] | ||||
|     t.jsonb "subscriptions", default: ["conversation_status_changed", "conversation_updated", "conversation_created", "contact_created", "contact_updated", "message_created", "message_updated", "webwidget_triggered"] | ||||
|     t.index ["account_id", "url"], name: "index_webhooks_on_account_id_and_url", unique: true | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -84,8 +84,8 @@ RSpec.describe 'Webhooks API', type: :request do | ||||
|         expect(response).to have_http_status(:ok) | ||||
|         expect( | ||||
|           JSON.parse(response.body)['payload']['webhook']['subscriptions'] | ||||
|         ).to eql %w[conversation_status_changed conversation_updated conversation_created message_created message_updated | ||||
|                     webwidget_triggered] | ||||
|         ).to eql %w[conversation_status_changed conversation_updated conversation_created contact_created contact_updated | ||||
|                     message_created message_updated webwidget_triggered] | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|   | ||||
| @@ -8,6 +8,8 @@ FactoryBot.define do | ||||
|         conversation_status_changed | ||||
|         conversation_updated | ||||
|         conversation_created | ||||
|         contact_created | ||||
|         contact_updated | ||||
|         message_created | ||||
|         message_updated | ||||
|         webwidget_triggered | ||||
|   | ||||
| @@ -5,6 +5,7 @@ describe WebhookListener do | ||||
|   let(:report_identity) { Reports::UpdateAccountIdentity.new(account, Time.zone.now) } | ||||
|   let!(:user) { create(:user, account: account) } | ||||
|   let!(:inbox) { create(:inbox, account: account) } | ||||
|   let!(:contact) { create(:contact, account: account) } | ||||
|   let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user) } | ||||
|   let!(:message) do | ||||
|     create(:message, message_type: 'outgoing', | ||||
| @@ -12,6 +13,7 @@ describe WebhookListener do | ||||
|   end | ||||
|   let!(:message_created_event) { Events::Base.new(event_name, Time.zone.now, message: message) } | ||||
|   let!(:conversation_created_event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation) } | ||||
|   let!(:contact_event) { Events::Base.new(event_name, Time.zone.now, contact: contact) } | ||||
|  | ||||
|   describe '#message_created' do | ||||
|     let(:event_name) { :'message.created' } | ||||
| @@ -159,4 +161,60 @@ describe WebhookListener do | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe '#contact_created' do | ||||
|     let(:event_name) { :'contact.created' } | ||||
|  | ||||
|     context 'when webhook is not configured' do | ||||
|       it 'does not trigger webhook' do | ||||
|         expect(WebhookJob).to receive(:perform_later).exactly(0).times | ||||
|         listener.contact_created(contact_event) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when webhook is configured' do | ||||
|       it 'triggers webhook' do | ||||
|         webhook = create(:webhook, account: account) | ||||
|         expect(WebhookJob).to receive(:perform_later).with(webhook.url, contact.webhook_data.merge(event: 'contact_created')).once | ||||
|         listener.contact_created(contact_event) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe '#contact_updated' do | ||||
|     let(:event_name) { :'contact.updated' } | ||||
|     let!(:contact_updated_event) { Events::Base.new(event_name, Time.zone.now, contact: contact, changed_attributes: changed_attributes) } | ||||
|     let(:changed_attributes) { { 'name' => ['Jane', 'Jane Doe'] } } | ||||
|  | ||||
|     context 'when webhook is not configured' do | ||||
|       it 'does not trigger webhook' do | ||||
|         expect(WebhookJob).to receive(:perform_later).exactly(0).times | ||||
|         listener.contact_updated(contact_updated_event) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when webhook is configured and there is no changed attributes' do | ||||
|       let(:changed_attributes) { {} } | ||||
|  | ||||
|       it 'triggers webhook' do | ||||
|         create(:webhook, account: account) | ||||
|         expect(WebhookJob).to receive(:perform_later).exactly(0).times | ||||
|         listener.contact_updated(contact_updated_event) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when webhook is configured and there are changed attributes' do | ||||
|       it 'triggers webhook' do | ||||
|         webhook = create(:webhook, account: account) | ||||
|         expect(WebhookJob).to receive(:perform_later).with( | ||||
|           webhook.url, | ||||
|           contact.webhook_data.merge( | ||||
|             event: 'contact_updated', | ||||
|             changed_attributes: [{ 'name' => { :current_value => 'Jane Doe', :previous_value => 'Jane' } }] | ||||
|           ) | ||||
|         ).once | ||||
|         listener.contact_updated(contact_updated_event) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -14,6 +14,8 @@ properties: | ||||
|         "conversation_created", | ||||
|         "conversation_status_changed", | ||||
|         "conversation_updated", | ||||
|         "contact_created", | ||||
|         "contact_updated", | ||||
|         "message_created", | ||||
|         "message_updated", | ||||
|         "webwidget_triggered" | ||||
|   | ||||
| @@ -5264,6 +5264,8 @@ | ||||
|               "conversation_created", | ||||
|               "conversation_status_changed", | ||||
|               "conversation_updated", | ||||
|               "contact_created", | ||||
|               "contact_updated", | ||||
|               "message_created", | ||||
|               "message_updated", | ||||
|               "webwidget_triggered" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Shubham Kumar
					Shubham Kumar