mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 11:37:58 +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