From 29025759d64fb34b74b11b25289bfc91d91876f9 Mon Sep 17 00:00:00 2001 From: Shubham Kumar Date: Tue, 14 Feb 2023 02:58:27 +0530 Subject: [PATCH] feat: Add webhook events for contact created, updated (#6415) Co-authored-by: Pranav Raj S --- .rubocop.yml | 2 +- .../i18n/locale/en/integrations.json | 4 +- .../integrations/Webhooks/WebhookForm.vue | 2 + app/listeners/webhook_listener.rb | 21 ++++++- app/models/contact.rb | 15 +++-- app/models/webhook.rb | 4 +- ...pdate_default_on_subscriptions_webhooks.rb | 9 +++ db/schema.rb | 4 +- .../v1/accounts/webhook_controller_spec.rb | 4 +- spec/factories/webhooks.rb | 2 + spec/listeners/webhook_listener_spec.rb | 58 +++++++++++++++++++ swagger/definitions/resource/webhook.yml | 2 + swagger/swagger.json | 2 + 13 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 db/migrate/20230209033203_update_default_on_subscriptions_webhooks.rb diff --git a/.rubocop.yml b/.rubocop.yml index 426989bd6..1abae8bb1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -163,7 +163,7 @@ RSpec/NamedSubject: Enabled: false # we should bring this down RSpec/MultipleMemoizedHelpers: - Max: 12 + Max: 14 AllCops: NewCops: enable diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json index 5c7395424..b4851fd71 100644 --- a/app/javascript/dashboard/i18n/locale/en/integrations.json +++ b/app/javascript/dashboard/i18n/locale/en/integrations.json @@ -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": { diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/WebhookForm.vue b/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/WebhookForm.vue index 3d6be7ae0..5ab54cfb2 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/WebhookForm.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/Webhooks/WebhookForm.vue @@ -61,6 +61,8 @@ const SUPPORTED_WEBHOOK_EVENTS = [ 'message_created', 'message_updated', 'webwidget_triggered', + 'contact_created', + 'contact_updated', ]; export default { diff --git a/app/listeners/webhook_listener.rb b/app/listeners/webhook_listener.rb index 81d2c6b0a..d8f4c88fb 100644 --- a/app/listeners/webhook_listener.rb +++ b/app/listeners/webhook_listener.rb @@ -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 diff --git a/app/models/contact.rb b/app/models/contact.rb index 2cfcbc3c0..ec6053eb5 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -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 diff --git a/app/models/webhook.rb b/app/models/webhook.rb index 5b0095093..12d2f0dbf 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -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 diff --git a/db/migrate/20230209033203_update_default_on_subscriptions_webhooks.rb b/db/migrate/20230209033203_update_default_on_subscriptions_webhooks.rb new file mode 100644 index 000000000..18be46ebe --- /dev/null +++ b/db/migrate/20230209033203_update_default_on_subscriptions_webhooks.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 2b279b55a..c85a598cd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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 diff --git a/spec/controllers/api/v1/accounts/webhook_controller_spec.rb b/spec/controllers/api/v1/accounts/webhook_controller_spec.rb index aeea4d45f..ae1adfa77 100644 --- a/spec/controllers/api/v1/accounts/webhook_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/webhook_controller_spec.rb @@ -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 diff --git a/spec/factories/webhooks.rb b/spec/factories/webhooks.rb index b49434fce..42acc2be6 100644 --- a/spec/factories/webhooks.rb +++ b/spec/factories/webhooks.rb @@ -8,6 +8,8 @@ FactoryBot.define do conversation_status_changed conversation_updated conversation_created + contact_created + contact_updated message_created message_updated webwidget_triggered diff --git a/spec/listeners/webhook_listener_spec.rb b/spec/listeners/webhook_listener_spec.rb index 5dac1a8ec..f1a39d932 100644 --- a/spec/listeners/webhook_listener_spec.rb +++ b/spec/listeners/webhook_listener_spec.rb @@ -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 diff --git a/swagger/definitions/resource/webhook.yml b/swagger/definitions/resource/webhook.yml index 1e4c58904..a9881b8e4 100644 --- a/swagger/definitions/resource/webhook.yml +++ b/swagger/definitions/resource/webhook.yml @@ -14,6 +14,8 @@ properties: "conversation_created", "conversation_status_changed", "conversation_updated", + "contact_created", + "contact_updated", "message_created", "message_updated", "webwidget_triggered" diff --git a/swagger/swagger.json b/swagger/swagger.json index 7a31a56d1..5878d1e41 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -5264,6 +5264,8 @@ "conversation_created", "conversation_status_changed", "conversation_updated", + "contact_created", + "contact_updated", "message_created", "message_updated", "webwidget_triggered"