Explorar el Código

FormConfigurable improvements

Made the BasecampAgent form configurable
Added specs
Added guard-livereload and guard-rspec
Custom values can be entered for `completable` attributes
Dominik Sander hace 10 años
padre
commit
c1f336d04e

+ 3 - 0
Gemfile

@@ -85,6 +85,9 @@ group :development do
   gem 'better_errors', '~> 1.1'
   gem 'binding_of_caller'
   gem 'quiet_assets'
+  gem 'guard'
+  gem 'guard-livereload'
+  gem 'guard-rspec'
 end
 
 group :development, :test do

+ 31 - 0
Gemfile.lock

@@ -55,6 +55,8 @@ GEM
       rails (>= 3.1)
     buftok (0.2.0)
     builder (3.2.2)
+    celluloid (0.15.2)
+      timers (~> 1.1.0)
     chronic (0.10.2)
     coderay (1.1.0)
     coffee-rails (4.0.1)
@@ -108,6 +110,9 @@ GEM
       http_parser.rb (>= 0.6.0)
     em-socksify (0.3.0)
       eventmachine (>= 1.0.0.beta.4)
+    em-websocket (0.5.1)
+      eventmachine (>= 0.12.9)
+      http_parser.rb (~> 0.6.0)
     equalizer (0.0.9)
     erector (0.10.0)
       treetop (>= 1.2.3)
@@ -134,6 +139,7 @@ GEM
     foreman (0.63.0)
       dotenv (>= 0.7)
       thor (>= 0.13.6)
+    formatador (0.2.5)
     geokit (1.8.5)
       multi_json (>= 1.3.2)
     geokit-rails (2.0.1)
@@ -150,6 +156,19 @@ GEM
       retriable (>= 1.4)
       signet (>= 0.5.0)
       uuidtools (>= 2.1.0)
+    guard (2.6.1)
+      formatador (>= 0.2.4)
+      listen (~> 2.7)
+      lumberjack (~> 1.0)
+      pry (>= 0.9.12)
+      thor (>= 0.18.1)
+    guard-livereload (2.2.0)
+      em-websocket (~> 0.5)
+      guard (~> 2.0)
+      multi_json (~> 1.8)
+    guard-rspec (4.3.1)
+      guard (~> 2.1)
+      rspec (>= 2.14, < 4.0)
     hashie (2.0.5)
     hike (1.2.3)
     hipchat (1.2.0)
@@ -178,6 +197,11 @@ GEM
       addressable (~> 2.3)
     libv8 (3.16.14.7)
     liquid (2.6.1)
+    listen (2.7.9)
+      celluloid (>= 0.15.2)
+      rb-fsevent (>= 0.9.3)
+      rb-inotify (>= 0.9)
+    lumberjack (1.0.9)
     macaddr (1.7.1)
       systemu (~> 2.6.2)
     mail (2.5.4)
@@ -262,6 +286,9 @@ GEM
       thor (>= 0.18.1, < 2.0)
     raindrops (0.13.0)
     rake (10.3.2)
+    rb-fsevent (0.9.4)
+    rb-inotify (0.9.5)
+      ffi (>= 0.5.0)
     rdoc (4.1.1)
       json (~> 1.4)
     ref (1.0.5)
@@ -350,6 +377,7 @@ GEM
     thor (0.19.1)
     thread_safe (0.3.4)
     tilt (1.4.1)
+    timers (1.1.0)
     tins (1.3.2)
     treetop (1.4.15)
       polyglot
@@ -438,6 +466,9 @@ DEPENDENCIES
   geokit (~> 1.8.4)
   geokit-rails (~> 2.0.1)
   google-api-client
+  guard
+  guard-livereload
+  guard-rspec
   hipchat (~> 1.2.0)
   httparty (~> 0.13)
   jquery-rails (~> 3.1.0)

+ 25 - 0
Guardfile

@@ -0,0 +1,25 @@
+
+guard 'livereload' do
+  watch(%r{app/views/.+\.(erb|haml|slim)$})
+  watch(%r{app/helpers/.+\.rb})
+  watch(%r{public/.+\.(css|js|html)})
+  watch(%r{config/locales/.+\.yml})
+  # Rails Assets Pipeline
+  watch(%r{(app|vendor)(/assets/\w+/(.+\.(css|js|html|png|jpg))).*}) { |m| "/assets/#{m[3]}" }
+end
+
+guard :rspec, cmd: 'bundle exec spring rspec' do
+  watch(%r{^spec/.+_spec\.rb$})
+  watch(%r{^lib/(.+)\.rb$})     { |m| "spec/lib/#{m[1]}_spec.rb" }
+  watch('spec/spec_helper.rb')  { "spec" }
+
+  # Rails example
+  watch(%r{^app/(.+)\.rb$})                           { |m| "spec/#{m[1]}_spec.rb" }
+  watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$})          { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
+  watch(%r{^app/controllers/(.+)_(controller)\.rb$})  { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
+  watch(%r{^spec/support/(.+)\.rb$})                  { "spec" }
+  watch('config/routes.rb')                           { "spec/routing" }
+  watch('app/controllers/application_controller.rb')  { "spec/controllers" }
+  watch('spec/rails_helper.rb')                       { "spec" }
+end
+

+ 6 - 1
app/assets/javascripts/components/form_configurable.js.coffee

@@ -51,9 +51,14 @@ $ ->
     $("input[role=validatable], select[role=validatable]").trigger('change')
 
     $.each $("input[role~=completable]"), (i, input) ->
-      $(input).select2
+      $(input).select2(
         data: ->
           completableDefaultOptions(input)
+      ).on("change", (e) ->
+        if e.added && e.added.id == 'manualInput'
+          $(e.currentTarget).select2("destroy")
+          $(e.currentTarget).val(e.removed.id)
+      )
 
     $("input[role~=completable]").on 'select2-open', (e) ->
       form_data = getFormData(e.currentTarget)

+ 0 - 9
app/concerns/form_configurable.rb

@@ -43,15 +43,6 @@ module FormConfigurable
         options[:roles] = [options[:roles]]
       end
 
-      if options[:roles].include?(:completable) && !self.method_defined?("complete_#{name}".to_sym)
-        # Not really sure, but method_defined? does not seem to work because we do not have the 'full' Agent class here
-        #raise ArgumentError.new("'complete_#{name}' needs to be defined to validate '#{name}'")
-      end
-
-      if options[:roles].include?(:validatable) && !self.method_defined?("validate_#{name}".to_sym)
-        #raise ArgumentError.new("'validate_#{name}' needs to be defined to validate '#{name}'")
-      end
-
       _form_configurable_fields[name] = options
     end
 

+ 22 - 13
app/models/agents/basecamp_agent.rb

@@ -1,23 +1,16 @@
 module Agents
   class BasecampAgent < Agent
     include FormConfigurable
-
-    cannot_receive_events!
-
     include Oauthable
     valid_oauth_providers :'37signals'
 
+    cannot_receive_events!
+
     description <<-MD
       The BasecampAgent checks a Basecamp project for new Events
 
       To be able to use this Agent you need to authenticate with 37signals in the [Services](/services) section first.
 
-      You need to provide the `project_id` of the project you want to monitor.
-      If you have your Basecamp project opened in your browser you can find the user_id and project_id as follows:
-
-      `https://basecamp.com/123456/projects/`
-      project_id
-      `-explore-basecamp`
     MD
 
     event_description <<-MD
@@ -52,6 +45,14 @@ module Agents
       }
     end
 
+    form_configurable :project_id, roles: :completable
+
+    def complete_project_id
+      service.prepare_request
+      response = HTTParty.get projects_url, request_options.merge(query_parameters)
+      response.map { |p| {name: "#{p['name']} (#{p['id']})", value: p['id']}}
+    end
+
     def validate_options
       errors.add(:base, "you need to specify the basecamp project id of which you want to receive events") unless options['project_id'].present?
     end
@@ -62,8 +63,8 @@ module Agents
 
     def check
       service.prepare_request
-      reponse = HTTParty.get request_url, request_options.merge(query_parameters)
-      events = JSON.parse(reponse.body)
+      response = HTTParty.get events_url, request_options.merge(query_parameters)
+      events = JSON.parse(response.body)
       if !memory[:last_event].nil?
         events.each do |event|
           create_event :payload => event
@@ -74,8 +75,16 @@ module Agents
     end
 
   private
-    def request_url
-      "https://basecamp.com/#{URI.encode(service.options[:user_id].to_s)}/api/v1/projects/#{URI.encode(interpolated[:project_id].to_s)}/events.json"
+    def base_url
+      "https://basecamp.com/#{URI.encode(service.options[:user_id].to_s)}/api/v1/"
+    end
+
+    def events_url
+      base_url + "projects/#{URI.encode(interpolated[:project_id].to_s)}/events.json"
+    end
+
+    def projects_url
+      base_url + "projects.json"
     end
 
     def request_options

+ 11 - 4
app/models/agents/hipchat_agent.rb

@@ -17,8 +17,10 @@ module Agents
 
       Change the `room_name` to the name of the room you want to send notifications to.
 
-      You can provide a `username` and a `message`. When sending a HTML formatted message change `format` to "html".
-      If you want your message to notify the room members change `notify` to "true".
+      You can provide a `username` and a `message`. If you want to use mentions change `format` to "text" ([details](https://www.hipchat.com/docs/api/method/rooms/message)).
+
+      If you want your message to notify the room members change `notify` to "Yes".
+
       Modify the background color of your message via the `color` attribute (one of "yellow", "red", "green", "purple", "gray", or "random")
 
       Have a look at the [Wiki](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) to learn more about liquid templating.
@@ -41,6 +43,7 @@ module Agents
     form_configurable :message, type: :text
     form_configurable :notify, type: :boolean
     form_configurable :color, type: :array, values: ['yellow', 'red', 'green', 'purple', 'gray', 'random']
+    form_configurable :format, type: :array, values: ['html', 'text']
 
     def validate_auth_token
       client.rooms
@@ -65,13 +68,17 @@ module Agents
     def receive(incoming_events)
       incoming_events.each do |event|
         mo = interpolated(event)
-        client[mo[:room_name]].send(mo[:username][0..14], mo[:message], :notify => boolify(mo[:notify]), :color => mo[:color])
+        client[mo[:room_name]].send(mo[:username][0..14], mo[:message],
+                                      notify: boolify(mo[:notify]),
+                                      color: mo[:color],
+                                      message_format: mo[:format].presence || 'html'
+                                    )
       end
     end
 
     private
     def client
-      @client ||= HipChat::Client.new(interpolated[:auth_token] || credential('hipchat_auth_token'))
+      @client ||= HipChat::Client.new(interpolated[:auth_token].presence || credential('hipchat_auth_token'))
     end
   end
 end

+ 1 - 1
app/presenters/form_configurable_agent_presenter.rb

@@ -33,7 +33,7 @@ class FormConfigurableAgentPresenter < Decorator
         end)
       end
     when :array
-      @view.select_tag "agent[options][#{attribute}]", @view.options_for_select(data[:values], value), html_options.merge(class: "form-control")
+      @view.select_tag("agent[options][#{attribute}]", @view.options_for_select(data[:values], value), html_options.merge(class: "form-control"))
     when :string
       @view.text_field_tag "agent[options][#{attribute}]", value, html_options.merge(:class => 'form-control')
     end

+ 56 - 0
spec/concerns/form_configurable_spec.rb

@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe FormConfigurable do
+  class Agent1
+    include FormConfigurable
+
+    def validate_test
+      true
+    end
+
+    def complete_test
+      [{name: 'test', value: 1234}]
+    end
+  end
+
+  class Agent2 < Agent
+  end
+
+  before(:all) do
+    @agent1 = Agent1.new
+    @agent2 = Agent2.new
+  end
+
+  it "#is_form_configurable" do
+    expect(@agent1.is_form_configurable?).to be true
+    expect(@agent2.is_form_configurable?).to be false
+  end
+
+  describe "#validete_option" do
+    it "should call the validation method if it is defined" do
+      expect(@agent1.validate_option('test')).to be true
+    end
+
+    it "should return false of the method is undefined" do
+      expect(@agent1.validate_option('undefined')).to be false
+    end
+  end
+
+  it "#complete_option" do
+    expect(@agent1.complete_option('test')).to eq [{name: 'test', value: 1234}]
+  end
+
+  describe "#form_configurable" do
+    it "should raise an ArgumentError for invalid  options" do
+      expect { Agent1.form_configurable(:test, invalid: true) }.to raise_error(ArgumentError)
+    end
+
+    it "should raise an ArgumentError when not providing an array with type: array" do
+      expect { Agent1.form_configurable(:test, type: :array, values: 1) }.to raise_error(ArgumentError)
+    end
+
+    it "should not require any options for the default values" do
+      expect { Agent1.form_configurable(:test) }.to change(Agent1, :form_configurable_attributes).by(['test'])
+    end
+  end
+end

+ 40 - 0
spec/controllers/agents_controller_spec.rb

@@ -307,4 +307,44 @@ describe AgentsController do
       expect(response).to redirect_to scenario_path(scenarios(:bob_weather))
     end
   end
+
+  describe "#form_configurable actions" do
+    before(:each) do
+      @params = {attribute: 'auth_token', agent: valid_attributes(:type => "Agents::HipchatAgent", options: {auth_token: '12345'})}
+      sign_in users(:bob)
+    end
+    describe "POST validate" do
+
+      it "returns with status 200 when called with a valid option" do
+        any_instance_of(Agents::HipchatAgent) do |klass|
+          stub(klass).validate_option { true }
+        end
+
+        post :validate, @params
+        expect(response.status).to eq 200
+      end
+
+      it "returns with status 403 when called with an invalid option" do
+        any_instance_of(Agents::HipchatAgent) do |klass|
+          stub(klass).validate_option { false }
+        end
+
+        post :validate, @params
+        expect(response.status).to eq 403
+      end
+    end
+
+    describe "POST complete" do
+      it "callsAgent#complete_option and renders json" do
+        any_instance_of(Agents::HipchatAgent) do |klass|
+          stub(klass).complete_option { [{name: 'test', value: 1}] }
+        end
+
+        post :complete, @params
+        expect(response.status).to eq 200
+        expect(response.header['Content-Type']).to include('application/json')
+
+      end
+    end
+  end
 end

+ 6 - 0
spec/fixtures/agents.yml

@@ -111,3 +111,9 @@ jane_basecamp_agent:
   user: jane
   service: generic
   guid: <%= SecureRandom.hex %>
+
+bob_hipchat_agent:
+  type: Agents::HipchatAgent
+  user: bob
+  service: generic
+  guid: <%= SecureRandom.hex %>

+ 28 - 5
spec/models/agents/basecamp_agent_spec.rb

@@ -5,8 +5,21 @@ describe Agents::BasecampAgent do
   it_behaves_like Oauthable
 
   before(:each) do
-    stub_request(:get, /json$/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/basecamp.json")), :status => 200, :headers => {"Content-Type" => "text/json"})
-    stub_request(:get, /02:00$/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/basecamp.json")), :status => 200, :headers => {"Content-Type" => "text/json"})
+    stub_request(:get, /events.json$/).to_return(
+      :body => File.read(Rails.root.join("spec/data_fixtures/basecamp.json")),
+      :status => 200,
+      :headers => {"Content-Type" => "text/json"}
+    )
+    stub_request(:get, /projects.json$/).to_return(
+      :body => JSON.dump([{name: 'test', id: 1234},{name: 'test1', id: 1235}]),
+      :status => 200,
+      :headers => {"Content-Type" => "text/json"}
+    )
+    stub_request(:get, /02:00$/).to_return(
+      :body => File.read(Rails.root.join("spec/data_fixtures/basecamp.json")),
+      :status => 200,
+      :headers => {"Content-Type" => "text/json"}
+    )
     @valid_params = { :project_id => 6789 }
 
     @checker = Agents::BasecampAgent.new(:name => "somename", :options => @valid_params)
@@ -32,10 +45,13 @@ describe Agents::BasecampAgent do
       expect(@checker.send(:request_options)).to eq({:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)", "Authorization" => 'Bearer "1234token"'}})
     end
 
-    it "should generate the currect request url" do
-      expect(@checker.send(:request_url)).to eq("https://basecamp.com/12345/api/v1/projects/6789/events.json")
+    it "should generate the correct events url" do
+      expect(@checker.send(:events_url)).to eq("https://basecamp.com/12345/api/v1/projects/6789/events.json")
     end
 
+    it "should generate the correct projects url" do
+      expect(@checker.send(:projects_url)).to eq("https://basecamp.com/12345/api/v1/projects.json")
+    end
 
     it "should not provide the since attribute on first run" do
       expect(@checker.send(:query_parameters)).to eq({})
@@ -48,6 +64,13 @@ describe Agents::BasecampAgent do
       expect(@checker.reload.send(:query_parameters)).to eq({:query => {:since => time}})
     end
   end
+
+  describe "#complete_project_id" do
+    it "should return a array of hashes" do
+      expect(@checker.complete_project_id).to eq [{name: 'test (1234)', value: 1234}, {name: 'test1 (1235)', value: 1235}]
+    end
+  end
+
   describe "#check" do
     it "should not emit events on its first run" do
       expect { @checker.check }.to change { Event.count }.by(0)
@@ -60,7 +83,7 @@ describe Agents::BasecampAgent do
   end
 
   describe "#working?" do
-    it "it is working when at least one event was emited" do
+    it "it is working when at least one event was emitted" do
       expect(@checker).not_to be_working
       @checker.memory[:last_event] = '2014-04-17T10:25:31.000+02:00'
       @checker.check

+ 25 - 0
spec/models/agents/hipchat_agent_spec.rb

@@ -50,6 +50,31 @@ describe Agents::HipchatAgent do
     end
   end
 
+  describe "#validate_auth_token" do
+    it "should return true when valid" do
+      any_instance_of(HipChat::Client) do |klass|
+        stub(klass).rooms { true }
+      end
+      expect(@checker.validate_auth_token).to be true
+    end
+
+    it "should return false when invalid" do
+      any_instance_of(HipChat::Client) do |klass|
+        stub(klass).rooms { raise HipChat::UnknownResponseCode.new }
+      end
+      expect(@checker.validate_auth_token).to be false
+    end
+  end
+
+  describe "#complete_room_name" do
+    it "should return a array of hashes" do
+      any_instance_of(HipChat::Client) do |klass|
+        stub(klass).rooms { [OpenStruct.new(name: 'test'), OpenStruct.new(name: 'test1')] }
+      end
+      expect(@checker.complete_room_name).to eq [{name: 'test', value: 'test'},{name: 'test1', value: 'test1'}]
+    end
+  end
+
   describe "#receive" do
     it "send a message to the hipchat" do
       any_instance_of(HipChat::Room) do |obj|