flowdock 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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