flowdock 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 24e1cb19dbf59f9a6e46d8bd350acba328186ceb
4
- data.tar.gz: 1e4a0a89662cf13e4f7f2d494450b356942bb6e6
3
+ metadata.gz: f1b21f2284092dd5c694e22637cd1f00bb85df48
4
+ data.tar.gz: ef80fc3c8718925b0efdcb7e2dadb6a6da8ffc85
5
5
  SHA512:
6
- metadata.gz: 892e801cc9d9fa275bad3809f7bdf69fd90060b9f27701c9ffc50fb33c798a688c8d08a6c976439f1aa9a04f94e5780fbaa6deb38eee95603172dbeccfa64485
7
- data.tar.gz: ae229181dfcebb6c491d42d80181e12dc32a8e96b8122bb58e8f34c744d0ffc5253fefdee099b57890b0d8e5a9763661840b415973f0d43357b4104d8c03bd39
6
+ metadata.gz: 38b75fa2a1bbeb1e912507c5672dec632601a8790e354029c00978b7e1564c678fb991b117cfcd62e3aa1d3ddec37c1f93f5101d8ae6abe302eaee1b4bf4cf23
7
+ data.tar.gz: a3f11ccf01ea48600d1208dbef5709ebc68fe8e3d36844d300f0f0e9adc7d984dc43da0d6686d2f826d3c260d2c2cc8062d7705b42bec0a4316d924ef06d0fb8
data/README.md CHANGED
@@ -21,9 +21,65 @@ If you're using JRuby, you'll also need to install jruby-openssl gem.
21
21
 
22
22
  ## Usage
23
23
 
24
- To post content to Chat or Team Inbox, you need to use the target flow's API token. They can be found from [tokens page](https://www.flowdock.com/account/tokens).
24
+ To post content to Chat or Team Inbox using `Flowdock::Flow`, you need to use the target flow's API token.
25
25
 
26
- ### Posting to Chat
26
+ Alternatively you can use your personal api token and the `Flowdock::Client`.
27
+
28
+ All tokens can be found in [tokens page](https://www.flowdock.com/account/tokens).
29
+
30
+ ### REST API
31
+
32
+ To create an api client you need your personal api token:
33
+
34
+ ```ruby
35
+ require 'rubygems'
36
+ require 'flowdock'
37
+
38
+ # Create a client that uses you api token to authenticate
39
+ client = Flowdock::Client.new(api_token: '__MY_PERSONAL_API_TOKEN__')
40
+ ```
41
+
42
+ #### Posting to Chat
43
+
44
+ To send a chat message or comment, you can use the client.chat_message:
45
+
46
+ ```ruby
47
+ flow_id = 'acdcabbacd0123456789'
48
+
49
+ # Send a simple chat message
50
+ client.chat_message(flow: flow_id, content: "I'm sending a message!", tags: ['foo', 'bar'])
51
+
52
+ # Send a comment to message 1234
53
+ client.chat_message(flow: flow_id, content: "Now I'm commenting!", message: 1234)
54
+ ```
55
+
56
+ Both methods return the created message as a hash.
57
+
58
+ #### Arbitary api access
59
+
60
+ You can use the client to access api in other ways too. See [REST API documentation](http://www.flowdock.com/api/rest) for all the resources.
61
+
62
+ ```ruby
63
+
64
+ # Fetch all my flows
65
+ flows = client.get('/flows')
66
+
67
+ # Update a flow's name
68
+ client.put('/flows/acme/my_flow', name: 'Your flow')
69
+
70
+ # Delete a message
71
+ client.delete('/flows/acme/my_flow/messages/12345')
72
+
73
+ # Create an invitation
74
+ client.post('/flows/acme/my_flow/invitations', email: 'user@example.com', message: "I'm inviting you to our flow using api.")
75
+
76
+ ```
77
+
78
+ ### Push api
79
+
80
+ To use the push api, you need a flow token:
81
+
82
+ #### Posting to Chat
27
83
 
28
84
  ```ruby
29
85
  require 'rubygems'
@@ -36,7 +92,7 @@ flow = Flowdock::Flow.new(:api_token => "__FLOW_TOKEN__", :external_user_name =>
36
92
  flow.push_to_chat(:content => "Hello!", :tags => ["cool", "stuff"])
37
93
  ```
38
94
 
39
- ### Posting to Team Inbox
95
+ #### Posting to Team Inbox
40
96
 
41
97
  ```ruby
42
98
  # create a new Flow object with target flow's api token and sender information for Team Inbox posting
@@ -49,7 +105,7 @@ flow.push_to_team_inbox(:subject => "Greetings from Flowdock API Gem!",
49
105
  :tags => ["cool", "stuff"], :link => "http://www.flowdock.com/")
50
106
  ```
51
107
 
52
- ### Posting to multiple flows
108
+ #### Posting to multiple flows
53
109
 
54
110
  ```ruby
55
111
  require 'rubygems'
@@ -63,7 +119,7 @@ flow = Flowdock::Flow.new(:api_token => ["__FLOW_TOKEN__", "__ANOTHER_FLOW_TOKEN
63
119
 
64
120
  ## API methods
65
121
 
66
- * Flow methods
122
+ * `Flowdock::Flow` methods
67
123
 
68
124
  `push_to_team_inbox` - Send message to Team Inbox. See [API documentation](http://www.flowdock.com/api/team-inbox) for details.
69
125
 
@@ -71,6 +127,11 @@ flow = Flowdock::Flow.new(:api_token => ["__FLOW_TOKEN__", "__ANOTHER_FLOW_TOKEN
71
127
 
72
128
  `send_message(params)` - Deprecated. Please use `push_to_team_inbox` instead.
73
129
 
130
+ * `Flowdock::Client` methods
131
+
132
+ `chat_message` - Send message to Chat.
133
+
134
+ `post`, `get`, `put`, `delete` - Send arbitary api calls. First parameter is the path, second is data. See [REST API documentation](http://www.flowdock.com/api/rest).
74
135
 
75
136
  ## Deployment notifications
76
137
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.0
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: flowdock 0.4.0 ruby lib
5
+ # stub: flowdock 0.5.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "flowdock"
9
- s.version = "0.4.0"
9
+ s.version = "0.5.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Antti Pitk\u{e4}nen"]
14
- s.date = "2014-04-10"
14
+ s.date = "2014-09-23"
15
15
  s.email = "team@flowdock.com"
16
16
  s.extra_rdoc_files = [
17
17
  "LICENSE",
@@ -5,11 +5,29 @@ require 'multi_json'
5
5
  module Flowdock
6
6
  FLOWDOCK_API_URL = "https://api.flowdock.com/v1"
7
7
 
8
+ class InvalidParameterError < StandardError; end
9
+ class ApiError < StandardError; end
10
+
11
+ module Helpers
12
+ def blank?(var)
13
+ var.nil? || var.respond_to?(:length) && var.length == 0
14
+ end
15
+
16
+ def handle_response(resp)
17
+ json = MultiJson.decode(resp.body || '{}')
18
+ unless resp.code >= 200 && resp.code < 300
19
+ errors = json["errors"].map {|k,v| "#{k}: #{v.join(',')}"}.join("\n") unless json["errors"].nil?
20
+ raise ApiError, "Flowdock API returned error:\nStatus: #{resp.code}\n Message: #{json["message"]}\n Errors:\n#{errors}"
21
+ end
22
+ json
23
+ rescue MultiJson::DecodeError
24
+ raise ApiError, "Flowdock API returned error:\nStatus: #{resp.code}\nBody: #{resp.body}"
25
+ end
26
+ end
27
+
8
28
  class Flow
9
29
  include HTTParty
10
- class InvalidParameterError < StandardError; end
11
- class ApiError < StandardError; end
12
-
30
+ include Helpers
13
31
  attr_reader :api_token, :source, :project, :from, :external_user_name
14
32
 
15
33
  # Required options keys: :api_token
@@ -77,12 +95,16 @@ module Flowdock
77
95
 
78
96
  tags = (params[:tags].kind_of?(Array)) ? params[:tags] : []
79
97
  tags.reject! { |tag| !tag.kind_of?(String) || blank?(tag) }
98
+ thread_id = params[:thread_id]
99
+ message_id = params[:message_id] || params[:message]
80
100
 
81
101
  params = {
82
102
  :content => params[:content],
83
103
  :external_user_name => @external_user_name
84
104
  }
85
105
  params[:tags] = tags.join(",") if tags.size > 0
106
+ params[:thread_id] = thread_id if thread_id
107
+ params[:message_id] = message_id if message_id
86
108
 
87
109
  # Send the request
88
110
  resp = self.class.post(get_flowdock_api_url("messages/chat"), :body => params)
@@ -98,25 +120,62 @@ module Flowdock
98
120
 
99
121
  private
100
122
 
101
- def blank?(var)
102
- var.nil? || var.respond_to?(:length) && var.length == 0
123
+ def get_flowdock_api_url(path)
124
+ "#{FLOWDOCK_API_URL}/#{path}/#{@api_token}"
103
125
  end
104
126
 
105
- def handle_response(resp)
106
- unless resp.code == 200
107
- begin
108
- # should have JSON response
109
- json = MultiJson.decode(resp.body)
110
- errors = json["errors"].map {|k,v| "#{k}: #{v.join(',')}"}.join("\n") unless json["errors"].nil?
111
- raise ApiError, "Flowdock API returned error:\nStatus: #{resp.code}\n Message: #{json["message"]}\n Errors:\n#{errors}"
112
- rescue MultiJson::DecodeError
113
- raise ApiError, "Flowdock API returned error:\nStatus: #{resp.code}\nBody: #{resp.body}"
114
- end
115
- end
127
+ end
128
+
129
+ class Client
130
+ include HTTParty
131
+ include Helpers
132
+ attr_reader :api_token
133
+ def initialize(options = {})
134
+ @api_token = options[:api_token]
135
+ raise InvalidParameterError, "Client must have :api_token attribute" if blank?(@api_token)
116
136
  end
117
137
 
118
- def get_flowdock_api_url(path)
119
- "#{FLOWDOCK_API_URL}/#{path}/#{@api_token}"
138
+ def chat_message(params)
139
+ raise InvalidParameterError, "Message must have :content" if blank?(params[:content])
140
+ raise InvalidParameterError, "Message must have :flow" if blank?(params[:flow])
141
+ params = params.clone
142
+ tags = (params[:tags].kind_of?(Array)) ? params[:tags] : []
143
+ params[:message] = params.delete(:message_id) if params[:message_id]
144
+ tags.reject! { |tag| !tag.kind_of?(String) || blank?(tag) }
145
+ event = if params[:message] then 'comment' else 'message' end
146
+ post(event + 's', params.merge(tags: tags, event: event))
147
+ end
148
+
149
+ def post(path, data = {})
150
+ resp = self.class.post(api_url(path), :body => MultiJson.dump(data), :basic_auth => {:username => @api_token, :password => ''}, :headers => headers)
151
+ handle_response(resp)
152
+ end
153
+
154
+ def get(path, data = {})
155
+ resp = self.class.get(api_url(path), :query => data, :basic_auth => {:username => @api_token, :password => ''}, :headers => headers)
156
+ handle_response(resp)
157
+ end
158
+
159
+ def put(path, data = {})
160
+ resp = self.class.put(api_url(path), :body => MultiJson.dump(data), :basic_auth => {:username => @api_token, :password => ''}, :headers => headers)
161
+ handle_response(resp)
162
+ end
163
+
164
+ def delete(path)
165
+ resp = self.class.delete(api_url(path), :basic_auth => {:username => @api_token, :password => ''}, :headers => headers)
166
+ handle_response(resp)
167
+ end
168
+
169
+ private
170
+
171
+ def api_url(path)
172
+ File.join(FLOWDOCK_API_URL, path)
173
+ end
174
+
175
+ def headers
176
+ {"Content-Type" => "application/json", "Accept" => "application/json"}
120
177
  end
121
178
  end
179
+
180
+
122
181
  end
@@ -11,7 +11,7 @@ describe Flowdock do
11
11
  it "should fail without token" do
12
12
  lambda {
13
13
  @flow = Flowdock::Flow.new(:api_token => "")
14
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
14
+ }.should raise_error(Flowdock::InvalidParameterError)
15
15
  end
16
16
 
17
17
  it "should succeed with array of tokens" do
@@ -36,46 +36,46 @@ describe Flowdock do
36
36
  lambda {
37
37
  @flow = Flowdock::Flow.new(@flow_attributes.merge(:source => ""))
38
38
  @flow.push_to_team_inbox(@valid_attributes)
39
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
39
+ }.should raise_error(Flowdock::InvalidParameterError)
40
40
  end
41
41
 
42
42
  it "should not send when source is not alphanumeric" do
43
43
  lambda {
44
44
  @flow = Flowdock::Flow.new(@flow_attributes.merge(:source => "$foobar"))
45
45
  @flow.push_to_team_inbox(@valid_attributes)
46
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
46
+ }.should raise_error(Flowdock::InvalidParameterError)
47
47
  end
48
48
 
49
49
  it "should not send when project is not alphanumeric" do
50
50
  lambda {
51
51
  @flow = Flowdock::Flow.new(:api_token => "test", :source => "myapp", :project => "$foobar")
52
52
  @flow.push_to_team_inbox(@valid_attributes)
53
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
53
+ }.should raise_error(Flowdock::InvalidParameterError)
54
54
  end
55
55
 
56
56
  it "should not send without sender information" do
57
57
  lambda {
58
58
  @flow = Flowdock::Flow.new(@flow_attributes.merge(:from => nil))
59
59
  @flow.push_to_team_inbox(@valid_attributes)
60
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
60
+ }.should raise_error(Flowdock::InvalidParameterError)
61
61
  end
62
62
 
63
63
  it "should not send without subject" do
64
64
  lambda {
65
65
  @flow.push_to_team_inbox(@valid_attributes.merge(:subject => ""))
66
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
66
+ }.should raise_error(Flowdock::InvalidParameterError)
67
67
  end
68
68
 
69
69
  it "should not send without content" do
70
70
  lambda {
71
71
  @flow.push_to_team_inbox(@valid_attributes.merge(:content => ""))
72
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
72
+ }.should raise_error(Flowdock::InvalidParameterError)
73
73
  end
74
74
 
75
75
  it "should send without reply_to address" do
76
76
  lambda {
77
77
  @flow.push_to_team_inbox(@valid_attributes.merge(:reply_to => ""))
78
- }.should_not raise_error(Flowdock::Flow::InvalidParameterError)
78
+ }.should_not raise_error(Flowdock::InvalidParameterError)
79
79
  end
80
80
 
81
81
  it "should succeed with correct token, source and sender information" do
@@ -221,7 +221,7 @@ describe Flowdock do
221
221
  to_return(:body => "Internal Server Error", :status => 500)
222
222
 
223
223
  @flow.push_to_team_inbox(:subject => "Hello World", :content => @example_content).should be_false
224
- }.should raise_error(Flowdock::Flow::ApiError)
224
+ }.should raise_error(Flowdock::ApiError)
225
225
  end
226
226
  end
227
227
 
@@ -235,19 +235,19 @@ describe Flowdock do
235
235
  it "should not send without content" do
236
236
  lambda {
237
237
  @flow.push_to_chat(@valid_parameters.merge(:content => ""))
238
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
238
+ }.should raise_error(Flowdock::InvalidParameterError)
239
239
  end
240
240
 
241
241
  it "should not send without external_user_name" do
242
242
  lambda {
243
243
  @flow.push_to_chat(@valid_parameters.merge(:external_user_name => ""))
244
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
244
+ }.should raise_error(Flowdock::InvalidParameterError)
245
245
  end
246
246
 
247
247
  it "should not send with invalid external_user_name" do
248
248
  lambda {
249
249
  @flow.push_to_chat(@valid_parameters.merge(:external_user_name => "foo bar"))
250
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
250
+ }.should raise_error(Flowdock::InvalidParameterError)
251
251
  end
252
252
 
253
253
  it "should send with valid parameters and return true" do
@@ -290,7 +290,29 @@ describe Flowdock do
290
290
  :status => 400)
291
291
 
292
292
  @flow.push_to_chat(@valid_parameters).should be_false
293
- }.should raise_error(Flowdock::Flow::ApiError)
293
+ }.should raise_error(Flowdock::ApiError)
294
+ end
295
+
296
+ it "should send supplied message to create comments" do
297
+ lambda {
298
+ stub_request(:post, push_to_chat_url(@token)).
299
+ with(:body => /message_id=12345/).
300
+ to_return(:body => "", :status => 200)
301
+
302
+ @flow = Flowdock::Flow.new(:api_token => @token, :external_user_name => "foobar")
303
+ @flow.push_to_chat(@valid_parameters.merge(:message_id => 12345))
304
+ }.should_not raise_error
305
+ end
306
+
307
+ it "should send supplied thread_id to post to threads" do
308
+ lambda {
309
+ stub_request(:post, push_to_chat_url(@token)).
310
+ with(:body => /thread_id=acdcabbacd/).
311
+ to_return(:body => "", :status => 200)
312
+
313
+ @flow = Flowdock::Flow.new(:api_token => @token, :external_user_name => "foobar")
314
+ @flow.push_to_chat(@valid_parameters.merge(:thread_id => 'acdcabbacd'))
315
+ }.should_not raise_error
294
316
  end
295
317
  end
296
318
 
@@ -310,3 +332,104 @@ describe Flowdock do
310
332
  end
311
333
  end
312
334
  end
335
+
336
+ describe Flowdock::Client do
337
+
338
+ let(:token) { SecureRandom.hex(8) }
339
+ let(:client) { Flowdock::Client.new(api_token: token) }
340
+
341
+ describe 'initializing' do
342
+
343
+ it 'should initialize with access token' do
344
+ expect {
345
+ client = Flowdock::Client.new(api_token: token)
346
+ expect(client.api_token).to equal(token)
347
+ }.not_to raise_error
348
+ end
349
+ it 'should raise error if initialized without access token' do
350
+ expect {
351
+ client = Flowdock::Client.new(api_token: nil)
352
+ }.to raise_error(Flowdock::InvalidParameterError)
353
+ end
354
+ end
355
+
356
+ describe 'posting to chat' do
357
+
358
+ let(:flow) { SecureRandom.hex(8) }
359
+
360
+ it 'posts to /messages' do
361
+ expect {
362
+ stub_request(:post, "https://#{token}:@api.flowdock.com/v1/messages").
363
+ with(:body => MultiJson.dump(flow: flow, content: "foobar", tags: [], event: "message"), :headers => {"Accept" => "application/json", "Content-Type" => "application/json"}).
364
+ to_return(:status => 201, :body => '{"id":123}', :headers => {"Content-Type" => "application/json"})
365
+ res = client.chat_message(flow: flow, content: 'foobar')
366
+ expect(res).to eq({"id" => 123})
367
+ }.not_to raise_error
368
+ end
369
+ it 'posts to /comments' do
370
+ expect {
371
+ stub_request(:post, "https://#{token}:@api.flowdock.com/v1/comments").
372
+ with(:body => MultiJson.dump(flow: flow, content: "foobar", message: 12345, tags: [], event: "comment"), :headers => {"Accept" => "application/json", "Content-Type" => "application/json"}).
373
+ to_return(:status => 201, :body => '{"id":1234}', :headers => {"Content-Type" => "application/json"})
374
+ res = client.chat_message(flow: flow, content: 'foobar', message: 12345)
375
+ expect(res).to eq({"id" => 1234})
376
+ }.not_to raise_error
377
+ end
378
+ it 'raises without flow' do
379
+ expect {
380
+ client.chat_message(content: 'foobar')
381
+ }.to raise_error(Flowdock::InvalidParameterError)
382
+ end
383
+ it 'raises without content' do
384
+ expect {
385
+ client.chat_message(flow: flow)
386
+ }.to raise_error(Flowdock::InvalidParameterError)
387
+ end
388
+ it 'handles error responses' do
389
+ expect {
390
+ stub_request(:post, "https://#{token}:@api.flowdock.com/v1/messages").
391
+ to_return(:body => '{"message":"Validation error","errors":{"content":["can\'t be blank"],"external_user_name":["should not contain whitespace"]}}',
392
+ :status => 400)
393
+ client.chat_message(flow: flow, content: 'foobar')
394
+ }.to raise_error(Flowdock::ApiError)
395
+ end
396
+ end
397
+
398
+ describe 'GET' do
399
+ it 'does abstract get with params' do
400
+ stub_request(:get, "https://#{token}:@api.flowdock.com/v1/some_path?sort_by=date").
401
+ with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json'}).
402
+ to_return(:status => 200, :body => '{"id": 123}', :headers => {"Content-Type" => "application/json"})
403
+ expect(client.get('/some_path', {sort_by: 'date'})).to eq({"id" => 123})
404
+ end
405
+ end
406
+
407
+ describe 'POST' do
408
+ it 'does abstract post with body' do
409
+ stub_request(:post, "https://#{token}:@api.flowdock.com/v1/other_path").
410
+ with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json'}, :body => MultiJson.dump(name: 'foobar')).
411
+ to_return(:status => 200, :body => '{"id": 123,"name": "foobar"}', :headers => {"Content-Type" => "application/json"})
412
+ expect(client.post('other_path', {name: 'foobar'})).to eq({"id" => 123, "name" => "foobar"})
413
+ end
414
+
415
+ end
416
+
417
+ describe 'PUT' do
418
+ it 'does abstract put with body' do
419
+ stub_request(:put, "https://#{token}:@api.flowdock.com/v1/other_path").
420
+ with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json'}, :body => MultiJson.dump(name: 'foobar')).
421
+ to_return(:status => 200, :body => '{"id": 123,"name": "foobar"}', :headers => {"Content-Type" => "application/json"})
422
+ expect(client.put('other_path', {name: 'foobar'})).to eq({"id" => 123, "name" => "foobar"})
423
+ end
424
+ end
425
+
426
+ describe 'DELETE' do
427
+ it 'does abstract delete with params' do
428
+ stub_request(:delete, "https://#{token}:@api.flowdock.com/v1/some_path").
429
+ with(:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json'}).
430
+ to_return(:status => 200, :body => '', :headers => {"Content-Type" => "application/json"})
431
+ expect(client.delete('/some_path')).to eq({})
432
+ end
433
+ end
434
+
435
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flowdock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antti Pitkänen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-10 00:00:00.000000000 Z
11
+ date: 2014-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty