Skip to content

Commit 1bd218d

Browse files
authored
Merge pull request #567 from dblock/fix-files-upload-v2-multiple-files
Add support for multiple files in files_upload_v2 method
2 parents fb8f307 + ebd9f0c commit 1bd218d

File tree

9 files changed

+491
-37
lines changed

9 files changed

+491
-37
lines changed

.rubocop_todo.yml

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2025-02-11 15:48:56 UTC using RuboCop version 1.26.1.
3+
# on 2025-07-19 11:04:04 UTC using RuboCop version 1.26.1.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new
@@ -47,17 +47,17 @@ Lint/RedundantCopDisableDirective:
4747
# Offense count: 13
4848
# Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
4949
Metrics/AbcSize:
50-
Max: 58
50+
Max: 67
5151

5252
# Offense count: 6
5353
# Configuration parameters: IgnoredMethods.
5454
Metrics/CyclomaticComplexity:
55-
Max: 11
55+
Max: 15
5656

57-
# Offense count: 15
57+
# Offense count: 17
5858
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
5959
Metrics/MethodLength:
60-
Max: 35
60+
Max: 45
6161

6262
# Offense count: 1
6363
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
@@ -67,12 +67,13 @@ Metrics/ParameterLists:
6767
# Offense count: 3
6868
# Configuration parameters: IgnoredMethods.
6969
Metrics/PerceivedComplexity:
70-
Max: 12
70+
Max: 17
7171

72-
# Offense count: 1
72+
# Offense count: 2
7373
# Configuration parameters: MinSize.
7474
Performance/CollectionLiteralInLoop:
7575
Exclude:
76+
- 'lib/slack/web/api/helpers/files.rb'
7677
- 'spec/slack/web/api/endpoints/custom/files_spec.rb'
7778

7879
# Offense count: 1
@@ -93,20 +94,20 @@ Performance/StringInclude:
9394
Exclude:
9495
- 'lib/tasks/web.rake'
9596

96-
# Offense count: 9
97+
# Offense count: 10
9798
# This cop supports safe auto-correction (--auto-correct).
9899
RSpec/ContextMethod:
99100
Exclude:
100101
- 'spec/slack/messages/formatting_spec.rb'
101102
- 'spec/slack/web/api/mixins/users_spec.rb'
102103

103-
# Offense count: 84
104+
# Offense count: 86
104105
# Configuration parameters: Prefixes.
105106
# Prefixes: when, with, without
106107
RSpec/ContextWording:
107108
Enabled: false
108109

109-
# Offense count: 72
110+
# Offense count: 75
110111
# Configuration parameters: CountAsOne.
111112
RSpec/ExampleLength:
112113
Max: 18
@@ -117,13 +118,13 @@ RSpec/ExampleLength:
117118
RSpec/FilePath:
118119
Enabled: false
119120

120-
# Offense count: 74
121+
# Offense count: 86
121122
# Configuration parameters: .
122123
# SupportedStyles: have_received, receive
123124
RSpec/MessageSpies:
124125
EnforcedStyle: receive
125126

126-
# Offense count: 97
127+
# Offense count: 103
127128
RSpec/MultipleExpectations:
128129
Max: 5
129130

@@ -151,7 +152,7 @@ RSpec/StubbedMock:
151152
- 'spec/slack/web/api/pagination/cursor_spec.rb'
152153
- 'spec/slack/web/client_spec.rb'
153154

154-
# Offense count: 2
155+
# Offense count: 14
155156
RSpec/SubjectStub:
156157
Exclude:
157158
- 'spec/slack/web/api/mixins/conversations_spec.rb'
@@ -232,6 +233,14 @@ Style/OptionalBooleanParameter:
232233
Exclude:
233234
- 'spec/support/queue_with_timeout.rb'
234235

236+
# Offense count: 1
237+
# This cop supports safe auto-correction (--auto-correct).
238+
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods.
239+
# AllowedMethods: present?, blank?, presence, try, try!
240+
Style/SafeNavigation:
241+
Exclude:
242+
- 'lib/slack/web/api/helpers/files.rb'
243+
235244
# Offense count: 1
236245
# This cop supports unsafe auto-correction (--auto-correct-all).
237246
Style/SlicingWithRange:

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* [#559](https://github.com/slack-ruby/slack-ruby-client/pull/559): Enable name-to-id translation of non-public channels - [@eizengan](https://github.com/eizengan).
55
* [#560](https://github.com/slack-ruby/slack-ruby-client/pull/560): Name-to-id translation can supply all sensible list options - [@eizengan](https://github.com/eizengan).
66
* [#561](https://github.com/slack-ruby/slack-ruby-client/issues/563): Raise InvalidSignature when verifying a request without a signature - [@wesleyjellis](https://github.com/wesleyjellis).
7+
* [#567](https://github.com/slack-ruby/slack-ruby-client/pull/567): Add support for multiple files in `files_upload_v2` - [@dblock](https://github.com/dblock).
78
* Your contribution here.
89

910
### 2.6.0 (2025/05/24)

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ bundle exec rake
2727

2828
### Run Examples in Development
2929

30-
Sign up for Slack, create a private Slack team for yourself, then [generate an API token](https://api.slack.com/tutorials/tracks/getting-a-token) for your app and use it for some interactions.
30+
Sign up for Slack, create a private Slack team for yourself, then [generate an API token](https://api.slack.com/tutorials/tracks/getting-a-token) for your app and use it for some interactions. To get a token you will need to install the app in a workspace, and use the "User OAuth Token" from installed app settings.
3131

3232
Try running the examples in the [examples](examples) directory.
3333

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ Upload files with [sequenced API calls](https://api.slack.com/messaging/files#up
173173

174174
This library provides a helper method `files_upload_v2` that wraps the three separate API calls.
175175

176+
Upload a single file.
177+
176178
```ruby
177179
client.files_upload_v2(
178180
# required options
@@ -188,6 +190,19 @@ client.files_upload_v2(
188190
)
189191
```
190192

193+
Upload multiple files.
194+
195+
```ruby
196+
client.files_upload_v2(
197+
files: [
198+
{ filename: 'report.pdf', content: File.read('/users/me/report.pdf'), title: 'Monthly Report' },
199+
{ filename: 'data.csv', content: File.read('/users/me/data.csv'), title: 'Raw Data' }
200+
],
201+
channels: ['#general'],
202+
initial_comment: 'Here are the monthly results!'
203+
)
204+
```
205+
191206
You can use a channel ID passed as `channel_id`, a single channel as `channel`, an array of channel IDs as `channels`, or a channel name or names (prefixed with `#`) in `files_upload_v2`. Lookup by name is not supported by the Slack API and this method called invokes `conversations_list` in order to locate the channel ID. This invocation can have a cost if you have many Slack channels and is only recommended when you intend to list channels anyway.
192207

193208
Note: This library includes a `files_upload` method that uses a deprecated endpoint `files.upload` that will [no longer be supported on 3/11/2025](https://api.slack.com/methods/files.upload#markdown).

examples/files_upload_v2/files_upload_v2.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,11 @@
3535
content: SecureRandom.hex,
3636
channel_id: channel_id
3737
).files.first.permalink_public
38+
39+
client.files_upload_v2(
40+
files: [
41+
{ filename: 'files_upload_v2_to_general_first_file.txt', content: SecureRandom.hex },
42+
{ filename: 'files_upload_v2_to_general_second_file.txt', content: SecureRandom.hex }
43+
],
44+
channels: ['#general']
45+
).files.each { |file| puts file.permalink_public }

lib/slack/web/api/helpers/files.rb

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ module Files
1313
# Name of the file being uploaded.
1414
# @option params [string] :content
1515
# File contents via a POST variable.
16+
# @option params [Array<Hash>] :files
17+
# Array of file objects with :filename, :content, and optionally :title, :alt_txt, :snippet_type.
1618
# @option params [string] :alt_txt
1719
# Description of image for screen-reader.
1820
# @option params [string] :snippet_type
@@ -32,8 +34,16 @@ module Files
3234
# Never use a reply's ts value; use its parent instead.
3335
# Also make sure to provide only one channel when using 'thread_ts'.
3436
def files_upload_v2(params = {})
35-
%i[filename content].each do |param|
36-
raise ArgumentError, "Required argument :#{param} missing" if params[param].nil?
37+
files_to_upload = if params[:files] && params[:files].is_a?(Array)
38+
params[:files]
39+
else
40+
[params.slice(:filename, :content, :title, :alt_txt, :snippet_type)]
41+
end
42+
43+
files_to_upload.each_with_index do |file, index|
44+
%i[filename content].each do |param|
45+
raise ArgumentError, "Required argument :#{param} missing in file (#{index})" if file[param].nil?
46+
end
3747
end
3848

3949
channel_params = %i[channel channels channel_id].map { |param| params[param] }.compact
@@ -53,31 +63,31 @@ def files_upload_v2(params = {})
5363
complete_upload_request_params[:channel_id] = params[:channel_id]
5464
end
5565

56-
content = params[:content]
57-
title = params[:title] || params[:filename]
66+
uploaded_files = files_to_upload.map do |file|
67+
content = file[:content]
68+
title = file[:title] || file[:filename]
5869

59-
upload_url_request_params = params.slice(:filename, :alt_txt, :snippet_type)
60-
upload_url_request_params[:length] = content.bytesize
70+
upload_url_request_params = file.slice(:filename, :alt_txt, :snippet_type)
71+
upload_url_request_params[:length] = content.bytesize
6172

62-
# Get the upload url.
63-
get_upload_url_response = files_getUploadURLExternal(upload_url_request_params)
64-
upload_url = get_upload_url_response[:upload_url]
65-
file_id = get_upload_url_response[:file_id]
73+
get_upload_url_response = files_getUploadURLExternal(upload_url_request_params)
74+
upload_url = get_upload_url_response[:upload_url]
75+
file_id = get_upload_url_response[:file_id]
6676

67-
# Upload the file.
68-
::Faraday::Connection.new(upload_url, options) do |connection|
69-
connection.request :multipart
70-
connection.request :url_encoded
71-
connection.use ::Slack::Web::Faraday::Response::WrapError
72-
connection.response :logger, logger if logger
73-
connection.adapter adapter
74-
end.post do |request|
75-
request.body = content
76-
end
77+
::Faraday::Connection.new(upload_url, options) do |connection|
78+
connection.request :multipart
79+
connection.request :url_encoded
80+
connection.use ::Slack::Web::Faraday::Response::WrapError
81+
connection.response :logger, logger if logger
82+
connection.adapter adapter
83+
end.post do |request|
84+
request.body = content
85+
end
7786

78-
# Complete the upload.
79-
complete_upload_request_params[:files] = [{ id: file_id, title: title }].to_json
87+
{ id: file_id, title: title }
88+
end
8089

90+
complete_upload_request_params[:files] = uploaded_files.to_json
8191
files_completeUploadExternal(complete_upload_request_params)
8292
end
8393
end

0 commit comments

Comments
 (0)