mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 19:17:48 +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 |   Enabled: false | ||||||
| # we should bring this down | # we should bring this down | ||||||
| RSpec/MultipleMemoizedHelpers: | RSpec/MultipleMemoizedHelpers: | ||||||
|   Max: 12 |   Max: 14 | ||||||
|  |  | ||||||
| AllCops: | AllCops: | ||||||
|   NewCops: enable |   NewCops: enable | ||||||
|   | |||||||
| @@ -14,7 +14,9 @@ | |||||||
|             "CONVERSATION_UPDATED": "Conversation Updated", |             "CONVERSATION_UPDATED": "Conversation Updated", | ||||||
|             "MESSAGE_CREATED": "Message created", |             "MESSAGE_CREATED": "Message created", | ||||||
|             "MESSAGE_UPDATED": "Message updated", |             "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": { |         "END_POINT": { | ||||||
|   | |||||||
| @@ -61,6 +61,8 @@ const SUPPORTED_WEBHOOK_EVENTS = [ | |||||||
|   'message_created', |   'message_created', | ||||||
|   'message_updated', |   'message_updated', | ||||||
|   'webwidget_triggered', |   'webwidget_triggered', | ||||||
|  |   'contact_created', | ||||||
|  |   'contact_updated', | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   | |||||||
| @@ -51,10 +51,25 @@ class WebhookListener < BaseListener | |||||||
|     deliver_webhook_payloads(payload, inbox) |     deliver_webhook_payloads(payload, inbox) | ||||||
|   end |   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 |   private | ||||||
|  |  | ||||||
|   def deliver_account_webhooks(payload, inbox) |   def deliver_account_webhooks(payload, account) | ||||||
|     inbox.account.webhooks.account_type.each do |webhook| |     account.webhooks.account_type.each do |webhook| | ||||||
|       next unless webhook.subscriptions.include?(payload[:event]) |       next unless webhook.subscriptions.include?(payload[:event]) | ||||||
|  |  | ||||||
|       WebhookJob.perform_later(webhook.url, payload) |       WebhookJob.perform_later(webhook.url, payload) | ||||||
| @@ -69,7 +84,7 @@ class WebhookListener < BaseListener | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   def deliver_webhook_payloads(payload, inbox) |   def deliver_webhook_payloads(payload, inbox) | ||||||
|     deliver_account_webhooks(payload, inbox) |     deliver_account_webhooks(payload, inbox.account) | ||||||
|     deliver_api_inbox_webhooks(payload, inbox) |     deliver_api_inbox_webhooks(payload, inbox) | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -121,11 +121,16 @@ class Contact < ApplicationRecord | |||||||
|  |  | ||||||
|   def webhook_data |   def webhook_data | ||||||
|     { |     { | ||||||
|       id: id, |       account: account.webhook_data, | ||||||
|       name: name, |       additional_attributes: additional_attributes, | ||||||
|       avatar: avatar_url, |       avatar: avatar_url, | ||||||
|       type: 'contact', |       custom_attributes: custom_attributes, | ||||||
|       account: account.webhook_data |       email: email, | ||||||
|  |       id: id, | ||||||
|  |       identifier: identifier, | ||||||
|  |       name: name, | ||||||
|  |       phone_number: phone_number, | ||||||
|  |       thumbnail: avatar_url | ||||||
|     } |     } | ||||||
|   end |   end | ||||||
|  |  | ||||||
| @@ -180,7 +185,7 @@ class Contact < ApplicationRecord | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   def dispatch_update_event |   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 |   end | ||||||
|  |  | ||||||
|   def dispatch_destroy_event |   def dispatch_destroy_event | ||||||
|   | |||||||
| @@ -25,8 +25,8 @@ class Webhook < ApplicationRecord | |||||||
|   validate :validate_webhook_subscriptions |   validate :validate_webhook_subscriptions | ||||||
|   enum webhook_type: { account_type: 0, inbox_type: 1 } |   enum webhook_type: { account_type: 0, inbox_type: 1 } | ||||||
|  |  | ||||||
|   ALLOWED_WEBHOOK_EVENTS = %w[conversation_status_changed conversation_updated conversation_created message_created message_updated |   ALLOWED_WEBHOOK_EVENTS = %w[conversation_status_changed conversation_updated conversation_created contact_created contact_updated | ||||||
|                               webwidget_triggered].freeze |                               message_created message_updated webwidget_triggered].freeze | ||||||
|  |  | ||||||
|   private |   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. | # 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 |   # These are extensions that must be enabled in order to support this database | ||||||
|   enable_extension "pg_stat_statements" |   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 "created_at", precision: 6, null: false | ||||||
|     t.datetime "updated_at", precision: 6, null: false |     t.datetime "updated_at", precision: 6, null: false | ||||||
|     t.integer "webhook_type", default: 0 |     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 |     t.index ["account_id", "url"], name: "index_webhooks_on_account_id_and_url", unique: true | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   | |||||||
| @@ -84,8 +84,8 @@ RSpec.describe 'Webhooks API', type: :request do | |||||||
|         expect(response).to have_http_status(:ok) |         expect(response).to have_http_status(:ok) | ||||||
|         expect( |         expect( | ||||||
|           JSON.parse(response.body)['payload']['webhook']['subscriptions'] |           JSON.parse(response.body)['payload']['webhook']['subscriptions'] | ||||||
|         ).to eql %w[conversation_status_changed conversation_updated conversation_created message_created message_updated |         ).to eql %w[conversation_status_changed conversation_updated conversation_created contact_created contact_updated | ||||||
|                     webwidget_triggered] |                     message_created message_updated webwidget_triggered] | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ FactoryBot.define do | |||||||
|         conversation_status_changed |         conversation_status_changed | ||||||
|         conversation_updated |         conversation_updated | ||||||
|         conversation_created |         conversation_created | ||||||
|  |         contact_created | ||||||
|  |         contact_updated | ||||||
|         message_created |         message_created | ||||||
|         message_updated |         message_updated | ||||||
|         webwidget_triggered |         webwidget_triggered | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ describe WebhookListener do | |||||||
|   let(:report_identity) { Reports::UpdateAccountIdentity.new(account, Time.zone.now) } |   let(:report_identity) { Reports::UpdateAccountIdentity.new(account, Time.zone.now) } | ||||||
|   let!(:user) { create(:user, account: account) } |   let!(:user) { create(:user, account: account) } | ||||||
|   let!(:inbox) { create(:inbox, 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!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user) } | ||||||
|   let!(:message) do |   let!(:message) do | ||||||
|     create(:message, message_type: 'outgoing', |     create(:message, message_type: 'outgoing', | ||||||
| @@ -12,6 +13,7 @@ describe WebhookListener do | |||||||
|   end |   end | ||||||
|   let!(:message_created_event) { Events::Base.new(event_name, Time.zone.now, message: message) } |   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!(: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 |   describe '#message_created' do | ||||||
|     let(:event_name) { :'message.created' } |     let(:event_name) { :'message.created' } | ||||||
| @@ -159,4 +161,60 @@ describe WebhookListener do | |||||||
|       end |       end | ||||||
|     end |     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 | end | ||||||
|   | |||||||
| @@ -14,6 +14,8 @@ properties: | |||||||
|         "conversation_created", |         "conversation_created", | ||||||
|         "conversation_status_changed", |         "conversation_status_changed", | ||||||
|         "conversation_updated", |         "conversation_updated", | ||||||
|  |         "contact_created", | ||||||
|  |         "contact_updated", | ||||||
|         "message_created", |         "message_created", | ||||||
|         "message_updated", |         "message_updated", | ||||||
|         "webwidget_triggered" |         "webwidget_triggered" | ||||||
|   | |||||||
| @@ -5264,6 +5264,8 @@ | |||||||
|               "conversation_created", |               "conversation_created", | ||||||
|               "conversation_status_changed", |               "conversation_status_changed", | ||||||
|               "conversation_updated", |               "conversation_updated", | ||||||
|  |               "contact_created", | ||||||
|  |               "contact_updated", | ||||||
|               "message_created", |               "message_created", | ||||||
|               "message_updated", |               "message_updated", | ||||||
|               "webwidget_triggered" |               "webwidget_triggered" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Shubham Kumar
					Shubham Kumar