mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 19:17:48 +00:00 
			
		
		
		
	feat: Slack link unfurling (#7940)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
		| @@ -58,6 +58,7 @@ Metrics/BlockLength: | ||||
| Metrics/ModuleLength: | ||||
|   Exclude: | ||||
|     - lib/seeders/message_seeder.rb | ||||
|     - spec/support/slack_stubs.rb | ||||
| Rails/ApplicationController: | ||||
|   Exclude: | ||||
|     - 'app/controllers/api/v1/widget/messages_controller.rb' | ||||
|   | ||||
| @@ -1,7 +1,15 @@ | ||||
| class Api::V1::Integrations::WebhooksController < ApplicationController | ||||
|   def create | ||||
|     builder = Integrations::Slack::IncomingMessageBuilder.new(params) | ||||
|     builder = Integrations::Slack::IncomingMessageBuilder.new(permitted_params) | ||||
|     response = builder.perform | ||||
|     render json: response | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   # TODO: This is a temporary solution to permit all params for slack unfurling job. | ||||
|   # We should only permit the params that we use. Handle all the params based on events and send it to the respective services. | ||||
|   def permitted_params | ||||
|     params.permit! | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										7
									
								
								app/jobs/slack_unfurl_job.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/jobs/slack_unfurl_job.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| class SlackUnfurlJob < ApplicationJob | ||||
|   queue_as :low | ||||
|  | ||||
|   def perform(params, integration_hook) | ||||
|     Integrations::Slack::SlackLinkUnfurlService.new(params: params, integration_hook: integration_hook).perform | ||||
|   end | ||||
| end | ||||
							
								
								
									
										21
									
								
								config/initializers/monkey_patches/chat.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								config/initializers/monkey_patches/chat.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| module Slack | ||||
|   module Web | ||||
|     module Api | ||||
|       module Endpoints | ||||
|         module Chat | ||||
|           # TODO: Remove this monkey patch when PR for this issue https://github.com/slack-ruby/slack-ruby-client/issues/388 is merged | ||||
|           def chat_unfurl(options = {}) | ||||
|             if (options[:channel].nil? || options[:ts].nil?) && (options[:unfurl_id].nil? || options[:source].nil?) | ||||
|               raise ArgumentError, 'Either a combination of :channel and :ts or :unfurl_id and :source is required' | ||||
|             end | ||||
|  | ||||
|             raise ArgumentError, 'Required arguments :unfurls missing' if options[:unfurls].nil? | ||||
|  | ||||
|             options = options.merge(channel: conversations_id(options)['channel']['id']) if options[:channel] | ||||
|             post('chat.unfurl', options) | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @@ -218,3 +218,12 @@ en: | ||||
|       made_with: Made with | ||||
|     header: | ||||
|       go_to_homepage: Go to the main site | ||||
|   slack_unfurl: | ||||
|     fields: | ||||
|      name: Name | ||||
|      email: Email | ||||
|      phone_number: Phone | ||||
|      company_name: Company | ||||
|      inbox_name: Inbox | ||||
|      inbox_type: Inbox Type | ||||
|     button: Open conversation | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| class Integrations::Slack::IncomingMessageBuilder | ||||
|   include Integrations::Slack::SlackMessageHelper | ||||
|  | ||||
|   attr_reader :params | ||||
|  | ||||
|   SUPPORTED_EVENT_TYPES = %w[event_callback url_verification].freeze | ||||
|   SUPPORTED_EVENTS = %w[message].freeze | ||||
|   SUPPORTED_EVENTS = %w[message link_shared].freeze | ||||
|   SUPPORTED_MESSAGE_TYPES = %w[rich_text].freeze | ||||
|  | ||||
|   def initialize(params) | ||||
| @@ -17,6 +18,8 @@ class Integrations::Slack::IncomingMessageBuilder | ||||
|       verify_hook | ||||
|     elsif create_message? | ||||
|       create_message | ||||
|     elsif link_shared? | ||||
|       SlackUnfurlJob.perform_later(params, integration_hook) | ||||
|     end | ||||
|   end | ||||
|  | ||||
| @@ -68,6 +71,10 @@ class Integrations::Slack::IncomingMessageBuilder | ||||
|     thread_timestamp_available? && supported_message? && integration_hook | ||||
|   end | ||||
|  | ||||
|   def link_shared? | ||||
|     params[:event][:type] == 'link_shared' && integration_hook | ||||
|   end | ||||
|  | ||||
|   def message | ||||
|     params[:event][:blocks]&.first | ||||
|   end | ||||
| @@ -82,19 +89,6 @@ class Integrations::Slack::IncomingMessageBuilder | ||||
|     @integration_hook ||= Integrations::Hook.find_by(reference_id: params[:event][:channel]) | ||||
|   end | ||||
|  | ||||
|   def conversation | ||||
|     @conversation ||= Conversation.where(identifier: params[:event][:thread_ts]).first | ||||
|   end | ||||
|  | ||||
|   def sender | ||||
|     user_email = slack_client.users_info(user: params[:event][:user])[:user][:profile][:email] | ||||
|     conversation.account.users.find_by(email: user_email) | ||||
|   end | ||||
|  | ||||
|   def private_note? | ||||
|     params[:event][:text].strip.downcase.starts_with?('note:', 'private:') | ||||
|   end | ||||
|  | ||||
|   def slack_client | ||||
|     @slack_client ||= Slack::Web::Client.new(token: @integration_hook.access_token) | ||||
|   end | ||||
|   | ||||
							
								
								
									
										59
									
								
								lib/integrations/slack/link_unfurl_formatter.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								lib/integrations/slack/link_unfurl_formatter.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| class Integrations::Slack::LinkUnfurlFormatter | ||||
|   pattr_initialize [:url!, :user_info!, :inbox_name!, :inbox_type!] | ||||
|  | ||||
|   def perform | ||||
|     return {} if url.blank? | ||||
|  | ||||
|     { | ||||
|       url => { | ||||
|         'blocks' => preivew_blocks(user_info) + | ||||
|           open_conversation_button(url) | ||||
|       } | ||||
|     } | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def preivew_blocks(user_info) | ||||
|     [ | ||||
|       { | ||||
|         'type' => 'section', | ||||
|         'fields' => [ | ||||
|           preview_field(I18n.t('slack_unfurl.fields.name'), user_info[:user_name]), | ||||
|           preview_field(I18n.t('slack_unfurl.fields.email'), user_info[:email]), | ||||
|           preview_field(I18n.t('slack_unfurl.fields.phone_number'), user_info[:phone_number]), | ||||
|           preview_field(I18n.t('slack_unfurl.fields.company_name'), user_info[:company_name]), | ||||
|           preview_field(I18n.t('slack_unfurl.fields.inbox_name'), inbox_name), | ||||
|           preview_field(I18n.t('slack_unfurl.fields.inbox_type'), inbox_type) | ||||
|         ] | ||||
|       } | ||||
|     ] | ||||
|   end | ||||
|  | ||||
|   def preview_field(label, value) | ||||
|     { | ||||
|       'type' => 'mrkdwn', | ||||
|       'text' => "*#{label}:*\n#{value}" | ||||
|     } | ||||
|   end | ||||
|  | ||||
|   def open_conversation_button(url) | ||||
|     [ | ||||
|       { | ||||
|         'type' => 'actions', | ||||
|         'elements' => [ | ||||
|           { | ||||
|             'type' => 'button', | ||||
|             'text' => { | ||||
|               'type' => 'plain_text', | ||||
|               'text' => I18n.t('slack_unfurl.button'), | ||||
|               'emoji' => true | ||||
|             }, | ||||
|             'url' => url, | ||||
|             'action_id' => 'button-action' | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
|     ] | ||||
|   end | ||||
| end | ||||
| @@ -14,6 +14,12 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService | ||||
|     perform_reply | ||||
|   end | ||||
|  | ||||
|   def link_unfurl(event) | ||||
|     slack_client.chat_unfurl( | ||||
|       event | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def valid_channel_for_slack? | ||||
|   | ||||
							
								
								
									
										84
									
								
								lib/integrations/slack/slack_link_unfurl_service.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								lib/integrations/slack/slack_link_unfurl_service.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| class Integrations::Slack::SlackLinkUnfurlService | ||||
|   pattr_initialize [:params!, :integration_hook!] | ||||
|  | ||||
|   def perform | ||||
|     event_links = params.dig(:event, :links) | ||||
|     return unless event_links | ||||
|  | ||||
|     event_links.each do |link_info| | ||||
|       url = link_info[:url] | ||||
|       # Unfurl only if the account id is same as the integration hook account id | ||||
|       unfurl_link(url) if url && valid_account?(url) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def unfurl_link(url) | ||||
|     conversation = conversation_from_url(url) | ||||
|     return unless conversation | ||||
|  | ||||
|     send_unfurls(url, conversation) | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def contact_attributes(conversation) | ||||
|     contact = conversation.contact | ||||
|     { | ||||
|       user_name: contact.name.presence || '---', | ||||
|       email: contact.email.presence || '---', | ||||
|       phone_number: contact.phone_number.presence || '---', | ||||
|       company_name: contact.additional_attributes&.dig('company_name').presence || '---' | ||||
|     } | ||||
|   end | ||||
|  | ||||
|   def generate_unfurls(url, user_info, inbox) | ||||
|     Integrations::Slack::LinkUnfurlFormatter.new( | ||||
|       url: url, | ||||
|       user_info: user_info, | ||||
|       inbox_name: inbox.name, | ||||
|       inbox_type: inbox.channel.name | ||||
|     ).perform | ||||
|   end | ||||
|  | ||||
|   def send_unfurls(url, conversation) | ||||
|     user_info = contact_attributes(conversation) | ||||
|     unfurls = generate_unfurls(url, user_info, conversation.inbox) | ||||
|     unfurl_params = { | ||||
|       unfurl_id: params.dig(:event, :unfurl_id), | ||||
|       source: params.dig(:event, :source), | ||||
|       unfurls: JSON.generate(unfurls) | ||||
|     } | ||||
|  | ||||
|     slack_service = Integrations::Slack::SendOnSlackService.new( | ||||
|       message: nil, | ||||
|       hook: integration_hook | ||||
|     ) | ||||
|     slack_service.link_unfurl(unfurl_params) | ||||
|   end | ||||
|  | ||||
|   def conversation_from_url(url) | ||||
|     conversation_id = extract_conversation_id(url) | ||||
|     find_conversation_by_id(conversation_id) if conversation_id | ||||
|   end | ||||
|  | ||||
|   def find_conversation_by_id(conversation_id) | ||||
|     Conversation.find_by(display_id: conversation_id, account_id: integration_hook.account_id) | ||||
|   end | ||||
|  | ||||
|   def valid_account?(url) | ||||
|     account_id = extract_account_id(url) | ||||
|     account_id == integration_hook.account_id.to_s | ||||
|   end | ||||
|  | ||||
|   def extract_account_id(url) | ||||
|     account_id_regex = %r{/accounts/(\d+)} | ||||
|     match_data = url.match(account_id_regex) | ||||
|     match_data[1] if match_data | ||||
|   end | ||||
|  | ||||
|   def extract_conversation_id(url) | ||||
|     conversation_id_regex = %r{/conversations/(\d+)} | ||||
|     match_data = url.match(conversation_id_regex) | ||||
|     match_data[1] if match_data | ||||
|   end | ||||
| end | ||||
| @@ -62,4 +62,17 @@ module Integrations::Slack::SlackMessageHelper | ||||
|       :file | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def conversation | ||||
|     @conversation ||= Conversation.where(identifier: params[:event][:thread_ts]).first | ||||
|   end | ||||
|  | ||||
|   def sender | ||||
|     user_email = slack_client.users_info(user: params[:event][:user])[:user][:profile][:email] | ||||
|     conversation.account.users.find_by(email: user_email) | ||||
|   end | ||||
|  | ||||
|   def private_note? | ||||
|     params[:event][:text].strip.downcase.starts_with?('note:', 'private:') | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										38
									
								
								spec/jobs/slack_unfurl_job_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								spec/jobs/slack_unfurl_job_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| require 'rails_helper' | ||||
|  | ||||
| RSpec.describe SlackUnfurlJob do | ||||
|   subject(:job) { described_class.perform_later(params: link_shared, integration_hook: hook) } | ||||
|  | ||||
|   let(:account) { create(:account) } | ||||
|   let(:hook) { create(:integrations_hook, account: account) } | ||||
|   let(:inbox) { create(:inbox, account: account) } | ||||
|   let!(:conversation) { create(:conversation, inbox: inbox) } | ||||
|  | ||||
|   let(:link_shared) do | ||||
|     { | ||||
|       team_id: 'TLST3048H', | ||||
|       api_app_id: 'A012S5UETV4', | ||||
|       event: link_shared_event.merge(links: [{ | ||||
|                                        url: "https://qa.chatwoot.com/app/accounts/1/conversations/#{conversation.display_id}", | ||||
|                                        domain: 'qa.chatwoot.com' | ||||
|                                      }]), | ||||
|       type: 'event_callback', | ||||
|       event_time: 1_588_623_033 | ||||
|     } | ||||
|   end | ||||
|  | ||||
|   it 'enqueues the job' do | ||||
|     expect { job }.to have_enqueued_job(described_class) | ||||
|       .on_queue('low') | ||||
|   end | ||||
|  | ||||
|   it 'calls the SlackLinkUnfurlService' do | ||||
|     slack_link_unfurl_service = instance_double(Integrations::Slack::SlackLinkUnfurlService) | ||||
|  | ||||
|     expect(Integrations::Slack::SlackLinkUnfurlService).to receive(:new) | ||||
|       .with(params: link_shared, integration_hook: hook) | ||||
|       .and_return(slack_link_unfurl_service) | ||||
|     expect(slack_link_unfurl_service).to receive(:perform) | ||||
|     described_class.perform_now(link_shared, hook) | ||||
|   end | ||||
| end | ||||
| @@ -3,6 +3,7 @@ require 'rails_helper' | ||||
| describe Integrations::Slack::IncomingMessageBuilder do | ||||
|   let(:account) { create(:account) } | ||||
|   let(:message_params) { slack_message_stub } | ||||
|   let(:builder) { described_class.new(hook: hook) } | ||||
|   let(:private_message_params) do | ||||
|     { | ||||
|       team_id: 'TLST3048H', | ||||
| @@ -30,6 +31,8 @@ describe Integrations::Slack::IncomingMessageBuilder do | ||||
|       event_time: 1_588_623_033 | ||||
|     } | ||||
|   end | ||||
|   let(:slack_client) { double } | ||||
|   let(:link_unfurl_service) { double } | ||||
|   let(:message_with_attachments) { slack_attachment_stub } | ||||
|   let(:message_without_thread_ts) { slack_message_stub_without_thread_ts } | ||||
|   let(:verification_params) { slack_url_verification_stub } | ||||
| @@ -145,5 +148,24 @@ describe Integrations::Slack::IncomingMessageBuilder do | ||||
|         expect(conversation.messages.count).to eql(messages_count) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when link shared' do | ||||
|       let(:link_shared) do | ||||
|         { | ||||
|           team_id: 'TLST3048H', | ||||
|           api_app_id: 'A012S5UETV4', | ||||
|           event: link_shared_event.merge({ links: [{ url: "https://qa.chatwoot.com/app/accounts/1/conversations/#{conversation.display_id}", | ||||
|                                                      domain: 'qa.chatwoot.com' }] }), | ||||
|           type: 'event_callback', | ||||
|           event_time: 1_588_623_033 | ||||
|         } | ||||
|       end | ||||
|  | ||||
|       it 'unfurls link' do | ||||
|         builder = described_class.new(link_shared) | ||||
|         expect(SlackUnfurlJob).to receive(:perform_later).with(link_shared, hook) | ||||
|         builder.perform | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										61
									
								
								spec/lib/integrations/slack/link_unfurl_formatter_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								spec/lib/integrations/slack/link_unfurl_formatter_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| require 'rails_helper' | ||||
|  | ||||
| describe Integrations::Slack::LinkUnfurlFormatter do | ||||
|   let(:contact) { create(:contact) } | ||||
|   let(:inbox) { create(:inbox) } | ||||
|   let(:url) { 'https://example.com/app/accounts/1/conversations/100' } | ||||
|  | ||||
|   describe '#perform' do | ||||
|     context 'when unfurling a URL' do | ||||
|       let(:user_info) do | ||||
|         { | ||||
|           user_name: contact.name, | ||||
|           email: contact.email, | ||||
|           phone_number: '---', | ||||
|           company_name: '---' | ||||
|         } | ||||
|       end | ||||
|  | ||||
|       let(:expected_payload) do | ||||
|         { | ||||
|           url => { | ||||
|             'blocks' => [ | ||||
|               { | ||||
|                 'type' => 'section', | ||||
|                 'fields' => [ | ||||
|                   { 'type' => 'mrkdwn', 'text' => "*Name:*\n#{contact.name}" }, | ||||
|                   { 'type' => 'mrkdwn', 'text' => "*Email:*\n#{contact.email}" }, | ||||
|                   { 'type' => 'mrkdwn', 'text' => "*Phone:*\n---" }, | ||||
|                   { 'type' => 'mrkdwn', 'text' => "*Company:*\n---" }, | ||||
|                   { 'type' => 'mrkdwn', 'text' => "*Inbox:*\n#{inbox.name}" }, | ||||
|                   { 'type' => 'mrkdwn', 'text' => "*Inbox Type:*\n#{inbox.channel_type}" } | ||||
|                 ] | ||||
|               }, | ||||
|               { | ||||
|                 'type' => 'actions', | ||||
|                 'elements' => [ | ||||
|                   { | ||||
|                     'type' => 'button', | ||||
|                     'text' => { 'type' => 'plain_text', 'text' => 'Open conversation', 'emoji' => true }, | ||||
|                     'url' => url, | ||||
|                     'action_id' => 'button-action' | ||||
|                   } | ||||
|                 ] | ||||
|               } | ||||
|             ] | ||||
|           } | ||||
|         } | ||||
|       end | ||||
|  | ||||
|       it 'returns expected unfurl blocks when the URL is not blank' do | ||||
|         formatter = described_class.new(url: url, user_info: user_info, inbox_name: inbox.name, inbox_type: inbox.channel_type) | ||||
|         expect(formatter.perform).to eq(expected_payload) | ||||
|       end | ||||
|  | ||||
|       it 'returns an empty hash when the URL is blank' do | ||||
|         formatter = described_class.new(url: nil, user_info: user_info, inbox_name: inbox.name, inbox_type: inbox.channel_type) | ||||
|         expect(formatter.perform).to eq({}) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @@ -18,12 +18,14 @@ describe Integrations::Slack::SendOnSlackService do | ||||
|   let(:slack_message_content) { double } | ||||
|   let(:slack_client) { double } | ||||
|   let(:builder) { described_class.new(message: message, hook: hook) } | ||||
|   let(:link_builder) { described_class.new(message: nil, hook: hook) } | ||||
|   let(:conversation_link) do | ||||
|     "<#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{account.id}/conversations/#{conversation.display_id}|Click here> to view the conversation." | ||||
|   end | ||||
|  | ||||
|   before do | ||||
|     allow(builder).to receive(:slack_client).and_return(slack_client) | ||||
|     allow(link_builder).to receive(:slack_client).and_return(slack_client) | ||||
|     allow(slack_message).to receive(:[]).with('ts').and_return('12345.6789') | ||||
|     allow(slack_message).to receive(:[]).with('message').and_return(slack_message_content) | ||||
|     allow(slack_message_content).to receive(:[]).with('ts').and_return('6789.12345') | ||||
| @@ -135,6 +137,18 @@ describe Integrations::Slack::SendOnSlackService do | ||||
|         expect(conversation.identifier).to eq '1691652432.896169' | ||||
|       end | ||||
|  | ||||
|       it 'sent lnk unfurl to slack' do | ||||
|         unflur_payload = { :channel => 'channel', | ||||
|                            :ts => 'timestamp', | ||||
|                            :unfurls => | ||||
|            { :'https://qa.chatwoot.com/app/accounts/1/conversations/1' => | ||||
|              { :blocks => [{ :type => 'section', | ||||
|                              :text => { :type => 'plain_text', :text => 'This is a plain text section block.', :emoji => true } }] } } } | ||||
|         allow(slack_client).to receive(:chat_unfurl).with(unflur_payload) | ||||
|         link_builder.link_unfurl(unflur_payload) | ||||
|         expect(slack_client).to have_received(:chat_unfurl).with(unflur_payload) | ||||
|       end | ||||
|  | ||||
|       it 'sent attachment on slack' do | ||||
|         expect(slack_client).to receive(:chat_postMessage).with( | ||||
|           channel: hook.reference_id, | ||||
|   | ||||
							
								
								
									
										163
									
								
								spec/lib/integrations/slack/slack_link_unfurl_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								spec/lib/integrations/slack/slack_link_unfurl_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| require 'rails_helper' | ||||
|  | ||||
| describe Integrations::Slack::SlackLinkUnfurlService do | ||||
|   let!(:contact) { create(:contact, name: 'Contact 1', email: nil, phone_number: nil) } | ||||
|   let(:channel_email) { create(:channel_email) } | ||||
|   let!(:conversation) { create(:conversation, inbox: channel_email.inbox, contact: contact, identifier: nil) } | ||||
|   let(:account) { conversation.account } | ||||
|   let!(:hook) { create(:integrations_hook, account: account) } | ||||
|  | ||||
|   describe '#perform' do | ||||
|     context 'when the event does not contain any link' do | ||||
|       let(:link_shared) do | ||||
|         { | ||||
|           team_id: 'TLST3048H', | ||||
|           api_app_id: 'A012S5UETV4', | ||||
|           event: link_shared_event.merge(links: []), | ||||
|           type: 'event_callback', | ||||
|           event_time: 1_588_623_033 | ||||
|         } | ||||
|       end | ||||
|       let(:link_builder) { described_class.new(params: link_shared, integration_hook: hook) } | ||||
|  | ||||
|       it 'does not send a POST request to Slack API' do | ||||
|         result = link_builder.perform | ||||
|         expect(result).to eq([]) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when the event link contains the account id which does not match the integration hook account id' do | ||||
|       let(:link_shared) do | ||||
|         { | ||||
|           team_id: 'TLST3048H', | ||||
|           api_app_id: 'A012S5UETV4', | ||||
|           event: link_shared_event.merge(links: [{ | ||||
|                                            url: "https://qa.chatwoot.com/app/accounts/1212/conversations/#{conversation.display_id}", | ||||
|                                            domain: 'qa.chatwoot.com' | ||||
|                                          }], channel: 'G054F6A6Q'), | ||||
|           type: 'event_callback', | ||||
|           event_time: 1_588_623_033 | ||||
|         } | ||||
|       end | ||||
|       let(:link_builder) { described_class.new(params: link_shared, integration_hook: hook) } | ||||
|  | ||||
|       it 'does not send a POST request to Slack API' do | ||||
|         link_builder.perform | ||||
|         expect(link_builder).not_to receive(:unfurl_link) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when the event link contains the conversation id which does not belong to the account' do | ||||
|       let(:link_shared) do | ||||
|         { | ||||
|           team_id: 'TLST3048H', | ||||
|           api_app_id: 'A012S5UETV4', | ||||
|           event: link_shared_event.merge(links: [{ | ||||
|                                            url: 'https://qa.chatwoot.com/app/accounts/1/conversations/1213', | ||||
|                                            domain: 'qa.chatwoot.com' | ||||
|                                          }], channel: 'G054F6A6Q'), | ||||
|           type: 'event_callback', | ||||
|           event_time: 1_588_623_033 | ||||
|         } | ||||
|       end | ||||
|       let(:link_builder) { described_class.new(params: link_shared, integration_hook: hook) } | ||||
|  | ||||
|       it 'does not send a POST request to Slack API' do | ||||
|         link_builder.perform | ||||
|         expect(link_builder).not_to receive(:unfurl_link) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when the event contains containing single link' do | ||||
|       let(:link_shared) do | ||||
|         { | ||||
|           team_id: 'TLST3048H', | ||||
|           api_app_id: 'A012S5UETV4', | ||||
|           event: link_shared_event.merge(links: [{ | ||||
|                                            url: "https://qa.chatwoot.com/app/accounts/1/conversations/#{conversation.display_id}", | ||||
|                                            domain: 'qa.chatwoot.com' | ||||
|                                          }]), | ||||
|           type: 'event_callback', | ||||
|           event_time: 1_588_623_033 | ||||
|         } | ||||
|       end | ||||
|       let(:link_builder) { described_class.new(params: link_shared, integration_hook: hook) } | ||||
|  | ||||
|       it 'sends a POST unfurl request to Slack' do | ||||
|         expected_body = { | ||||
|           'source' => 'conversations_history', | ||||
|           'unfurl_id' => 'C7NQEAE5Q.1695111587.937099.7e240338c6d2053fb49f56808e7c1f619f6ef317c39ebc59fc4af1cc30dce49b', | ||||
|           'unfurls' => '{"https://qa.chatwoot.com/app/accounts/1/conversations/1":' \ | ||||
|                        '{"blocks":[{' \ | ||||
|                        '"type":"section",' \ | ||||
|                        '"fields":[{' \ | ||||
|                        '"type":"mrkdwn",' \ | ||||
|                        "\"text\":\"*Name:*\\n#{contact.name}\"}," \ | ||||
|                        '{"type":"mrkdwn","text":"*Email:*\\n---"},' \ | ||||
|                        '{"type":"mrkdwn","text":"*Phone:*\\n---"},' \ | ||||
|                        '{"type":"mrkdwn","text":"*Company:*\\n---"},' \ | ||||
|                        "{\"type\":\"mrkdwn\",\"text\":\"*Inbox:*\\n#{channel_email.inbox.name}\"}," \ | ||||
|                        "{\"type\":\"mrkdwn\",\"text\":\"*Inbox Type:*\\n#{channel_email.inbox.channel.name}\"}]}," \ | ||||
|                        '{"type":"actions","elements":[{' \ | ||||
|                        '"type":"button",' \ | ||||
|                        '"text":{"type":"plain_text","text":"Open conversation","emoji":true},' \ | ||||
|                        '"url":"https://qa.chatwoot.com/app/accounts/1/conversations/1",' \ | ||||
|                        '"action_id":"button-action"}]}]}}' | ||||
|         } | ||||
|  | ||||
|         stub_request(:post, 'https://slack.com/api/chat.unfurl') | ||||
|           .with(body: expected_body) | ||||
|           .to_return(status: 200, body: '', headers: {}) | ||||
|         result = link_builder.perform | ||||
|         expect(result).to eq([{ url: 'https://qa.chatwoot.com/app/accounts/1/conversations/1', domain: 'qa.chatwoot.com' }]) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when the event contains containing multiple links' do | ||||
|       let(:link_shared_1) do | ||||
|         { | ||||
|           team_id: 'TLST3048H', | ||||
|           api_app_id: 'A012S5UETV4', | ||||
|           event: link_shared_event.merge(links: [{ | ||||
|                                            url: "https://qa.chatwoot.com/app/accounts/1/conversations/#{conversation.display_id}", | ||||
|                                            domain: 'qa.chatwoot.com' | ||||
|                                          }, | ||||
|                                                  { | ||||
|                                                    url: "https://qa.chatwoot.com/app/accounts/1/conversations/#{conversation.display_id}", | ||||
|                                                    domain: 'qa.chatwoot.com' | ||||
|                                                  }]), | ||||
|           type: 'event_callback', | ||||
|           event_time: 1_588_623_033 | ||||
|         } | ||||
|       end | ||||
|       let(:link_builder) { described_class.new(params: link_shared_1, integration_hook: hook) } | ||||
|  | ||||
|       it('sends multiple POST unfurl request to Slack') do | ||||
|         expected_body = { | ||||
|           'source' => 'conversations_history', | ||||
|           'unfurl_id' => 'C7NQEAE5Q.1695111587.937099.7e240338c6d2053fb49f56808e7c1f619f6ef317c39ebc59fc4af1cc30dce49b', | ||||
|           'unfurls' => '{"https://qa.chatwoot.com/app/accounts/1/conversations/1":' \ | ||||
|                        '{"blocks":[{' \ | ||||
|                        '"type":"section",' \ | ||||
|                        '"fields":[{' \ | ||||
|                        '"type":"mrkdwn",' \ | ||||
|                        "\"text\":\"*Name:*\\n#{contact.name}\"}," \ | ||||
|                        '{"type":"mrkdwn","text":"*Email:*\\n---"},' \ | ||||
|                        '{"type":"mrkdwn","text":"*Phone:*\\n---"},' \ | ||||
|                        '{"type":"mrkdwn","text":"*Company:*\\n---"},' \ | ||||
|                        "{\"type\":\"mrkdwn\",\"text\":\"*Inbox:*\\n#{channel_email.inbox.name}\"}," \ | ||||
|                        "{\"type\":\"mrkdwn\",\"text\":\"*Inbox Type:*\\n#{channel_email.inbox.channel.name}\"}]}," \ | ||||
|                        '{"type":"actions","elements":[{' \ | ||||
|                        '"type":"button",' \ | ||||
|                        '"text":{"type":"plain_text","text":"Open conversation","emoji":true},' \ | ||||
|                        '"url":"https://qa.chatwoot.com/app/accounts/1/conversations/1",' \ | ||||
|                        '"action_id":"button-action"}]}]}}' | ||||
|         } | ||||
|         stub_request(:post, 'https://slack.com/api/chat.unfurl') | ||||
|           .with(body: expected_body) | ||||
|           .to_return(status: 200, body: '', headers: {}) | ||||
|         expect { link_builder.perform }.not_to raise_error | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @@ -107,4 +107,14 @@ module SlackStubs | ||||
|       channel: 'G01354F6A6Q' | ||||
|     } | ||||
|   end | ||||
|  | ||||
|   def link_shared_event | ||||
|     { | ||||
|       type: 'link_shared', | ||||
|       user: 'ULYPAKE5S', | ||||
|       source: 'conversations_history', | ||||
|       unfurl_id: 'C7NQEAE5Q.1695111587.937099.7e240338c6d2053fb49f56808e7c1f619f6ef317c39ebc59fc4af1cc30dce49b', | ||||
|       channel: 'G01354F6A6Q' | ||||
|     } | ||||
|   end | ||||
| end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Muhsin Keloth
					Muhsin Keloth