diff --git a/Gemfile b/Gemfile index 7118b7c5fe..8f2ac282f9 100644 --- a/Gemfile +++ b/Gemfile @@ -37,6 +37,7 @@ gem 'haversine' # Optional Services. gem 'omniauth-37signals' # BasecampAgent # gem 'omniauth-github' +gem 'omniauth-wunderlist', github: 'wunderlist/omniauth-wunderlist', ref: 'd0910d0396107b9302aa1bc50e74bb140990ccb8' # Bundler <1.5 does not recognize :x64_mingw as a valid platform name. # Unfortunately, it can't self-update because it errors when encountering :x64_mingw. diff --git a/Gemfile.lock b/Gemfile.lock index b80bf1d21c..434f489aa0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,6 +8,15 @@ GIT http_parser.rb (~> 0.6.0) simple_oauth (~> 0.2.0) +GIT + remote: git://github.com/wunderlist/omniauth-wunderlist.git + revision: d0910d0396107b9302aa1bc50e74bb140990ccb8 + ref: d0910d0396107b9302aa1bc50e74bb140990ccb8 + specs: + omniauth-wunderlist (0.0.1) + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.1) + GEM remote: https://rubygems.org/ specs: @@ -523,6 +532,7 @@ DEPENDENCIES omniauth-dropbox omniauth-tumblr omniauth-twitter + omniauth-wunderlist! pg protected_attributes (~> 1.0.8) pry diff --git a/app/assets/stylesheets/application.css.scss.erb b/app/assets/stylesheets/application.css.scss.erb index f7c017fad2..f9e95da971 100644 --- a/app/assets/stylesheets/application.css.scss.erb +++ b/app/assets/stylesheets/application.css.scss.erb @@ -252,8 +252,8 @@ h2 .scenario, a span.label.scenario { width: 200px; } -$services: twitter 37signals github tumblr dropbox; -$service-colors: #55acee #8fc857 #444444 #2c4762 #007EE5; +$services: twitter 37signals github tumblr dropbox wunderlist; +$service-colors: #55acee #8fc857 #444444 #2c4762 #007EE5 #ED5F27; @mixin services { @each $service in $services { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 543e9a0bac..a7bf573382 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -57,6 +57,8 @@ def omniauth_provider_icon(provider) case provider.to_sym when :twitter, :tumblr, :github, :dropbox icon_tag("fa-#{provider}") + when :wunderlist + icon_tag("fa-list") else icon_tag("fa-lock") end diff --git a/app/models/agents/wunderlist_agent.rb b/app/models/agents/wunderlist_agent.rb new file mode 100644 index 0000000000..cc5d72b4e3 --- /dev/null +++ b/app/models/agents/wunderlist_agent.rb @@ -0,0 +1,78 @@ +module Agents + class WunderlistAgent < Agent + include FormConfigurable + include Oauthable + valid_oauth_providers :wunderlist + + cannot_be_scheduled! + + gem_dependency_check { Devise.omniauth_providers.include?(:wunderlist) } + + description <<-MD + #{'## Include the `omniauth-wunderlist` gem in your `Gemfile` and set `WUNDERLIST_OAUTH_KEY` and `WUNDERLIST_OAUTH_SECRET` in your environment to use this Agent' if dependencies_missing?} + + The WunderlistAgent creates new new tasks based on the incoming event. + + To be able to use this Agent you need to authenticate with Wunderlist in the [Services](/services) section first. + + MD + + def default_options + { + 'list_id' => '', + 'title' => '{{title}}' + } + end + + form_configurable :list_id, roles: :completable + form_configurable :title + + def complete_list_id + response = request_guard do + HTTParty.get lists_url, request_options + end + response.map { |p| {text: "#{p['title']} (#{p['id']})", id: p['id']}} + end + + def validate_options + errors.add(:base, "you need to specify the list you want to add tasks to") unless options['list_id'].present? + errors.add(:base, "you need to specify the title of the task to create") unless options['title'].present? + end + + def working? + !recent_error_logs? + end + + def receive(incoming_events) + incoming_events.each do |event| + mo = interpolated(event) + title = mo[:title][0..244] + log("Creating new task '#{title}' on list #{mo[:list_id]}", inbound_event: event) + request_guard do + HTTParty.post tasks_url, request_options.merge(body: {title: title, list_id: mo[:list_id].to_i}.to_json) + end + end + end + private + def request_guard(&blk) + response = yield + error("Error during http request: #{response.body}") if response.code > 400 + response + end + + def lists_url + "https://a.wunderlist.com/api/v1/lists" + end + + def tasks_url + "https://a.wunderlist.com/api/v1/tasks" + end + + def request_options + {:headers => {'Content-Type' => 'application/json', + 'User-Agent' => 'Huginn (https://github.com/cantino/huginn)', + 'X-Access-Token' => service.token, + 'X-Client-ID' => ENV["WUNDERLIST_OAUTH_KEY"] }} + end + end +end \ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index f806ef2385..6ae99abd33 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -263,6 +263,12 @@ config.omniauth :dropbox, key, secret end + if defined?(OmniAuth::Strategies::Wunderlist) && + (key = ENV["WUNDERLIST_OAUTH_KEY"]).present? && + (secret = ENV["WUNDERLIST_OAUTH_SECRET"]).present? + config.omniauth :wunderlist, key, secret + end + # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or # change the failure app, you can configure them inside the config.warden block. diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index dd10d119e0..8af9503fc9 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -32,6 +32,7 @@ en: github: "GitHub" 37signals: "37Signals (Basecamp)" dropbox: "Dropbox" + wunderlist: 'Wunderlist' passwords: no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." diff --git a/spec/env.test b/spec/env.test index dfc9fcc819..4f567ded96 100644 --- a/spec/env.test +++ b/spec/env.test @@ -7,4 +7,5 @@ THIRTY_SEVEN_SIGNALS_OAUTH_KEY=TESTKEY THIRTY_SEVEN_SIGNALS_OAUTH_SECRET=TESTSECRET DROPBOX_OAUTH_KEY=dropboxoauthkey DROPBOX_OAUTH_SECRET=dropboxoauthsecret +WUNDERLIST_OAUTH_KEY=wunderoauthkey FAILED_JOBS_TO_KEEP=2 \ No newline at end of file diff --git a/spec/models/agents/wunderlist_agent_spec.rb b/spec/models/agents/wunderlist_agent_spec.rb new file mode 100644 index 0000000000..4a57c3fccb --- /dev/null +++ b/spec/models/agents/wunderlist_agent_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' +require 'models/concerns/oauthable' + +describe Agents::WunderlistAgent do + it_behaves_like Oauthable + + before(:each) do + + @valid_params = { + 'list_id' => '12345', + 'title' => '{{title}}: {{url}}', + } + + @checker = Agents::WunderlistAgent.new(:name => "somename", :options => @valid_params) + @checker.user = users(:jane) + @checker.service = services(:generic) + @checker.save! + + @event = Event.new + @event.agent = agents(:bob_weather_agent) + @event.payload = { title: 'hello', url: 'www.example.com'} + @event.save! + end + + describe "validating" do + before do + expect(@checker).to be_valid + end + + it "should require the title" do + @checker.options['title'] = nil + expect(@checker).not_to be_valid + end + + it "should require the list_id" do + @checker.options['list_id'] = nil + expect(@checker).not_to be_valid + end + end + + it "should generate the request_options" do + expect(@checker.send(:request_options)).to eq({:headers=>{"Content-Type"=>"application/json", "User-Agent"=>"Huginn (https://github.com/cantino/huginn)", "X-Access-Token"=>"1234token", "X-Client-ID"=>"wunderoauthkey"}}) + end + + describe "#complete_list_id" do + it "should return a array of hashes" do + stub_request(:get, 'https://a.wunderlist.com/api/v1/lists').to_return( + :body => JSON.dump([{title: 'test', id: 12345}]), + :headers => {"Content-Type" => "text/json"} + ) + expect(@checker.complete_list_id).to eq([{:text=>"test (12345)", :id=>12345}]) + end + end + + describe "#receive" do + it "send a message to the hipchat" do + stub_request(:post, 'https://a.wunderlist.com/api/v1/tasks').with { |request| request.body == 'abc'} + @checker.receive([@event]) + end + end + + describe "#working?" do + it "should be working with no entry in the error log" do + expect(@checker).to be_working + end + + it "should not be working with a recent entry in the error log" do + @checker.error("test") + @checker.reload + @checker.last_event_at = Time.now + expect(@checker).to_not be_working + end + end +end