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:
Muhsin Keloth
2025-09-24 11:31:06 +05:30
committed by GitHub
parent 68c070bcd9
commit 44fab70048
4 changed files with 52 additions and 47 deletions

View File

@@ -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!