Browse files

New API update: support Chat posting and JSON responses. Deprecated o…

…ld API stuff.
  • Loading branch information...
1 parent df458ad commit 9e2e498c4fcbec031cfa135725b4548caf0f704a Antti Pitkanen committed Feb 17, 2012
Showing with 254 additions and 72 deletions.
  1. +1 −0 Gemfile
  2. +35 −10 README.md
  3. +61 −15 lib/flowdock.rb
  4. +1 −1 lib/flowdock/capistrano.rb
  5. +156 −46 spec/flowdock_spec.rb
View
1 Gemfile
@@ -2,6 +2,7 @@ source "http://rubygems.org"
# Add dependencies required to use your gem here.
gem "httparty", "~> 0.7"
+gem "multi_json"
# Add dependencies to develop your gem here.
# Include everything needed to run rake, tests, features, etc.
View
45 README.md
@@ -1,8 +1,6 @@
# Flowdock
-Ruby Gem for using the Flowdock API.
-
-http://www.flowdock.com/api
+Ruby Gem for using the Flowdock Push API. See [Push API documentation](http://www.flowdock.com/api/push) for details.
## Build Status
@@ -13,33 +11,60 @@ flowdock gem is tested on Ruby 1.8.7, 1.9.3 and JRuby.
## Requirements
* HTTParty
+* MultiJson
## Installing
gem install flowdock
If you're using JRuby, you'll also need to install jruby-openssl gem.
-## Example
+## Posting to Chat
require 'rubygems'
require 'flowdock'
- # create a new Flow object with target flow's api token and sender information
+ # create a new Flow object with target flow's api token and external user name (enough for posting to Chat)
+ flow = Flowdock::Flow.new(:api_token => "56188e2003e370c6efa9711988f7bf02", :external_user_name => "John")
+
+ # send message to Chat
+ flow.push_to_chat(:content => "Hello!", :tags => ["cool", "stuff"])
+
+## Posting to Team Inbox
+
+ # create a new Flow object with target flow's api token and sender information for Team Inbox posting
flow = Flowdock::Flow.new(:api_token => "56188e2003e370c6efa9711988f7bf02",
- :source => "myapp",
- :from => {:name => "John Doe", :address => "john.doe@example.com"})
+ :source => "myapp", :from => {:name => "John Doe", :address => "john.doe@example.com"})
- # send message to the flow
- flow.send_message(:subject => "Greetings from Flowdock API Gem!",
+ # send message to Team Inbox
+ flow.push_to_team_inbox(:subject => "Greetings from Flowdock API Gem!",
+ :content => "<h2>It works!</h2><p>Now you can start developing your awesome application for Flowdock.</p>",
+ :tags => ["cool", "stuff"], :link => "http://www.flowdock.com/")
+
+
+## Posting to both
+
+ # create a new Flow object with target flow's api token, external user name and sender information
+ flow = Flowdock::Flow.new(:api_token => "56188e2003e370c6efa9711988f7bf02", :external_user_name => "John",
+ :source => "myapp", :from => {:name => "John Doe", :address => "john.doe@example.com"})
+
+ # send message to Chat
+ flow.push_to_chat(:content => "Hello!", :tags => ["cool", "stuff"])
+
+ # send message to Team Inbox
+ flow.push_to_team_inbox(:subject => "Greetings from Flowdock API Gem!",
:content => "<h2>It works!</h2><p>Now you can start developing your awesome application for Flowdock.</p>",
:tags => ["cool", "stuff"], :link => "http://www.flowdock.com/")
## API methods
* Flow methods
- *send_message(params)* - Send message to flow. See [API documentation](http://www.flowdock.com/help/api_documentation) for details.
+ *push_to_team_inbox* - Send message to Team Inbox. See [API documentation](http://www.flowdock.com/api/team-inbox) for details.
+
+ *push_to_chat* - Send message to Chat. See [API documentation](http://www.flowdock.com/api/chat) for details.
+
+ *send_message(params)* - Deprecated. Please use *push_to_team_inbox* instead.
## Capistrano deployment task
View
76 lib/flowdock.rb
@@ -1,33 +1,37 @@
require 'rubygems'
require 'httparty'
+require 'multi_json'
module Flowdock
- FLOWDOCK_API_URL = "https://api.flowdock.com/v1/messages/influx"
+ FLOWDOCK_API_URL = "https://api.flowdock.com/v1"
class Flow
include HTTParty
class InvalidParameterError < StandardError; end
class ApiError < StandardError; end
- # Required options keys: :api_token, :source, :from => { :name, :address }
+ # Required options keys: :api_token, optional keys: :external_user_name, :source, :from => { :name, :address }
def initialize(options = {})
@api_token = options[:api_token]
raise InvalidParameterError, "Flow must have :api_token attribute" if blank?(@api_token)
- @source = options[:source]
- raise InvalidParameterError, "Flow must have valid :source attribute, only alphanumeric characters and underscores can be used" if blank?(@source) || !@source.match(/^[a-z0-9\-_ ]+$/i)
-
- @project = options[:project]
- raise InvalidParameterError, "Optional attribute :project can only contain alphanumeric characters and underscores" if !blank?(@project) && !@project.match(/^[a-z0-9\-_ ]+$/i)
-
+ @source = options[:source] || nil
+ @project = options[:project] || nil
@from = options[:from] || {}
+ @external_user_name = options[:external_user_name] || nil
end
- def send_message(params)
+ def push_to_team_inbox(params)
+ @source ||= params[:source]
+ raise InvalidParameterError, "Message must have valid :source attribute, only alphanumeric characters and underscores can be used" if blank?(@source) || !@source.match(/^[a-z0-9\-_ ]+$/i)
+
+ @project ||= params[:project]
+ raise InvalidParameterError, "Optional attribute :project can only contain alphanumeric characters and underscores" if !blank?(@project) && !@project.match(/^[a-z0-9\-_ ]+$/i)
+
raise InvalidParameterError, "Message must have both :subject and :content" if blank?(params[:subject]) || blank?(params[:content])
from = (params[:from].kind_of?(Hash)) ? params[:from] : @from
- raise InvalidParameterError, "Flow's :from attribute must have :address attribute" if blank?(from[:address])
+ raise InvalidParameterError, "Message's :from attribute must have :address attribute" if blank?(from[:address])
tags = (params[:tags].kind_of?(Array)) ? params[:tags] : []
tags.reject! { |tag| !tag.kind_of?(String) || blank?(tag) }
@@ -37,29 +41,71 @@ def send_message(params)
params = {
:source => @source,
:format => 'html', # currently only supported format
- :from_name => from[:name],
:from_address => from[:address],
:subject => params[:subject],
:content => params[:content],
}
+ params[:from_name] = from[:name] unless blank?(from[:name])
params[:tags] = tags.join(",") if tags.size > 0
params[:project] = @project unless blank?(@project)
params[:link] = link unless blank?(link)
# Send the request
- resp = self.class.post(get_flowdock_api_url, :body => params)
- raise ApiError, "Flowdock API returned error: Status: #{resp.code} Body: #{resp.body}" unless resp.code == 200
+ resp = self.class.post(get_flowdock_api_url("messages/team_inbox"), :body => params)
+ handle_response(resp)
+ true
+ end
+
+ def push_to_chat(params)
+ raise InvalidParameterError, "Message must have :content" if blank?(params[:content])
+
+ @external_user_name ||= params[:external_user_name]
+ if blank?(@external_user_name) || @external_user_name.match(/^[\S]+$/).nil? || @external_user_name.length > 16
+ raise InvalidParameterError, "Message must have :external_user_name that has no whitespace and maximum of 16 characters"
+ end
+
+ tags = (params[:tags].kind_of?(Array)) ? params[:tags] : []
+ tags.reject! { |tag| !tag.kind_of?(String) || blank?(tag) }
+
+ params = {
+ :content => params[:content],
+ :external_user_name => @external_user_name
+ }
+ params[:tags] = tags.join(",") if tags.size > 0
+
+ # Send the request
+ resp = self.class.post(get_flowdock_api_url("messages/chat"), :body => params)
+ handle_response(resp)
true
end
+ # <b>DEPRECATED:</b> Please use <tt>useful</tt> instead.
+ def send_message(params)
+ warn "[DEPRECATION] `send_message` is deprecated. Please use `push_to_team_inbox` instead."
+ push_to_team_inbox(params)
+ end
+
private
def blank?(var)
var.nil? || var.respond_to?(:length) && var.length == 0
end
- def get_flowdock_api_url
- "#{FLOWDOCK_API_URL}/#{@api_token}"
+ def handle_response(resp)
+ unless resp.code == 200
+ begin
+ # should have JSON response
+ json = MultiJson.decode(resp.body)
+ errors = json["errors"].map {|k,v| "#{k}: #{v.join(',')}"}.join("\n") unless json["errors"].nil?
+ raise ApiError, "Flowdock API returned error:\nStatus: #{resp.code}\n Message: #{json["message"]}\n Errors:\n#{errors}"
+ rescue MultiJson::DecodeError
+ raise ApiError, "Flowdock API returned error:\nStatus: #{resp.code}\nBody: #{resp.body}"
+ end
+ end
+ end
+
+ def get_flowdock_api_url(path)
+ "#{FLOWDOCK_API_URL}/#{path}/#{@api_token}"
end
end
end
View
2 lib/flowdock/capistrano.rb
@@ -42,7 +42,7 @@
task :notify_deploy_finished do
# send message to the flow
begin
- flowdock_api.send_message(:format => "html",
+ flowdock_api.push_to_team_inbox(:format => "html",
:subject => "#{flowdock_project_name} deployed with branch #{branch} on ##{rails_env}",
:content => notification_message,
:tags => ["deploy", "#{rails_env}"] | flowdock_deploy_tags)
View
202 spec/flowdock_spec.rb
@@ -2,88 +2,132 @@
describe Flowdock do
describe "with initializing flow" do
- it "should succeed with correct token and source" do
+ it "should succeed with correct token" do
lambda {
- @flow = Flowdock::Flow.new(:api_token => "test", :source => "myapp")
+ @flow = Flowdock::Flow.new(:api_token => "test")
}.should_not raise_error
end
- it "should succeed with correct token, source and sender information" do
+ it "should fail without token" do
lambda {
- @flow = Flowdock::Flow.new(:api_token => "test", :source => "myapp",
- :from => {:name => "test", :address => "invalid@nodeta.fi"})
- }.should_not raise_error
+ @flow = Flowdock::Flow.new(:api_token => "")
+ }.should raise_error(Flowdock::Flow::InvalidParameterError)
end
+ end
- it "should succeed with correct token, sender information, source and project" do
- lambda {
- @flow = Flowdock::Flow.new(:api_token => "test", :source => "myapp", :project => "myproject",
- :from => {:name => "test", :address => "invalid@nodeta.fi"})
- }.should_not raise_error
+ describe "with sending Team Inbox messages" do
+ before(:each) do
+ @token = "test"
+ @flow_attributes = {:api_token => @token, :source => "myapp", :project => "myproject",
+ :from => {:name => "Eric Example", :address => "eric@example.com"}}
+ @flow = Flowdock::Flow.new(@flow_attributes)
+ @example_content = "<h1>Hello</h1>\n<p>Let's rock and roll!</p>"
+ @valid_attributes = {:subject => "Hello World", :content => @example_content,
+ :link => "http://www.flowdock.com/", :tags => ["cool", "stuff"]}
end
- it "should succeed without the optional from-name parameter" do
+ it "should not send without source" do
lambda {
- @flow = Flowdock::Flow.new(:api_token => "test", :source => "myapp",
- :from => {:address => "invalid@nodeta.fi"})
- }.should_not raise_error
+ @flow = Flowdock::Flow.new(@flow_attributes.merge(:source => ""))
+ @flow.push_to_team_inbox(@valid_attributes)
+ }.should raise_error(Flowdock::Flow::InvalidParameterError)
end
- it "should fail without token" do
+ it "should not send when source is not alphanumeric" do
lambda {
- @flow = Flowdock::Flow.new(:api_token => "", :source => "myapp")
+ @flow = Flowdock::Flow.new(@flow_attributes.merge(:source => "$foobar"))
+ @flow.push_to_team_inbox(@valid_attributes)
}.should raise_error(Flowdock::Flow::InvalidParameterError)
end
- it "should fail without source" do
+ it "should not send when project is not alphanumeric" do
lambda {
- @flow = Flowdock::Flow.new(:api_token => "test", :source => "")
+ @flow = Flowdock::Flow.new(:api_token => "test", :source => "myapp", :project => "$foobar")
+ @flow.push_to_team_inbox(@valid_attributes)
}.should raise_error(Flowdock::Flow::InvalidParameterError)
end
- it "should fail when source is not alphanumeric" do
+ it "should not send without sender information" do
lambda {
- @flow = Flowdock::Flow.new(:api_token => "test", :source => "$foobar")
+ @flow = Flowdock::Flow.new(@flow_attributes.merge(:from => nil))
+ @flow.push_to_team_inbox(@valid_attributes)
}.should raise_error(Flowdock::Flow::InvalidParameterError)
end
- it "should fail when project is not alphanumeric" do
+ it "should not send without subject" do
lambda {
- @flow = Flowdock::Flow.new(:api_token => "test", :source => "myapp", :project => "$foobar")
+ @flow.push_to_team_inbox(@valid_attributes.merge(:subject => ""))
}.should raise_error(Flowdock::Flow::InvalidParameterError)
end
- end
- describe "with sending messages" do
- before(:each) do
- @token = "test"
- @flow = Flowdock::Flow.new(:api_token => @token, :source => "myapp", :project => "myproject",
- :from => {:name => "Eric Example", :address => "eric@example.com"})
- @example_content = "<h1>Hello</h1>\n<p>Let's rock and roll!</p>"
+ it "should not send without content" do
+ lambda {
+ @flow.push_to_team_inbox(@valid_attributes.merge(:content => ""))
+ }.should raise_error(Flowdock::Flow::InvalidParameterError)
end
- it "should not send without subject" do
+ it "should succeed with correct token, source and sender information" do
lambda {
- @flow.send_message(:subject => "", :content => "Test")
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
+ stub_request(:post, push_to_team_inbox_url(@token)).
+ with(:body => {
+ :source => "myapp",
+ :format => "html",
+ :from_name => "Eric Example",
+ :from_address => "eric@example.com",
+ :subject => "Hello World",
+ :content => @example_content,
+ :tags => "cool,stuff",
+ :link => "http://www.flowdock.com/"
+ }).
+ to_return(:body => "", :status => 200)
+
+ @flow = Flowdock::Flow.new(@flow_attributes.merge(:project => ""))
+ @flow.push_to_team_inbox(@valid_attributes)
+ }.should_not raise_error
end
- it "should not send without content" do
+ it "should succeed without the optional from-name parameter" do
lambda {
- @flow.send_message(:subject => "Test", :content => "")
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
+ stub_request(:post, push_to_team_inbox_url(@token)).
+ with(:body => {
+ :source => "myapp",
+ :project => "myproject",
+ :format => "html",
+ :from_address => "eric@example.com",
+ :subject => "Hello World",
+ :content => @example_content,
+ :tags => "cool,stuff",
+ :link => "http://www.flowdock.com/"
+ }).
+ to_return(:body => "", :status => 200)
+ @flow = Flowdock::Flow.new(@flow_attributes.merge(:from => {:address => "eric@example.com"}))
+ @flow.push_to_team_inbox(@valid_attributes)
+ }.should_not raise_error
end
- it "should not send without sender information" do
- @flow = Flowdock::Flow.new(:api_token => @token, :source => "myapp")
+ it "should succeed with correct token, sender information, source and project" do
lambda {
- @flow.send_message(:subject => "Test", :content => @example_content)
- }.should raise_error(Flowdock::Flow::InvalidParameterError)
+ stub_request(:post, push_to_team_inbox_url(@token)).
+ with(:body => {
+ :source => "myapp",
+ :project => "myproject",
+ :format => "html",
+ :from_name => "Eric Example",
+ :from_address => "eric@example.com",
+ :subject => "Hello World",
+ :content => @example_content,
+ :tags => "cool,stuff",
+ :link => "http://www.flowdock.com/"
+ }).
+ to_return(:body => "", :status => 200)
+ @flow = Flowdock::Flow.new(@flow_attributes)
+ @flow.push_to_team_inbox(@valid_attributes)
+ }.should_not raise_error
end
it "should send with valid parameters and return true" do
lambda {
- stub_request(:post, "#{Flowdock::FLOWDOCK_API_URL}/#{@token}").
+ stub_request(:post, push_to_team_inbox_url(@token)).
with(:body => {
:source => "myapp",
:project => "myproject",
@@ -97,14 +141,14 @@
}).
to_return(:body => "", :status => 200)
- @flow.send_message(:subject => "Hello World", :content => @example_content,
+ @flow.push_to_team_inbox(:subject => "Hello World", :content => @example_content,
:tags => ["cool", "stuff"], :link => "http://www.flowdock.com/").should be_true
}.should_not raise_error
end
it "should allow overriding sender information per message" do
lambda {
- stub_request(:post, "#{Flowdock::FLOWDOCK_API_URL}/#{@token}").
+ stub_request(:post, push_to_team_inbox_url(@token)).
with(:body => {
:source => "myapp",
:project => "myproject",
@@ -117,14 +161,14 @@
}).
to_return(:body => "", :status => 200)
- @flow.send_message(:subject => "Hello World", :content => @example_content, :tags => ["cool", "stuff"],
+ @flow.push_to_team_inbox(:subject => "Hello World", :content => @example_content, :tags => ["cool", "stuff"],
:from => {:name => "Test", :address => "invalid@nodeta.fi"}).should be_true
}.should_not raise_error
end
it "should raise error if backend returns anything but 200 OK" do
lambda {
- stub_request(:post, "#{Flowdock::FLOWDOCK_API_URL}/#{@token}").
+ stub_request(:post, push_to_team_inbox_url(@token)).
with(:body => {
:source => "myapp",
:project => "myproject",
@@ -136,8 +180,74 @@
}).
to_return(:body => "Internal Server Error", :status => 500)
- @flow.send_message(:subject => "Hello World", :content => @example_content).should be_false
+ @flow.push_to_team_inbox(:subject => "Hello World", :content => @example_content).should be_false
}.should raise_error(Flowdock::Flow::ApiError)
end
end
+
+ describe "with sending Chat messages" do
+ before(:each) do
+ @token = "test"
+ @flow = Flowdock::Flow.new(:api_token => @token)
+ @valid_parameters = {:external_user_name => "foobar", :content => "Hello", :tags => ["cool","stuff"]}
+ end
+
+ it "should not send without content" do
+ lambda {
+ @flow.push_to_chat(@valid_parameters.merge(:content => ""))
+ }.should raise_error(Flowdock::Flow::InvalidParameterError)
+ end
+
+ it "should not send without external_user_name" do
+ lambda {
+ @flow.push_to_chat(@valid_parameters.merge(:external_user_name => ""))
+ }.should raise_error(Flowdock::Flow::InvalidParameterError)
+ end
+
+ it "should not send with invalid external_user_name" do
+ lambda {
+ @flow.push_to_chat(@valid_parameters.merge(:external_user_name => "foo bar"))
+ }.should raise_error(Flowdock::Flow::InvalidParameterError)
+ end
+
+ it "should send with valid parameters and return true" do
+ lambda {
+ stub_request(:post, push_to_chat_url(@token)).
+ with(:body => @valid_parameters.merge(:tags => "cool,stuff")).
+ to_return(:body => "", :status => 200)
+
+ @flow.push_to_chat(@valid_parameters).should be_true
+ }.should_not raise_error
+ end
+
+ it "should accept external_user_name in init" do
+ lambda {
+ stub_request(:post, push_to_chat_url(@token)).
+ with(:body => @valid_parameters.merge(:tags => "cool,stuff")).
+ to_return(:body => "", :status => 200)
+
+ @flow = Flowdock::Flow.new(:api_token => @token, :external_user_name => "foobar")
+ @flow.push_to_chat(@valid_parameters.merge(:external_user_name => ""))
+ }.should_not raise_error
+ end
+
+ it "should raise error if backend returns anything but 200 OK" do
+ lambda {
+ stub_request(:post, push_to_chat_url(@token)).
+ with(:body => @valid_parameters.merge(:tags => "cool,stuff")).
+ to_return(:body => '{"message":"Validation error","errors":{"content":["can\'t be blank"],"external_user_name":["should not contain whitespace"]}}',
+ :status => 400)
+
+ @flow.push_to_chat(@valid_parameters).should be_false
+ }.should raise_error(Flowdock::Flow::ApiError)
+ end
+ end
+
+ def push_to_chat_url(token)
+ "#{Flowdock::FLOWDOCK_API_URL}/messages/chat/#{token}"
+ end
+
+ def push_to_team_inbox_url(token)
+ "#{Flowdock::FLOWDOCK_API_URL}/messages/team_inbox/#{token}"
+ end
end

0 comments on commit 9e2e498

Please sign in to comment.