mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-28 17:52:39 +00:00
feat: Add support for grouped file uploads in Slack (#12454)
Fixes https://linear.app/chatwoot/issue/CW-5646/add-support-for-grouped-file-uploads-in-slack Previously, when sending multiple attachments to Slack, we uploaded them one by one. For example, sending 5 images would result in 5 separate Slack messages. This created clutter and a poor user experience, since Slack displayed each file as an individual message. This PR updates the implementation to group all attachments from a message and send them as a single Slack message. As a result, attachments now appear together in one grouped block, providing a much cleaner and more intuitive experience for users. **Before:** Each file uploaded as a separate Slack message. <img width="400" height="800" alt="before" src="https://github.com/user-attachments/assets/c8c7f666-549b-428f-bd19-c94e39ed2513" /> **After:** All files from a single message grouped and displayed together in one Slack message (similar to how Slack natively handles grouped uploads). <img width="400" height="800" alt="after" src="https://github.com/user-attachments/assets/0b1f22d5-4d37-4b84-905a-15e742317e72" /> **Changes** - Upgraded Slack file upload implementation to use the new multiple attachments API available in slack-ruby-client `v2.7.0`. - Updated attachment handling to upload all files from a message in a single API call. - Enabled proper attachment grouping in Slack, ensuring related files are presented together.
This commit is contained in:
2
Gemfile
2
Gemfile
@@ -103,7 +103,7 @@ gem 'twitty', '~> 0.1.5'
|
||||
# facebook client
|
||||
gem 'koala'
|
||||
# slack client
|
||||
gem 'slack-ruby-client', '~> 2.5.2'
|
||||
gem 'slack-ruby-client', '~> 2.7.0'
|
||||
# for dialogflow integrations
|
||||
gem 'google-cloud-dialogflow-v2', '>= 0.24.0'
|
||||
gem 'grpc'
|
||||
|
||||
@@ -292,7 +292,7 @@ GEM
|
||||
logger
|
||||
faraday-follow_redirects (0.3.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-mashify (0.1.1)
|
||||
faraday-mashify (1.0.0)
|
||||
faraday (~> 2.0)
|
||||
hashie
|
||||
faraday-multipart (1.0.4)
|
||||
@@ -876,8 +876,8 @@ GEM
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.13.2)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
slack-ruby-client (2.5.2)
|
||||
faraday (>= 2.0)
|
||||
slack-ruby-client (2.7.0)
|
||||
faraday (>= 2.0.1)
|
||||
faraday-mashify
|
||||
faraday-multipart
|
||||
gli
|
||||
@@ -1103,7 +1103,7 @@ DEPENDENCIES
|
||||
sidekiq_alive
|
||||
simplecov (>= 0.21)
|
||||
simplecov_json_formatter
|
||||
slack-ruby-client (~> 2.5.2)
|
||||
slack-ruby-client (~> 2.7.0)
|
||||
spring
|
||||
spring-watcher-listen
|
||||
squasher
|
||||
|
||||
@@ -101,7 +101,7 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService
|
||||
|
||||
def send_message
|
||||
post_message if message_content.present?
|
||||
upload_file if message.attachments.any?
|
||||
upload_files if message.attachments.any?
|
||||
rescue Slack::Web::Api::Errors::AccountInactive, Slack::Web::Api::Errors::MissingScope, Slack::Web::Api::Errors::InvalidAuth,
|
||||
Slack::Web::Api::Errors::ChannelNotFound, Slack::Web::Api::Errors::NotInChannel => e
|
||||
Rails.logger.error e
|
||||
@@ -120,36 +120,35 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService
|
||||
)
|
||||
end
|
||||
|
||||
def upload_file
|
||||
message.attachments.each do |attachment|
|
||||
next unless attachment.with_attached_file?
|
||||
def upload_files
|
||||
return unless message.attachments.any?
|
||||
|
||||
begin
|
||||
result = slack_client.files_upload_v2(
|
||||
filename: attachment.file.filename.to_s,
|
||||
content: attachment.file.download,
|
||||
initial_comment: 'Attached File!',
|
||||
thread_ts: conversation.identifier,
|
||||
channel_id: hook.reference_id
|
||||
)
|
||||
Rails.logger.info "slack_upload_result: #{result}"
|
||||
rescue Slack::Web::Api::Errors::SlackError => e
|
||||
Rails.logger.error "Failed to upload file #{attachment.file.filename}: #{e.message}"
|
||||
end
|
||||
files = build_files_array
|
||||
return if files.empty?
|
||||
|
||||
begin
|
||||
result = slack_client.files_upload_v2(
|
||||
files: files,
|
||||
initial_comment: 'Attached File!',
|
||||
thread_ts: conversation.identifier,
|
||||
channel_id: hook.reference_id
|
||||
)
|
||||
Rails.logger.info "slack_upload_result: #{result}"
|
||||
rescue Slack::Web::Api::Errors::SlackError => e
|
||||
Rails.logger.error "Failed to upload files: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def file_type
|
||||
File.extname(message.attachments.first.download_url).strip.downcase[1..]
|
||||
end
|
||||
def build_files_array
|
||||
message.attachments.filter_map do |attachment|
|
||||
next unless attachment.with_attached_file?
|
||||
|
||||
def file_information
|
||||
{
|
||||
filename: message.attachments.first.file.filename,
|
||||
filetype: file_type,
|
||||
content: message.attachments.first.file.download,
|
||||
title: message.attachments.first.file.filename
|
||||
}
|
||||
{
|
||||
filename: attachment.file.filename.to_s,
|
||||
content: attachment.file.download,
|
||||
title: attachment.file.filename.to_s
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def sender_name(sender)
|
||||
|
||||
@@ -163,8 +163,11 @@ describe Integrations::Slack::SendOnSlackService do
|
||||
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||
|
||||
expect(slack_client).to receive(:files_upload_v2).with(
|
||||
filename: attachment.file.filename.to_s,
|
||||
content: anything,
|
||||
files: [{
|
||||
filename: attachment.file.filename.to_s,
|
||||
content: anything,
|
||||
title: attachment.file.filename.to_s
|
||||
}],
|
||||
channel_id: hook.reference_id,
|
||||
thread_ts: conversation.identifier,
|
||||
initial_comment: 'Attached File!'
|
||||
@@ -179,27 +182,27 @@ describe Integrations::Slack::SendOnSlackService do
|
||||
end
|
||||
|
||||
it 'sent multiple attachments on slack' do
|
||||
expect(slack_client).to receive(:chat_postMessage).with(
|
||||
channel: hook.reference_id,
|
||||
text: message.content,
|
||||
username: "#{message.sender.name} (Contact)",
|
||||
thread_ts: conversation.identifier,
|
||||
icon_url: anything,
|
||||
unfurl_links: true
|
||||
).and_return(slack_message)
|
||||
expect(slack_client).to receive(:chat_postMessage).and_return(slack_message)
|
||||
|
||||
attachment1 = message.attachments.new(account_id: message.account_id, file_type: :image)
|
||||
attachment1.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||
|
||||
attachment2 = message.attachments.new(account_id: message.account_id, file_type: :image)
|
||||
attachment2.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'logo.png', content_type: 'image/png')
|
||||
|
||||
expect(slack_client).to receive(:files_upload_v2).twice.and_return(file_attachment)
|
||||
expected_files = [
|
||||
{ filename: 'avatar.png', content: anything, title: 'avatar.png' },
|
||||
{ filename: 'logo.png', content: anything, title: 'logo.png' }
|
||||
]
|
||||
expect(slack_client).to receive(:files_upload_v2).with(
|
||||
files: expected_files,
|
||||
channel_id: hook.reference_id,
|
||||
thread_ts: conversation.identifier,
|
||||
initial_comment: 'Attached File!'
|
||||
).and_return(file_attachment)
|
||||
|
||||
message.save!
|
||||
builder.perform
|
||||
|
||||
expect(message.external_source_id_slack).to eq 'cw-origin-6789.12345'
|
||||
expect(message.attachments.count).to eq 2
|
||||
end
|
||||
|
||||
@@ -217,14 +220,17 @@ describe Integrations::Slack::SendOnSlackService do
|
||||
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||
|
||||
expect(slack_client).to receive(:files_upload_v2).with(
|
||||
filename: attachment.file.filename.to_s,
|
||||
content: anything,
|
||||
files: [{
|
||||
filename: attachment.file.filename.to_s,
|
||||
content: anything,
|
||||
title: attachment.file.filename.to_s
|
||||
}],
|
||||
channel_id: hook.reference_id,
|
||||
thread_ts: conversation.identifier,
|
||||
initial_comment: 'Attached File!'
|
||||
).and_raise(Slack::Web::Api::Errors::SlackError.new('File upload failed'))
|
||||
|
||||
expect(Rails.logger).to receive(:error).with('Failed to upload file avatar.png: File upload failed')
|
||||
expect(Rails.logger).to receive(:error).with('Failed to upload files: File upload failed')
|
||||
|
||||
message.save!
|
||||
|
||||
|
||||
Reference in New Issue
Block a user