mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 04:57:51 +00:00 
			
		
		
		
	chore: Webhook event data improvements (#4317)
This commit is contained in:
		@@ -22,4 +22,12 @@ class BaseListener
 | 
				
			|||||||
    contact = event.data[:contact]
 | 
					    contact = event.data[:contact]
 | 
				
			||||||
    [contact, contact.account]
 | 
					    [contact, contact.account]
 | 
				
			||||||
  end
 | 
					  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
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,23 +2,34 @@ class WebhookListener < BaseListener
 | 
				
			|||||||
  # FIXME: deprecate the opened and resolved events in future in favor of status changed event.
 | 
					  # FIXME: deprecate the opened and resolved events in future in favor of status changed event.
 | 
				
			||||||
  def conversation_resolved(event)
 | 
					  def conversation_resolved(event)
 | 
				
			||||||
    conversation = extract_conversation_and_account(event)[0]
 | 
					    conversation = extract_conversation_and_account(event)[0]
 | 
				
			||||||
 | 
					    changed_attributes = extract_changed_attributes(event)
 | 
				
			||||||
    inbox = conversation.inbox
 | 
					    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)
 | 
					    deliver_webhook_payloads(payload, inbox)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # FIXME: deprecate the opened and resolved events in future in favor of status changed event.
 | 
					  # FIXME: deprecate the opened and resolved events in future in favor of status changed event.
 | 
				
			||||||
  def conversation_opened(event)
 | 
					  def conversation_opened(event)
 | 
				
			||||||
    conversation = extract_conversation_and_account(event)[0]
 | 
					    conversation = extract_conversation_and_account(event)[0]
 | 
				
			||||||
 | 
					    changed_attributes = extract_changed_attributes(event)
 | 
				
			||||||
    inbox = conversation.inbox
 | 
					    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)
 | 
					    deliver_webhook_payloads(payload, inbox)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def conversation_status_changed(event)
 | 
					  def conversation_status_changed(event)
 | 
				
			||||||
    conversation = extract_conversation_and_account(event)[0]
 | 
					    conversation = extract_conversation_and_account(event)[0]
 | 
				
			||||||
 | 
					    changed_attributes = extract_changed_attributes(event)
 | 
				
			||||||
    inbox = conversation.inbox
 | 
					    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)
 | 
					    deliver_webhook_payloads(payload, inbox)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -188,7 +188,7 @@ class Conversation < ApplicationRecord
 | 
				
			|||||||
    return unless previous_changes.keys.present? && (previous_changes.keys & %w[team_id assignee_id status snoozed_until
 | 
					    return unless previous_changes.keys.present? && (previous_changes.keys & %w[team_id assignee_id status snoozed_until
 | 
				
			||||||
                                                                                custom_attributes]).present?
 | 
					                                                                                custom_attributes]).present?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dispatcher_dispatch(CONVERSATION_UPDATED)
 | 
					    dispatcher_dispatch(CONVERSATION_UPDATED, previous_changes)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def self_assign?(assignee_id)
 | 
					  def self_assign?(assignee_id)
 | 
				
			||||||
@@ -207,14 +207,15 @@ class Conversation < ApplicationRecord
 | 
				
			|||||||
      CONVERSATION_READ => -> { saved_change_to_contact_last_seen_at? },
 | 
					      CONVERSATION_READ => -> { saved_change_to_contact_last_seen_at? },
 | 
				
			||||||
      CONVERSATION_CONTACT_CHANGED => -> { saved_change_to_contact_id? }
 | 
					      CONVERSATION_CONTACT_CHANGED => -> { saved_change_to_contact_id? }
 | 
				
			||||||
    }.each do |event, condition|
 | 
					    }.each do |event, condition|
 | 
				
			||||||
      condition.call && dispatcher_dispatch(event)
 | 
					      condition.call && dispatcher_dispatch(event, status_change)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  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)
 | 
					    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
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def conversation_status_changed_to_open?
 | 
					  def conversation_status_changed_to_open?
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -105,4 +105,80 @@ describe WebhookListener do
 | 
				
			|||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    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
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,7 +54,8 @@ RSpec.describe Conversation, type: :model do
 | 
				
			|||||||
    it 'runs after_create callbacks' do
 | 
					    it 'runs after_create callbacks' do
 | 
				
			||||||
      # send_events
 | 
					      # send_events
 | 
				
			||||||
      expect(Rails.configuration.dispatcher).to have_received(:dispatch)
 | 
					      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
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -115,14 +116,21 @@ RSpec.describe Conversation, type: :model do
 | 
				
			|||||||
        assignee: new_assignee,
 | 
					        assignee: new_assignee,
 | 
				
			||||||
        label_list: [label.title]
 | 
					        label_list: [label.title]
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
 | 
					      status_change = conversation.status_change
 | 
				
			||||||
 | 
					      changed_attributes = conversation.previous_changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(Rails.configuration.dispatcher).to have_received(:dispatch)
 | 
					      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)
 | 
					      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)
 | 
					      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)
 | 
					      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
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'will not run conversation_updated event for empty updates' do
 | 
					    it 'will not run conversation_updated event for empty updates' do
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user