├── Rakefile ├── Gemfile ├── lib ├── asana_digest │ ├── version.rb │ ├── task_list.rb │ ├── client.rb │ ├── template.rb │ └── task.rb └── asana_digest.rb ├── bin └── asana_digest ├── .gitignore ├── README.md ├── asana_digest.gemspec └── LICENSE.txt /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | -------------------------------------------------------------------------------- /lib/asana_digest/version.rb: -------------------------------------------------------------------------------- 1 | module AsanaDigest 2 | VERSION = "0.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /lib/asana_digest.rb: -------------------------------------------------------------------------------- 1 | require "asana_digest/version" 2 | 3 | module AsanaDigest 4 | # Your code goes here... 5 | end 6 | -------------------------------------------------------------------------------- /bin/asana_digest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift File.dirname(__FILE__) + '/../lib' if $0 == __FILE__ 3 | require 'asana_digest/task' 4 | 5 | AsanaDigest::Task.new(ARGV).run 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /lib/asana_digest/task_list.rb: -------------------------------------------------------------------------------- 1 | module AsanaDigest 2 | class TaskList 3 | def initialize 4 | @tasks = {} 5 | end 6 | 7 | def add(user, task) 8 | @tasks[user] ||= [] 9 | @tasks[user] << task 10 | end 11 | 12 | def tasks_for(user) 13 | @tasks[user] || [] 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/asana_digest/client.rb: -------------------------------------------------------------------------------- 1 | require 'net/https' 2 | require 'uri' 3 | require 'json' 4 | 5 | module AsanaDigest 6 | class Client 7 | def initialize(apikey) 8 | @apikey = apikey 9 | end 10 | 11 | def get(path, options={}) 12 | uri = URI.parse("https://app.asana.com/api/1.0#{path}") 13 | 14 | http = Net::HTTP.new(uri.host, uri.port) 15 | http.use_ssl = true 16 | request = Net::HTTP::Get.new(uri.request_uri) 17 | request.basic_auth(@apikey, '') 18 | response = http.request(request) 19 | 20 | JSON.load(response.body)['data'] 21 | end 22 | end 23 | end 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asana Digest 2 | 3 | asana_digest is a command line application to fetch Asana tasks of the previous work day and sends the digest to configured Hipchat channel. 4 | 5 | ## Usage 6 | 7 | Required environment variables: 8 | 9 | - `ASANA_DIGEST_APIKEY`: API key for Asana 10 | - `ASANA_DIGEST_PROJECT_ID`: Project ID(s) to get tasks from. Separate with comma. 11 | - `ASANA_DIGEST_HIPCHAT_TOKEN`: API token for Hipchat messaging 12 | - `ASANA_DIGEST_HIPCHAT_ROOM_NAME`: Room name (ID) for Hipchat messaging 13 | ``` 14 | 15 | Optional environment variables: 16 | 17 | - `ASANA_DIGEST_ACTIVE_USERS`: User IDs to report tasks. Separate with comma. By default it will report all users in the project. 18 | 19 | 20 | ``` 21 | bundle install 22 | bundle exec bin/asana_digest 23 | ``` 24 | 25 | ## Screenshot 26 | 27 | ![](http://dl.dropbox.com/u/135035/Screenshots/i_bfp2zt3g-q.png) 28 | 29 | ## Author 30 | 31 | Tatsuhiko Miyagawa 32 | -------------------------------------------------------------------------------- /asana_digest.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'asana_digest/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "asana_digest" 8 | spec.version = AsanaDigest::VERSION 9 | spec.authors = ["Tatsuhiko Miyagawa"] 10 | spec.email = ["miyagawa@bulknews.net"] 11 | spec.description = %q{TODO: Write a gem description} 12 | spec.summary = %q{TODO: Write a gem summary} 13 | spec.homepage = "" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency "hipchat" 22 | spec.add_dependency "active_support" 23 | spec.add_dependency "tzinfo" 24 | 25 | spec.add_development_dependency "bundler", "~> 1.3" 26 | spec.add_development_dependency "rake" 27 | end 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Tatsuhiko Miyagawa 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/asana_digest/template.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | 3 | module AsanaDigest 4 | class Template 5 | def self.done 6 | < 8 | On <%= date.strftime "%m/%d" %>, <%= user['name'] %> has not completed any tasks. :( 9 | <% else %> 10 | On <%= date.strftime "%m/%d" %>, <%= user['name'] %> has completed following tasks.
11 | <% tasks.each do |task| %> 12 | ✓ <%= task['name'] %> (#<%= task['id'] %>)
13 | <% end %> 14 | <% end %> 15 | EOF 16 | end 17 | 18 | def self.will_do 19 | < 21 | Today, <%= user['name'] %> hasn't scheduled any tasks to work on :( 22 | <% else %> 23 | Today, <%= user['name'] %> will work on following tasks.
24 | <% tasks.each do |task| %> 25 | ▢ <%= task['name'] %> (#<%= task['id'] %>)
26 | <% end %> 27 | <% end %> 28 | EOF 29 | end 30 | 31 | def self.render(template, binding) 32 | ERB.new(send(template), nil, '<>').result(binding) 33 | end 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /lib/asana_digest/task.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'hipchat' 3 | require 'active_support/time' 4 | require 'asana_digest/client' 5 | require 'asana_digest/task_list' 6 | require 'asana_digest/template' 7 | 8 | module AsanaDigest 9 | class Task 10 | attr_reader :asana, :hipchat 11 | 12 | def initialize(args) 13 | @asana = AsanaDigest::Client.new(ENV['ASANA_DIGEST_APIKEY']) 14 | @hipchat = HipChat::Client.new(ENV['ASANA_DIGEST_HIPCHAT_TOKEN'])[ENV['ASANA_DIGEST_HIPCHAT_ROOM_NAME'].to_i] 15 | @name = 'Asana Digest' 16 | end 17 | 18 | def active_users(users) 19 | if ENV['ASANA_DIGEST_ACTIVE_USERS'] 20 | ENV['ASANA_DIGEST_ACTIVE_USERS'].split(',').map { |id| 21 | users.find {|data| data['id'] == id.to_i } 22 | } 23 | else 24 | users 25 | end 26 | end 27 | 28 | def previous_work_day 29 | # Use TZ=Asia/Tokyo to change the timezone 30 | # This doesn't take holidays into account 31 | # Use ASANA_DIGEST_OFFSET=2 if prev day was a holiday 32 | today = Time.now 33 | today - date_offset(today) 34 | end 35 | 36 | def date_offset(today) 37 | if ENV['ASANA_DIGEST_OFFSET'] 38 | ENV['ASANA_DIGEST_OFFSET'].to_i.days 39 | else 40 | today.monday? ? 3.days : 1.day 41 | end 42 | end 43 | 44 | def date_includes?(date, completed) 45 | time = Time.parse(completed) 46 | time.between?(date.beginning_of_day, date.end_of_day) 47 | end 48 | 49 | def render_tasks(template, user, tasks, date=nil) 50 | result = AsanaDigest::Template.render(template, binding) 51 | color = tasks.empty? ? "yellow" : "green" 52 | hipchat.send(@name, result, :color => color, :message_format => 'html') 53 | end 54 | 55 | def run 56 | date = previous_work_day 57 | 58 | tasks = [] 59 | ENV['ASANA_DIGEST_PROJECT_ID'].split(',').each do |project| 60 | tasks += asana.get("/projects/#{project}/tasks?opt_fields=assignee,completed_at,name") 61 | end 62 | 63 | done = TaskList.new 64 | will_do = TaskList.new 65 | 66 | tasks.each do |task| 67 | next if task['assignee'].nil? 68 | 69 | if task['completed_at'].nil? 70 | will_do.add task['assignee']['id'], task 71 | elsif date_includes?(date, task['completed_at']) 72 | done.add task['assignee']['id'], task 73 | end 74 | end 75 | 76 | users = active_users(asana.get("/users")) 77 | 78 | users.each do |user| 79 | render_tasks(:done, user, done.tasks_for(user['id']), date) 80 | end 81 | 82 | users.each do |user| 83 | render_tasks(:will_do, user, will_do.tasks_for(user['id'])) 84 | end 85 | end 86 | end 87 | end 88 | 89 | --------------------------------------------------------------------------------