mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 03:27:52 +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:
|
Metrics/ModuleLength:
|
||||||
Exclude:
|
Exclude:
|
||||||
- lib/seeders/message_seeder.rb
|
- lib/seeders/message_seeder.rb
|
||||||
|
- spec/support/slack_stubs.rb
|
||||||
Rails/ApplicationController:
|
Rails/ApplicationController:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/controllers/api/v1/widget/messages_controller.rb'
|
- 'app/controllers/api/v1/widget/messages_controller.rb'
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
class Api::V1::Integrations::WebhooksController < ApplicationController
|
class Api::V1::Integrations::WebhooksController < ApplicationController
|
||||||
def create
|
def create
|
||||||
builder = Integrations::Slack::IncomingMessageBuilder.new(params)
|
builder = Integrations::Slack::IncomingMessageBuilder.new(permitted_params)
|
||||||
response = builder.perform
|
response = builder.perform
|
||||||
render json: response
|
render json: response
|
||||||
end
|
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
|
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
|
made_with: Made with
|
||||||
header:
|
header:
|
||||||
go_to_homepage: Go to the main site
|
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
|
class Integrations::Slack::IncomingMessageBuilder
|
||||||
include Integrations::Slack::SlackMessageHelper
|
include Integrations::Slack::SlackMessageHelper
|
||||||
|
|
||||||
attr_reader :params
|
attr_reader :params
|
||||||
|
|
||||||
SUPPORTED_EVENT_TYPES = %w[event_callback url_verification].freeze
|
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
|
SUPPORTED_MESSAGE_TYPES = %w[rich_text].freeze
|
||||||
|
|
||||||
def initialize(params)
|
def initialize(params)
|
||||||
@@ -17,6 +18,8 @@ class Integrations::Slack::IncomingMessageBuilder
|
|||||||
verify_hook
|
verify_hook
|
||||||
elsif create_message?
|
elsif create_message?
|
||||||
create_message
|
create_message
|
||||||
|
elsif link_shared?
|
||||||
|
SlackUnfurlJob.perform_later(params, integration_hook)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -68,6 +71,10 @@ class Integrations::Slack::IncomingMessageBuilder
|
|||||||
thread_timestamp_available? && supported_message? && integration_hook
|
thread_timestamp_available? && supported_message? && integration_hook
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def link_shared?
|
||||||
|
params[:event][:type] == 'link_shared' && integration_hook
|
||||||
|
end
|
||||||
|
|
||||||
def message
|
def message
|
||||||
params[:event][:blocks]&.first
|
params[:event][:blocks]&.first
|
||||||
end
|
end
|
||||||
@@ -82,19 +89,6 @@ class Integrations::Slack::IncomingMessageBuilder
|
|||||||
@integration_hook ||= Integrations::Hook.find_by(reference_id: params[:event][:channel])
|
@integration_hook ||= Integrations::Hook.find_by(reference_id: params[:event][:channel])
|
||||||
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
|
|
||||||
|
|
||||||
def slack_client
|
def slack_client
|
||||||
@slack_client ||= Slack::Web::Client.new(token: @integration_hook.access_token)
|
@slack_client ||= Slack::Web::Client.new(token: @integration_hook.access_token)
|
||||||
end
|
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
|
perform_reply
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def link_unfurl(event)
|
||||||
|
slack_client.chat_unfurl(
|
||||||
|
event
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def valid_channel_for_slack?
|
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
|
:file
|
||||||
end
|
end
|
||||||
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
|
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
|
describe Integrations::Slack::IncomingMessageBuilder do
|
||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:message_params) { slack_message_stub }
|
let(:message_params) { slack_message_stub }
|
||||||
|
let(:builder) { described_class.new(hook: hook) }
|
||||||
let(:private_message_params) do
|
let(:private_message_params) do
|
||||||
{
|
{
|
||||||
team_id: 'TLST3048H',
|
team_id: 'TLST3048H',
|
||||||
@@ -30,6 +31,8 @@ describe Integrations::Slack::IncomingMessageBuilder do
|
|||||||
event_time: 1_588_623_033
|
event_time: 1_588_623_033
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
let(:slack_client) { double }
|
||||||
|
let(:link_unfurl_service) { double }
|
||||||
let(:message_with_attachments) { slack_attachment_stub }
|
let(:message_with_attachments) { slack_attachment_stub }
|
||||||
let(:message_without_thread_ts) { slack_message_stub_without_thread_ts }
|
let(:message_without_thread_ts) { slack_message_stub_without_thread_ts }
|
||||||
let(:verification_params) { slack_url_verification_stub }
|
let(:verification_params) { slack_url_verification_stub }
|
||||||
@@ -145,5 +148,24 @@ describe Integrations::Slack::IncomingMessageBuilder do
|
|||||||
expect(conversation.messages.count).to eql(messages_count)
|
expect(conversation.messages.count).to eql(messages_count)
|
||||||
end
|
end
|
||||||
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
|
||||||
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_message_content) { double }
|
||||||
let(:slack_client) { double }
|
let(:slack_client) { double }
|
||||||
let(:builder) { described_class.new(message: message, hook: hook) }
|
let(:builder) { described_class.new(message: message, hook: hook) }
|
||||||
|
let(:link_builder) { described_class.new(message: nil, hook: hook) }
|
||||||
let(:conversation_link) do
|
let(:conversation_link) do
|
||||||
"<#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{account.id}/conversations/#{conversation.display_id}|Click here> to view the conversation."
|
"<#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{account.id}/conversations/#{conversation.display_id}|Click here> to view the conversation."
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(builder).to receive(:slack_client).and_return(slack_client)
|
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('ts').and_return('12345.6789')
|
||||||
allow(slack_message).to receive(:[]).with('message').and_return(slack_message_content)
|
allow(slack_message).to receive(:[]).with('message').and_return(slack_message_content)
|
||||||
allow(slack_message_content).to receive(:[]).with('ts').and_return('6789.12345')
|
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'
|
expect(conversation.identifier).to eq '1691652432.896169'
|
||||||
end
|
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
|
it 'sent attachment on slack' do
|
||||||
expect(slack_client).to receive(:chat_postMessage).with(
|
expect(slack_client).to receive(:chat_postMessage).with(
|
||||||
channel: hook.reference_id,
|
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'
|
channel: 'G01354F6A6Q'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def link_shared_event
|
||||||
|
{
|
||||||
|
type: 'link_shared',
|
||||||
|
user: 'ULYPAKE5S',
|
||||||
|
source: 'conversations_history',
|
||||||
|
unfurl_id: 'C7NQEAE5Q.1695111587.937099.7e240338c6d2053fb49f56808e7c1f619f6ef317c39ebc59fc4af1cc30dce49b',
|
||||||
|
channel: 'G01354F6A6Q'
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user