diff --git a/app/listeners/base_listener.rb b/app/listeners/base_listener.rb index 21b84c34a..397ec62ed 100644 --- a/app/listeners/base_listener.rb +++ b/app/listeners/base_listener.rb @@ -22,4 +22,12 @@ class BaseListener contact = event.data[:contact] [contact, contact.account] end + + def extract_changed_attributes(event) + changed_attributes = event.data[:changed_attributes] + + return if changed_attributes.blank? + + changed_attributes.map { |k, v| { k => { previous_value: v[0], current_value: v[1] } } } + end end diff --git a/app/listeners/webhook_listener.rb b/app/listeners/webhook_listener.rb index 98a694f2c..b0629a469 100644 --- a/app/listeners/webhook_listener.rb +++ b/app/listeners/webhook_listener.rb @@ -2,23 +2,34 @@ class WebhookListener < BaseListener # FIXME: deprecate the opened and resolved events in future in favor of status changed event. def conversation_resolved(event) conversation = extract_conversation_and_account(event)[0] + changed_attributes = extract_changed_attributes(event) inbox = conversation.inbox - payload = conversation.webhook_data.merge(event: __method__.to_s) + payload = conversation.webhook_data.merge(event: __method__.to_s, changed_attributes: changed_attributes) deliver_webhook_payloads(payload, inbox) end # FIXME: deprecate the opened and resolved events in future in favor of status changed event. def conversation_opened(event) conversation = extract_conversation_and_account(event)[0] + changed_attributes = extract_changed_attributes(event) inbox = conversation.inbox - payload = conversation.webhook_data.merge(event: __method__.to_s) + payload = conversation.webhook_data.merge(event: __method__.to_s, changed_attributes: changed_attributes) deliver_webhook_payloads(payload, inbox) end def conversation_status_changed(event) conversation = extract_conversation_and_account(event)[0] + changed_attributes = extract_changed_attributes(event) inbox = conversation.inbox - payload = conversation.webhook_data.merge(event: __method__.to_s) + payload = conversation.webhook_data.merge(event: __method__.to_s, changed_attributes: changed_attributes) + deliver_webhook_payloads(payload, inbox) + end + + def conversation_updated(event) + conversation = extract_conversation_and_account(event)[0] + changed_attributes = extract_changed_attributes(event) + inbox = conversation.inbox + payload = conversation.webhook_data.merge(event: __method__.to_s, changed_attributes: changed_attributes) deliver_webhook_payloads(payload, inbox) end diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 7bdb30ede..0296b5631 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -188,7 +188,7 @@ class Conversation < ApplicationRecord return unless previous_changes.keys.present? && (previous_changes.keys & %w[team_id assignee_id status snoozed_until custom_attributes]).present? - dispatcher_dispatch(CONVERSATION_UPDATED) + dispatcher_dispatch(CONVERSATION_UPDATED, previous_changes) end def self_assign?(assignee_id) @@ -207,14 +207,15 @@ class Conversation < ApplicationRecord CONVERSATION_READ => -> { saved_change_to_contact_last_seen_at? }, CONVERSATION_CONTACT_CHANGED => -> { saved_change_to_contact_id? } }.each do |event, condition| - condition.call && dispatcher_dispatch(event) + condition.call && dispatcher_dispatch(event, status_change) end end - def dispatcher_dispatch(event_name) + def dispatcher_dispatch(event_name, changed_attributes = nil) return if Current.executed_by.present? && Current.executed_by.instance_of?(AutomationRule) - Rails.configuration.dispatcher.dispatch(event_name, Time.zone.now, conversation: self, notifiable_assignee_change: notifiable_assignee_change?) + Rails.configuration.dispatcher.dispatch(event_name, Time.zone.now, conversation: self, notifiable_assignee_change: notifiable_assignee_change?, + changed_attributes: changed_attributes) end def conversation_status_changed_to_open? diff --git a/spec/listeners/webhook_listener_spec.rb b/spec/listeners/webhook_listener_spec.rb index 0bebc8d15..bcc990c5b 100644 --- a/spec/listeners/webhook_listener_spec.rb +++ b/spec/listeners/webhook_listener_spec.rb @@ -105,4 +105,80 @@ describe WebhookListener do end end end + + describe '#conversation_resolved' do + let!(:conversation_resolved_event) do + Events::Base.new(event_name, Time.zone.now, conversation: conversation.reload, changed_attributes: { status: [:open, :resolved] }) + end + let(:event_name) { :'conversation.resolved' } + + context 'when webhook is not configured' do + it 'does not trigger webhook' do + expect(WebhookJob).to receive(:perform_later).exactly(0).times + listener.conversation_resolved(conversation_resolved_event) + end + end + + context 'when webhook is configured' do + it 'triggers webhook' do + webhook = create(:webhook, inbox: inbox, account: account) + + conversation.update(status: :resolved) + + expect(WebhookJob).to receive(:perform_later).with(webhook.url, + conversation.webhook_data.merge(event: 'conversation_resolved', + changed_attributes: [{ status: { + current_value: :resolved, previous_value: :open + } }])).once + + listener.conversation_resolved(conversation_resolved_event) + end + end + end + + describe '#conversation_updated' do + let(:custom_attributes) { { test: nil } } + let!(:conversation_updated_event) do + Events::Base.new( + event_name, Time.zone.now, + conversation: conversation.reload, + changed_attributes: { + custom_attributes: [{ test: nil }, { test: 'testing custom attri webhook' }] + } + ) + end + let(:event_name) { :'conversation.updated' } + + context 'when webhook is not configured' do + it 'does not trigger webhook' do + expect(WebhookJob).to receive(:perform_later).exactly(0).times + listener.conversation_updated(conversation_updated_event) + end + end + + context 'when webhook is configured' do + it 'triggers webhook' do + webhook = create(:webhook, inbox: inbox, account: account) + + conversation.update(custom_attributes: { test: 'testing custom attri webhook' }) + + expect(WebhookJob).to receive(:perform_later).with( + webhook.url, + conversation.webhook_data.merge( + event: 'conversation_updated', + changed_attributes: [ + { + custom_attributes: { + previous_value: { test: nil }, + current_value: { test: 'testing custom attri webhook' } + } + } + ] + ) + ).once + + listener.conversation_updated(conversation_updated_event) + end + end + end end diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index a1d67f7d0..182073fbc 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -54,7 +54,8 @@ RSpec.describe Conversation, type: :model do it 'runs after_create callbacks' do # send_events expect(Rails.configuration.dispatcher).to have_received(:dispatch) - .with(described_class::CONVERSATION_CREATED, kind_of(Time), conversation: conversation, notifiable_assignee_change: false) + .with(described_class::CONVERSATION_CREATED, kind_of(Time), conversation: conversation, notifiable_assignee_change: false, + changed_attributes: nil) end end @@ -115,14 +116,21 @@ RSpec.describe Conversation, type: :model do assignee: new_assignee, label_list: [label.title] ) + status_change = conversation.status_change + changed_attributes = conversation.previous_changes + expect(Rails.configuration.dispatcher).to have_received(:dispatch) - .with(described_class::CONVERSATION_RESOLVED, kind_of(Time), conversation: conversation, notifiable_assignee_change: true) + .with(described_class::CONVERSATION_RESOLVED, kind_of(Time), conversation: conversation, notifiable_assignee_change: true, + changed_attributes: status_change) expect(Rails.configuration.dispatcher).to have_received(:dispatch) - .with(described_class::CONVERSATION_READ, kind_of(Time), conversation: conversation, notifiable_assignee_change: true) + .with(described_class::CONVERSATION_READ, kind_of(Time), conversation: conversation, notifiable_assignee_change: true, + changed_attributes: nil) expect(Rails.configuration.dispatcher).to have_received(:dispatch) - .with(described_class::ASSIGNEE_CHANGED, kind_of(Time), conversation: conversation, notifiable_assignee_change: true) + .with(described_class::ASSIGNEE_CHANGED, kind_of(Time), conversation: conversation, notifiable_assignee_change: true, + changed_attributes: nil) expect(Rails.configuration.dispatcher).to have_received(:dispatch) - .with(described_class::CONVERSATION_UPDATED, kind_of(Time), conversation: conversation, notifiable_assignee_change: true) + .with(described_class::CONVERSATION_UPDATED, kind_of(Time), conversation: conversation, notifiable_assignee_change: true, + changed_attributes: changed_attributes) end it 'will not run conversation_updated event for empty updates' do