asana-0.6.0/ 0000755 0001750 0001750 00000000000 12775347346 011646 5 ustar pravi pravi asana-0.6.0/.travis.yml 0000644 0001750 0001750 00000001472 12775347346 013763 0 ustar pravi pravi language: ruby
rvm:
- 2.2.5
deploy:
provider: rubygems
api_key:
secure: ZFzExKt6auBOcQyg8955GwlZW2OaS64Q+XHGSay2MzjALw1iiy5uuMdwkueCKrGWSv6/eBe9jjsmYBe3OfkUIpIBbUacnbEYeaC70AyucNjvFrrl0YVYHb7neojarJUmKz9bz9Pkju/jdxksaYaj58xfq5YPQDfjFtdmylvuNEYpujT6goPEbxG4U4PpIhhQOZRDRXXAPS+f7jHejTSK06kvJjiJw0d51VJtBbp+0TKNKL6BDKdOKjKeHuebuUmSw8crDyaYdnwYwmNg1cJrGOv2t76M08zoKkkIO2lwPMHisi1/+cbVcZfxM4SfdHJeU6cQuRdb0uCUbbj6GsGwT8vWP2mGUrLe4UV/GfZDmvK3MKeKIlkgig31a3Qny9yjn8EjSnKHYuHBbJvPQDPPpFUfgEneUxn2t4P6m+epkd1gldWqTWf8mhMR/6xAFT4s+BaxnMMJsTC3Ea+dZZ30EqCw/kx5B2Z1KVLgsxHeMN/Q+AeOcbOvlGDsFL0Mjk/PqDTW1AWKLs/D1ohcxjSmlNJGWR6JHa/Ei0GqjDE2+/ZGsKsRfcDD4kU5qnKdqdzDlbL3cL4tChzuWVcguYdrg1yZzqPrCPzmy+2D7Hphyaj9CPKEh7qwT+IQU5o/V2peOJUjKrMlJS4gFq6MvTDh5U59J88Kkg72DXhcEUcySkU=
gem: asana
on:
tags: true
repo: Asana/ruby-asana
asana-0.6.0/.gitignore 0000644 0001750 0001750 00000000172 12775347346 013636 0 ustar pravi pravi .idea
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
/bin/
test.rb
/node_modules/
asana-0.6.0/examples/ 0000755 0001750 0001750 00000000000 12775347346 013464 5 ustar pravi pravi asana-0.6.0/examples/events.rb 0000644 0001750 0001750 00000002141 12775347346 015313 0 ustar pravi pravi # -*- coding: utf-8 -*-
require 'bundler'
Bundler.require
require 'asana'
access_token = ENV['ASANA_ACCESS_TOKEN']
unless access_token
abort "Run this program with the env var ASANA_ACCESS_TOKEN.\n" \
"Go to http://app.asana.com/-/account_api to create a personal access token."
end
client = Asana::Client.new do |c|
c.authentication :access_token, access_token
end
workspace = client.workspaces.find_all.first
task = client.tasks.find_all(assignee: "me", workspace: workspace.id).first
unless task
task = client.tasks.create(workspace: workspace.id, name: "Hello world!", assignee: "me")
end
Thread.abort_on_exception = true
Thread.new do
puts "Listening for 'changed' events on #{task} in one thread..."
task.events(wait: 2).lazy.select { |event| event.action == 'changed' }.each do |event|
puts "#{event.user.name} changed #{event.resource}"
end
end
Thread.new do
puts "Listening for non-'changed' events on #{task} in another thread..."
task.events(wait: 1).lazy.reject { |event| event.action == 'changed' }.each do |event|
puts "'#{event.action}' event: #{event}"
end
end
sleep
asana-0.6.0/examples/personal_access_token.rb 0000644 0001750 0001750 00000001074 12775347346 020357 0 ustar pravi pravi require 'bundler'
Bundler.require
require 'asana'
access_token = ENV['ASANA_ACCESS_TOKEN']
unless access_token
abort "Run this program with the env var ASANA_ACCESS_TOKEN.\n" \
"Go to http://app.asana.com/-/account_api to create a personal access token."
end
client = Asana::Client.new do |c|
c.authentication :access_token, access_token
end
puts "My Workspaces:"
client.workspaces.find_all.each do |workspace|
puts "\t* #{workspace.name} - tags:"
client.tags.find_by_workspace(workspace: workspace.id).each do |tag|
puts "\t\t- #{tag.name}"
end
end
asana-0.6.0/examples/omniauth_integration.rb 0000644 0001750 0001750 00000002516 12775347346 020244 0 ustar pravi pravi require 'bundler'
Bundler.require
require 'asana'
class SinatraApp < Sinatra::Base
id, secret = ENV['ASANA_CLIENT_ID'], ENV['ASANA_CLIENT_SECRET']
unless id && secret
abort "Run this program with the env vars ASANA_CLIENT_ID and ASANA_CLIENT_SECRET.\n" \
"Refer to https://asana.com/developers/documentation/getting-started/authentication "\
"to get your credentials."
end
use OmniAuth::Strategies::Asana, id, secret
enable :sessions
get '/' do
if $client
'My Workspaces'
else
'sign in to asana'
end
end
get '/workspaces' do
if $client
"
My Workspaces
" \
"" + $client.workspaces.find_all.map { |w| "- #{w.name}
" }.join + "
"
else
redirect '/sign_in'
end
end
get '/auth/:name/callback' do
creds = request.env["omniauth.auth"]["credentials"].tap { |h| h.delete('expires') }
strategy = request.env["omniauth.strategy"]
access_token = OAuth2::AccessToken.from_hash(strategy.client, creds).refresh!
$client = Asana::Client.new do |c|
c.authentication :oauth2, access_token
end
redirect '/workspaces'
end
get '/sign_in' do
redirect '/auth/asana'
end
get '/sign_out' do
$client = nil
redirect '/'
end
end
SinatraApp.run! if __FILE__ == $0
asana-0.6.0/examples/cli_app.rb 0000644 0001750 0001750 00000001574 12775347346 015427 0 ustar pravi pravi require 'bundler'
Bundler.require
require 'asana'
id, secret = ENV['ASANA_CLIENT_ID'], ENV['ASANA_CLIENT_SECRET']
unless id && secret
abort "Run this program with the env vars ASANA_CLIENT_ID and ASANA_CLIENT_SECRET.\n" \
"Refer to https://asana.com/developers/documentation/getting-started/authentication "\
"to get your credentials." \
"The redirect URI for your application should be \"urn:ietf:wg:oauth:2.0:oob\"."
end
access_token = Asana::Authentication::OAuth2.offline_flow(client_id: id,
client_secret: secret)
client = Asana::Client.new do |c|
c.authentication :oauth2, access_token
end
puts "My Workspaces:"
client.workspaces.find_all.each do |workspace|
puts "\t* #{workspace.name} - tags:"
client.tags.find_by_workspace(workspace: workspace.id).each do |tag|
puts "\t\t- #{tag.name}"
end
end
asana-0.6.0/examples/Gemfile.lock 0000644 0001750 0001750 00000002213 12775347346 015704 0 ustar pravi pravi PATH
remote: ../
specs:
asana (0.1.1)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0)
GEM
remote: https://rubygems.org/
specs:
faraday (0.9.1)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.9.1)
faraday (>= 0.7.4, < 0.10)
faraday_middleware-multi_json (0.0.6)
faraday_middleware
multi_json
hashie (3.4.1)
jwt (1.5.0)
multi_json (1.11.0)
multi_xml (0.5.5)
multipart-post (2.0.0)
oauth2 (1.0.0)
faraday (>= 0.8, < 0.10)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
omniauth (1.2.2)
hashie (>= 1.2, < 4)
rack (~> 1.0)
omniauth-asana (0.0.1)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-oauth2 (1.3.0)
oauth2 (~> 1.0)
omniauth (~> 1.2)
rack (1.6.1)
rack-protection (1.5.3)
rack
sinatra (1.4.6)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
tilt (2.0.1)
PLATFORMS
ruby
DEPENDENCIES
asana!
omniauth
omniauth-asana
sinatra
BUNDLED WITH
1.10.3
asana-0.6.0/examples/Gemfile 0000644 0001750 0001750 00000000152 12775347346 014755 0 ustar pravi pravi source 'https://rubygems.org'
gem 'omniauth'
gem 'omniauth-asana'
gem 'sinatra'
gem 'asana', path: '../'
asana-0.6.0/README.md 0000644 0001750 0001750 00000033617 12775347346 013137 0 ustar pravi pravi # Asana
[](http://badge.fury.io/rb/asana)
[](https://travis-ci.org/Asana/ruby-asana)
[](https://codeclimate.com/github/Asana/ruby-asana)
[](https://gemnasium.com/Asana/ruby-asana)
A Ruby client for the 1.0 version of the Asana API.
Supported rubies:
* MRI 2.0.0 up to 2.2.x stable
## Required: Security procedures for outdated OpenSSL versions
Older versions of OpenSSL can cause a problem when using `ruby-asana` In particular, at the time of this writing, at least **MacOS X 10.11 and below** ship with a very old version of OpenSSL:
$ openssl version
OpenSSL 0.9.8zh 14 Jan 2016
OpenSSL 0.9.8 was first released in 2005, and therefore only supports TLS (Transport Layer Security) version 1.0. Asana has deprecated and stopped accepting requests for clients which do not suport [TLS 1.0 and above](https://asa.na/tls), which unfortunately includes any software linked against this version of the library - this includes both the MacOS X provided Ruby interpreter and any homebrew installed Ruby that is not specifically configured to link against a newer version.
To see if your Ruby version is affected, run
$ ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION'
If the version printed at the command line is older than `1.0.1`, when, in 2012, OpenSSL first supported TLS 1.1 and 1.2, you will not be able to use `ruby-asana` to connect to Asana. Specifically, you will recieve `400 Bad Request` responses with an error message in the response body about the lack of support for TLS 1.1 and above.
Asana highly recommends using a Ruby installation manager, either RVM or `rbenv`. Instructions on how to install an up-to-date `ruby` for each of these are below.
### Solution when using RVM
RVM makes it easy to install both an updated OpenSSL and a Ruby interpreter that links to it. If you are using MacPorts or Homebrew, you're probably fine out of the box; RVM favors package management using either one of these to satisfy dependencies, and so can keep your ruby up to date automatically. If you are not using these, consider using them, as they're very simple to install and use.
If you don't use your package manager, you can use RVM's [package manager](https://rvm.io/packages) to install from source.
If you want to build OpenSSL from source yourself, you have to specify how to link to this OpenSSL installation:
$ rvm install ruby-{version} --with-openssl-dir={ssl_dir}
# Specify your openssl path prefix, wherever openssl dirs
# "bin", "include", and "lib" are installed; usually
# "/usr" for system installs, or $PREFIX for configure/make locally.
$ ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION' # Verify inside Ruby
OpenSSL 1.0.2h 3 May 2016
If you see the version of OpenSSL greater than OpenSSL 1.0.1, then you're all set to start using `ruby-asana`
### Solution when using rbenv
Similar to RVM, rbenv compiles rubies with knowledge of MacPorts and Homebrew libraries. When a newer version of OpenSSL is installed via the method above, all rubies built (after that time of course) will link to the newer version of OpenSSL.
If you don't use a package manager, as above, you can build by explicitly supplying the directory in which to find OpenSSL:
$ RUBY_CONFIGURE_OPTS=--with-openssl-dir=/opt/local rbenv install ruby-{version}
# Specify your openssl path prefix, wherever openssl dirs
# "bin", "include", and "lib" are installed; usually
# "/usr" for system installs, or $PREFIX for configure/make locally.
$ ruby -ropenssl -e 'puts OpenSSL::OPENSSL_VERSION' # Verify inside Ruby
OpenSSL 1.0.2h 3 May 2016
## Gem Installation
Add this line to your application's Gemfile:
```ruby
gem 'asana'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install asana
## Usage
To do anything, you'll need always an instance of `Asana::Client` configured
with your preferred authentication method (see the Authentication section below
for more complex scenarios) and other options.
The most minimal example would be as follows:
```ruby
require 'asana'
client = Asana::Client.new do |c|
c.authentication :access_token, 'personal_access_token'
end
client.workspaces.find_all.first
```
A full-blown customized client using OAuth2 wih a previously obtained refresh
token, Typhoeus as a Faraday adapter, a custom user agent and custom Faraday
middleware:
```ruby
require 'asana'
client = Asana::Client.new do |c|
c.authentication :oauth2,
refresh_token: 'abc',
client_id: 'bcd',
client_secret: 'cde',
redirect_uri: 'http://example.org/auth'
c.faraday_adapter :typhoeus
c.configure_faraday { |conn| conn.use SomeFaradayMiddleware }
end
workspace = client.workspaces.find_by_id(12)
workspace.users
# => # ...>
client.tags.create_in_workspace(workspace: workspace.id, name: 'foo')
# => #
```
All resources are exposed as methods on the `Asana::Client` instance. Check out
the [documentation for each of them][docs].
### Authentication
This gem supports authenticating against the Asana API with either an API token or through OAuth2.
#### Personal Access Token
```ruby
Asana::Client.new do |c|
c.authentication :access_token, 'personal_access_token'
end
```
#### OAuth2
Authenticating through OAuth2 is preferred. There are many ways you can do this.
##### With a plain bearer token (doesn't support auto-refresh)
If you have a plain bearer token obtained somewhere else and you don't mind not
having your token auto-refresh, you can authenticate with it as follows:
```ruby
Asana::Client.new do |c|
c.authentication :oauth2, bearer_token: 'my_bearer_token'
end
```
##### With a refresh token and client credentials
If you obtained a refresh token, you can use it together with your client
credentials to authenticate:
```ruby
Asana::Client.new do |c|
c.authentication :oauth2,
refresh_token: 'abc',
client_id: 'bcd',
client_secret: 'cde',
redirect_uri: 'http://example.org/auth'
end
```
##### With an ::OAuth2::AccessToken object (from `omniauth-asana` for example)
If you use `omniauth-asana` or a browser-based OAuth2 authentication strategy in
general, possibly because your application is a web application, you can reuse
those credentials to authenticate with this API client. Here's how to do it from
the callback method:
```ruby
# assuming we're using Sinatra and omniauth-asana
get '/auth/:name/callback' do
creds = request.env["omniauth.auth"]["credentials"].tap { |h| h.delete('expires') }
strategy = request.env["omniauth.strategy"]
# We need to refresh the omniauth OAuth2 token
access_token = OAuth2::AccessToken.from_hash(strategy.client, creds).refresh!
$client = Asana::Client.new do |c|
c.authentication :oauth2, access_token
end
redirect '/'
end
```
See `examples/omniauth_integration.rb` for a working example of this.
##### Using an OAuth2 offline authentication flow (for CLI applications)
If your application can't receive HTTP requests and thus you can't use
`omniauth-asana`, for example if it's a CLI application, you can authenticate as
follows:
```ruby
access_token = Asana::Authentication::OAuth2.offline_flow(client_id: ...,
client_secret: ...)
client = Asana::Client.new do |c|
c.authentication :oauth2, access_token
end
client.tasks.find_by_id(12)
```
This will print an authorization URL on STDOUT, and block until you paste in the
authorization code, which you can get by visiting that URL and granting the
necessary permissions.
### Pagination
Whenever you ask for a collection of resources, you can provide a number of
results per page to fetch, between 1 and 100. If you don't provide any, it
defaults to 20.
```ruby
my_tasks = client.tasks.find_by_tag(tag: tag_id, per_page: 5)
# => # ...>
```
An `Asana::Collection` is a paginated collection -- it holds the first
`per_page` results, and a reference to the next page if any.
When you iterate an `Asana::Collection`, it'll transparently keep fetching all
the pages, and caching them along the way:
```ruby
my_tasks.size # => 23, not 5
my_tasks.take(14)
# => [#, #, ... until 14]
```
#### Manual pagination
If you only want to deal with one page at a time and manually paginate, you can
get the elements of the current page with `#elements` and ask for the next page
with `#next_page`, which will return an `Asana::Collection` with the next page
of elements:
```ruby
my_tasks.elements # => [#, #, ... until 5]
my_tasks.next_page # => #
```
#### Lazy pagination
Because an `Asana::Collection` represents the entire collection, it is often
handy to just take what you need from it, rather than let it fetch all its
contents from the network. You can accomplish this by turning it into a lazy
collection with `#lazy`:
```ruby
# let my_tasks be an Asana::Collection of 10 pages of 100 elements each
my_tasks.lazy.drop(120).take(15).to_a
# Fetches only 2 pages, enough to get elements 120 to 135
# => [#, #, ...]
```
### Error handling
In any request against the Asana API, there a number of errors that could
arise. Those are well documented in the [Asana API Documentation][apidocs], and
are represented as exceptions under the namespace `Asana::Errors`.
All errors are subclasses of `Asana::Errors::APIError`, so make sure to rescue
instances of this class if you want to handle them yourself.
### I/O options
All requests (except `DELETE`) accept extra I/O options
[as documented in the API docs][io]. Just pass an extra `options` hash to any
request:
```ruby
client.tasks.find_by_id(12, options: { expand: ['workspace'] })
```
### Attachment uploading
To attach a file to a task or a project, you just need its absolute path on your
filesystem and its MIME type, and the file will be uploaded for you:
```ruby
task = client.tasks.find_by_id(12)
attachment = task.attach(filename: '/absolute/path/to/my/file.png',
mime: 'image/png')
attachment.name # => 'file.png'
```
### Event streams
To subscribe to an event stream of a task or a project, just call `#events` on
it:
```ruby
task = client.tasks.find_by_id(12)
task.events # => #
# You can do the same with only the task id:
events = client.events.for(task.id)
```
An `Asana::Events` object is an infinite collection of `Asana::Event`
instances. Be warned that if you call `#each` on it, it will block forever!
Note that, by default, an event stream will wait at least 1 second between
polls, but that's configurable with the `wait` parameter:
```ruby
# wait at least 3 and a half seconds between each poll to the API
task.events(wait: 3.5) # => #
```
There are some interesting things you can do with an event stream, as it is a
normal Ruby Enumerable. Read below to get some ideas.
#### Subscribe to the event stream with a callback, polling every 2 seconds
```ruby
# Run this in another thread so that we don't block forever
events = client.tasks.find_by_id(12).events(wait: 2)
Thread.new do
events.each do |event|
notify_someone "New event arrived! #{event}"
end
end
```
#### Make the stream lazy and filter it by a specific pattern
To do that we need to call `#lazy` on the `Events` instance, just like with any
other `Enumerable`.
```ruby
events = client.tasks.find_by_id(12).events
only_change_events = events.lazy.select { |event| event.action == 'changed' }
Thread.new do
only_change_events.each do |event|
notify_someone "New change event arrived! #{event}"
end
end
```
## Development
You'll need Ruby 2.1+ and Node v0.10.26+ / NPM 1.4.3+ installed.
After checking out the repo, run `bin/setup` to install dependencies. Then, run
`bin/console` for an interactive prompt that will allow you to experiment.
Run the build with `rake`. This is equivalent to:
$ rake spec && rake rubocop && rake yard
To install this gem onto your local machine, run `bundle exec rake install`.
## Releasing a new version
To release a new version, run either of these commands:
rake bump:patch
rake bump:minor
rake bump:major
This will: update `lib/asana/version.rb`, commit and tag the commit. Then you
just need to `push --tags` to let Travis build and release the new version to
Rubygems:
git push --tags
### Code generation
The specific Asana resource classes (`Tag`, `Workspace`, `Task`, etc) are
generated code, hence they shouldn't be modified by hand. The code that
generates it lives in `lib/templates/resource.ejs`, and is tested by generating
`spec/templates/unicorn.rb` and running `spec/templates/unicorn_spec.rb` as part
of the build.
If you wish to make changes on the code generation script:
1. Add/modify a spec on `spec/templates/unicorn_spec.rb`
2. Add your new feature or change to `lib/templates/resource.ejs`
3. Run `rake` or, more granularly, `rake codegen && rspec
spec/templates/unicorn_spec.rb`
Once you're sure your code works, submit a pull request and ask the maintainer
to make a release, as they'll need to run a release script from the
[asana-api-meta][meta] repository.
## Contributing
1. Fork it ( https://github.com/[my-github-username]/asana/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
[apidocs]: https://asana.com/developers
[io]: https://asana.com/developers/documentation/getting-started/input-output-options
[docs]: http://www.rubydoc.info/github/Asana/ruby-asana/master
[meta]: https://github.com/asana/asana-api-meta
asana-0.6.0/package.json 0000644 0001750 0001750 00000000150 12775347346 014130 0 ustar pravi pravi {
"devDependencies": {
"inflect": "^0.3.0",
"js-yaml": "^3.2.5",
"lodash": "^2.4.1"
}
}
asana-0.6.0/Rakefile 0000644 0001750 0001750 00000003057 12775347346 013320 0 ustar pravi pravi require 'rspec/core/rake_task'
require 'rubocop/rake_task'
require 'yard'
RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new
YARD::Rake::YardocTask.new do |t|
t.stats_options = ['--list-undoc']
end
desc 'Generates a test resource from a YAML using the resource template.'
task :codegen do
# TODO: I believe this is obsolete and can be removed
`node spec/support/codegen.js`
end
namespace :bump do
def read_version
File.readlines('./lib/asana/version.rb')
.detect { |l| l =~ /VERSION/ }
.scan(/VERSION = '([^']+)/).flatten.first.split('.')
.map { |n| Integer(n) }
end
# rubocop:disable Metrics/MethodLength
def write_version(major, minor, patch)
str = <<-EOS
#:nodoc:
module Asana
# Public: Version of the gem.
VERSION = '#{major}.#{minor}.#{patch}'
end
EOS
File.open('./lib/asana/version.rb', 'w') do |f|
f.write str
end
new_version = "#{major}.#{minor}.#{patch}"
system('git add lib/asana/version.rb')
system(%(git commit -m "Bumped to #{new_version}" && ) +
%(git tag -a v#{new_version} -m "Version #{new_version}"))
puts "\nRun git push --tags to release."
end
desc 'Bumps a patch version'
task :patch do
major, minor, patch = read_version
write_version major, minor, patch + 1
end
desc 'Bumps a minor version'
task :minor do
major, minor, = read_version
write_version major, minor + 1, 0
end
desc 'Bumps a major version'
task :major do
major, = read_version
write_version major + 1, 0, 0
end
end
task default: [:spec, :rubocop, :yard]
asana-0.6.0/asana.gemspec 0000644 0001750 0001750 00000002235 12775347346 014300 0 ustar pravi pravi # coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'asana/version'
Gem::Specification.new do |spec|
spec.name = "asana"
spec.version = Asana::VERSION
spec.authors = ["Txus"]
spec.email = ["me@txus.io"]
spec.summary = %q{Official Ruby client for the Asana API}
spec.description = %q{Official Ruby client for the Asana API}
spec.homepage = "https://github.com/asana/ruby-asana"
spec.license = "MIT"
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.required_ruby_version = '~> 2.0'
spec.add_dependency "oauth2", "~> 1.0"
spec.add_dependency "faraday", "~> 0.9"
spec.add_dependency "faraday_middleware", "~> 0.9"
spec.add_dependency "faraday_middleware-multi_json", "~> 0.0"
spec.add_development_dependency "bundler", "~> 1.7"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.2"
end
asana-0.6.0/LICENSE.txt 0000644 0001750 0001750 00000002066 12775347346 013475 0 ustar pravi pravi The MIT License (MIT)
Copyright (c) 2015 Asana, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
asana-0.6.0/CODE_OF_CONDUCT.md 0000644 0001750 0001750 00000002615 12775347346 014451 0 ustar pravi pravi # Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
asana-0.6.0/lib/ 0000755 0001750 0001750 00000000000 12775347346 012414 5 ustar pravi pravi asana-0.6.0/lib/templates/ 0000755 0001750 0001750 00000000000 12775347346 014412 5 ustar pravi pravi asana-0.6.0/lib/templates/resource.ejs 0000644 0001750 0001750 00000021676 12775347346 016760 0 ustar pravi pravi <%
var singularName = resource.name,
pluralName = plural(singularName),
mixins = {
task: ["AttachmentUploading", "EventSubscription"],
project: ["EventSubscription"]
},
skip = { attachment: ["createOnTask"] },
formatComment = function formatComment(text, indentation) {
var indent = Array(indentation + 1).join(" ")
return text.trim().split("\n").map(function(line) {
return indent + (line.length > 0 ? "# " : "#") + line
}).join("\n")
}
function Action(action) {
var that = this
this.action = action
this.collection = action.collection == true
this.requiresData = action.method == "POST" || action.method == "PUT"
this.isInstanceAction = (action.method == "PUT" || action.method == "DELETE")
this.method = action.method
this.methodName = snake(action.name)
this.clientMethod = action.method.toLowerCase()
this.returnsUpdatedRecord = action.method == 'PUT' || action.comment.match(/Returns[a-z\W]+updated/) !== null
this.returnsNothing = action.comment.match(/Returns an empty/) !== null
// Params and idParams
var params = action.params || []
this.idParams = _.filter(params, function(p) { return p.type == "Id" })
// If it looks like an instance action but it's not, make it one
if (!this.isInstanceAction) {
var mainIdParam = _.find(this.idParams, function(p) { return p.name == singularName })
if (mainIdParam !== undefined && !action.name.match(/Id/)) {
this.isInstanceAction = true
this.mainIdParam = mainIdParam
}
}
if (this.idParams.length == 1 &&
action.path.match(/%d/) &&
(action.name.match(/Id/) || (this.isInstanceAction && this.mainIdParam == undefined))) {
var mainIdParam = this.idParams[0]
this.mainIdParam = mainIdParam
this.inferredReturnType = this.isInstanceAction ? 'self.class' : 'self'
}
if (mainIdParam !== undefined) {
this.params = _.reject(params, function(p) { return p.name == mainIdParam.name })
} else {
this.params = params
}
if (!this.inferredReturnType) {
// Infer return type
var name = action.path.match(/\/([a-zA-Z]+)$/)
if (name !== null) {
name = name[1]
// Desugarize 'addProject' to 'project'
var camelCaseTail = name.match(/.*([A-Z][a-z]+)$/)
if (camelCaseTail !== null) { name = decap(camelCaseTail[1]) }
name = single(name)
var explicit = _.find(resources, function(p) { return p == name })
if (name == singularName || name == 'parent' || name == 'children' || name.match(/^sub/) !== null) {
this.inferredReturnType = this.isInstanceAction ? 'self.class' : 'self'
} else if (explicit !== undefined) {
this.inferredReturnType = cap(explicit)
} else {
this.inferredReturnType = 'Resource'
}
} else {
this.inferredReturnType = 'Resource'
}
}
// Endpoint path
this.path = _.reduce(this.idParams, function(acc, id) {
var localName = that.mainIdParam == id ? "id" : id.name
return acc.replace("\%d", "#{" + localName + "}")
}, action.path)
// Extra params (not in the URL) to be passed in the body of the call
this.extraParams = _.reject(this.params, function(p) {
return that.path.match(new RegExp("#{" + p.name + "}"))
})
// Params processing
var paramsLocal = "data"
if (this.collection) { this.extraParams.push({ name: "per_page", apiParamName: "limit" }) }
if (this.extraParams.length > 0) {
var paramNames = _.map(this.extraParams, function(p) { return (p.apiParamName || p.name) + ": " + p.name })
if (this.requiresData) {
var paramsProcessing = "with_params = data.merge(" + paramNames.join(", ") + ")"
paramsLocal = "with_params"
} else {
var paramsProcessing = "params = { " + paramNames.join(", ") + " }"
paramsLocal = "params"
}
paramsProcessing += ".reject { |_,v| v.nil? || Array(v).empty? }"
}
this.paramsProcessing = paramsProcessing
this.paramsLocal = paramsLocal
// Method argument names
var argumentNames = Array()
if (!this.isInstanceAction) { argumentNames.push("client") }
if (this.mainIdParam !== undefined && !this.isInstanceAction) { argumentNames.push("id") }
_.forEach(this.params, function(param) {
argumentNames.push(param.name + ":" + (param.required ? " required(\"" + param.name + "\")" : " nil"))
})
if (this.collection) { argumentNames.push("per_page: 20") }
if (this.method != 'DELETE') { argumentNames.push("options: {}") }
if (this.requiresData) { argumentNames.push("**data") }
this.argumentNames = argumentNames
// API request params
var requestParams = Array()
requestParams.push('"' + this.path + '"')
if (this.paramsProcessing || this.argumentNames.indexOf("**data") != -1) {
var argument = this.requiresData ? "body" : "params"
requestParams.push(argument + ": " + paramsLocal)
}
if (this.method != 'DELETE') { requestParams.push("options: options") }
this.requestParams = requestParams
this.documentation = this.renderDocumentation()
// Constructor
this.constructor = function(body) {
var pre = '', post = ''
var wrapWithParsing = function(body) {
var pre = '', post = ''
if (!that.returnsNothing) {
pre = 'parse('
post = ')' + (that.collection ? '' : '.first')
}
return pre + body + post
}
if (!that.returnsNothing) {
if (that.isInstanceAction && that.returnsUpdatedRecord) {
pre = "refresh_with("
post = ')'
} else {
pre = that.collection ? "Collection.new(" : that.inferredReturnType + ".new("
post = (that.collection ? ', type: ' + that.inferredReturnType : '') + ', client: client)'
}
} else { post = ' && true' }
return pre + wrapWithParsing(body) + post
}
this.request = this.constructor("client." + this.clientMethod + "(" + this.requestParams.join(", ") + ")")
}
Action.prototype.renderDocumentation = function () {
var formatParamNotes = function(params) {
var trimmed = _.flatten(_.map(params, function(p) {
return _.map(p.notes, function(note) { return note.trim() })
}))
return (trimmed.length > 0 ? "\nNotes:\n\n" + trimmed.join("\n\n") : "")
}
var formatParam = function(p, name) {
return (name !== undefined ? name : p.name) + " - [" + p.type + "] " + p.comment
}
var lines = _.map(this.params, function(p) { return formatParam(p) })
if (this.mainIdParam !== undefined && !this.isInstanceAction) { lines.unshift(formatParam(this.mainIdParam, "id")) }
if (this.collection) { lines.push("per_page - [Integer] the number of records to fetch per page.") }
if (this.method != 'DELETE') { lines.push("options - [Hash] the request I/O options.") }
if (this.requiresData) { lines.push("data - [Hash] the attributes to post.") }
return this.action.comment + "\n" + lines.join("\n") + formatParamNotes(this.params)
}
var actionsToSkip = skip[resource.name] || []
var actionsToGen = _.reject(resource.actions, function(action) {
return actionsToSkip.indexOf(action.name) != -1
})
var allActions = _.map(actionsToGen, function(action) { return new Action(action) }),
instanceActions = _.filter(allActions, function(action) { return action.isInstanceAction }),
classActions = _.reject(allActions, function(action) { return action.isInstanceAction })
var mixinsToInclude = mixins[resource.name] || []
%>### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
<%= formatComment(resource.comment, 4) %>
class <%= cap(singularName) %> < Resource
<% _.forEach(mixinsToInclude, function(mixin) { %>
include <%= mixin %>
<% }) %>
<% _.forEach(resource.properties, function(property) { %>
attr_reader :<%= property.name %>
<% }) %>
class << self
# Returns the plural name of the resource.
def plural_name
'<%= pluralName %>'
end
<% _.forEach(classActions, function(action) { %>
<%= formatComment(action.documentation, 8) %>
def <%= action.methodName %>(<%= action.argumentNames.join(", ") %>)
<% if (action.paramsProcessing) { %> <%= action.paramsProcessing %><% } %>
<%= action.request %>
end
<% }) %> end
<% _.forEach(instanceActions, function(action) { %>
<%= formatComment(action.documentation, 6) %>
def <%= action.methodName %>(<%= action.argumentNames.join(", ") %>)
<% if (action.paramsProcessing) { %> <%= action.paramsProcessing %><% } %>
<%= action.request %>
end
<% }) %>
end
end
end
asana-0.6.0/lib/templates/index.js 0000644 0001750 0001750 00000000234 12775347346 016056 0 ustar pravi pravi module.exports = {
resource: {
template: 'resource.ejs',
filename: function(resource, helpers) {
return resource.name + '.rb';
}
}
};
asana-0.6.0/lib/asana/ 0000755 0001750 0001750 00000000000 12775347346 013477 5 ustar pravi pravi asana-0.6.0/lib/asana/client.rb 0000644 0001750 0001750 00000010106 12775347346 015300 0 ustar pravi pravi require_relative 'authentication'
require_relative 'client/configuration'
require_relative 'resources'
module Asana
# Public: A client to interact with the Asana API. It exposes all the
# available resources of the Asana API in idiomatic Ruby.
#
# Examples
#
# # Authentication with a personal access token
# Asana::Client.new do |client|
# client.authentication :access_token, '...'
# end
#
# # OAuth2 with a plain bearer token (doesn't support auto-refresh)
# Asana::Client.new do |client|
# client.authentication :oauth2, bearer_token: '...'
# end
#
# # OAuth2 with a plain refresh token and client credentials
# Asana::Client.new do |client|
# client.authentication :oauth2,
# refresh_token: '...',
# client_id: '...',
# client_secret: '...',
# redirect_uri: '...'
# end
#
# # OAuth2 with an ::OAuth2::AccessToken object
# Asana::Client.new do |client|
# client.authentication :oauth2, my_oauth2_access_token_object
# end
#
# # Use a custom Faraday network adapter
# Asana::Client.new do |client|
# client.authentication ...
# client.adapter :typhoeus
# end
#
# # Use a custom user agent string
# Asana::Client.new do |client|
# client.authentication ...
# client.user_agent '...'
# end
#
# # Pass in custom configuration to the Faraday connection
# Asana::Client.new do |client|
# client.authentication ...
# client.configure_faraday { |conn| conn.use MyMiddleware }
# end
#
class Client
# Internal: Proxies Resource classes to implement a fluent API on the Client
# instances.
class ResourceProxy
def initialize(client: required('client'), resource: required('resource'))
@client = client
@resource = resource
end
def method_missing(m, *args, &block)
@resource.public_send(m, *([@client] + args), &block)
end
def respond_to_missing?(m, *)
@resource.respond_to?(m)
end
end
# Public: Initializes a new client.
#
# Yields a {Asana::Client::Configuration} object as a configuration
# DSL. See {Asana::Client} for usage examples.
def initialize
config = Configuration.new.tap { |c| yield c }.to_h
@http_client =
HttpClient.new(authentication: config.fetch(:authentication),
adapter: config[:faraday_adapter],
user_agent: config[:user_agent],
debug_mode: config[:debug_mode],
&config[:faraday_configuration])
end
# Public: Performs a GET request against an arbitrary Asana URL. Allows for
# the user to interact with the API in ways that haven't been
# reflected/foreseen in this library.
def get(url, *args)
@http_client.get(url, *args)
end
# Public: Performs a POST request against an arbitrary Asana URL. Allows for
# the user to interact with the API in ways that haven't been
# reflected/foreseen in this library.
def post(url, *args)
@http_client.post(url, *args)
end
# Public: Performs a PUT request against an arbitrary Asana URL. Allows for
# the user to interact with the API in ways that haven't been
# reflected/foreseen in this library.
def put(url, *args)
@http_client.put(url, *args)
end
# Public: Performs a DELETE request against an arbitrary Asana URL. Allows
# for the user to interact with the API in ways that haven't been
# reflected/foreseen in this library.
def delete(url, *args)
@http_client.delete(url, *args)
end
# Public: Exposes queries for all top-evel endpoints.
#
# E.g. #users will query /users and return a
# Asana::Resources::Collection.
Resources::Registry.resources.each do |resource_class|
define_method(resource_class.plural_name) do
ResourceProxy.new(client: @http_client,
resource: resource_class)
end
end
end
end
asana-0.6.0/lib/asana/http_client/ 0000755 0001750 0001750 00000000000 12775347346 016014 5 ustar pravi pravi asana-0.6.0/lib/asana/http_client/response.rb 0000644 0001750 0001750 00000001660 12775347346 020202 0 ustar pravi pravi module Asana
class HttpClient
# Internal: Represents a response from the Asana API.
class Response
# Public:
# Returns a [Faraday::Env] object for debugging.
attr_reader :faraday_env
# Public:
# Returns the [Integer] status code of the response.
attr_reader :status
# Public:
# Returns the [Hash] representing the parsed JSON body.
attr_reader :body
# Public: Wraps a Faraday response.
#
# faraday_response - [Faraday::Response] the Faraday response to wrap.
def initialize(faraday_response)
@faraday_env = faraday_response.env
@status = faraday_env.status
@body = faraday_env.body
end
# Public:
# Returns a [String] representation of the response.
def to_s
"#"
end
alias_method :inspect, :to_s
end
end
end
asana-0.6.0/lib/asana/http_client/environment_info.rb 0000644 0001750 0001750 00000002650 12775347346 021723 0 ustar pravi pravi require_relative '../version'
require 'openssl'
module Asana
class HttpClient
# Internal: Adds environment information to a Faraday request.
class EnvironmentInfo
# Internal: The default user agent to use in all requests to the API.
USER_AGENT = "ruby-asana v#{Asana::VERSION}"
def initialize(user_agent = nil)
@user_agent = user_agent || USER_AGENT
@openssl_version = OpenSSL::OPENSSL_VERSION
@client_version = Asana::VERSION
@os = os
end
# Public: Augments a Faraday connection with information about the
# environment.
def configure(builder)
builder.headers[:user_agent] = @user_agent
builder.headers[:"X-Asana-Client-Lib"] = header
end
private
def header
{ os: @os,
language: 'ruby',
language_version: RUBY_VERSION,
version: @client_version,
openssl_version: @openssl_version }
.map { |k, v| "#{k}=#{v}" }.join('&')
end
# rubocop:disable Metrics/MethodLength
def os
if RUBY_PLATFORM =~ /win32/ || RUBY_PLATFORM =~ /mingw/
'windows'
elsif RUBY_PLATFORM =~ /linux/
'linux'
elsif RUBY_PLATFORM =~ /darwin/
'darwin'
elsif RUBY_PLATFORM =~ /freebsd/
'freebsd'
else
'unknown'
end
end
# rubocop:enable Metrics/MethodLength
end
end
end
asana-0.6.0/lib/asana/http_client/error_handling.rb 0000644 0001750 0001750 00000006721 12775347346 021344 0 ustar pravi pravi require 'multi_json'
require_relative '../errors'
module Asana
class HttpClient
# Internal: Handles errors from the API and re-raises them as proper
# exceptions.
module ErrorHandling
include Errors
module_function
# Public: Perform a request handling any API errors correspondingly.
#
# request - [Proc] a block that will execute the request.
#
# Returns a [Faraday::Response] object.
#
# Raises [Asana::Errors::InvalidRequest] for invalid requests.
# Raises [Asana::Errors::NotAuthorized] for unauthorized requests.
# Raises [Asana::Errors::Forbidden] for forbidden requests.
# Raises [Asana::Errors::NotFound] when a resource can't be found.
# Raises [Asana::Errors::RateLimitEnforced] when the API is throttling.
# Raises [Asana::Errors::ServerError] when there's a server problem.
# Raises [Asana::Errors::APIError] when the API returns an unknown error.
#
# rubocop:disable all
def handle(&request)
request.call
rescue Faraday::ClientError => e
raise e unless e.response
case e.response[:status]
when 400 then raise invalid_request(e.response)
when 401 then raise not_authorized(e.response)
when 403 then raise forbidden(e.response)
when 404 then raise not_found(e.response)
when 412 then recover_response(e.response)
when 429 then raise rate_limit_enforced(e.response)
when 500 then raise server_error(e.response)
else raise api_error(e.response)
end
end
# rubocop:enable all
# Internal: Returns an InvalidRequest exception including a list of
# errors.
def invalid_request(response)
errors = body(response).fetch('errors', []).map { |e| e['message'] }
InvalidRequest.new(errors).tap do |exception|
exception.response = response
end
end
# Internal: Returns a NotAuthorized exception.
def not_authorized(response)
NotAuthorized.new.tap { |exception| exception.response = response }
end
# Internal: Returns a Forbidden exception.
def forbidden(response)
Forbidden.new.tap { |exception| exception.response = response }
end
# Internal: Returns a NotFound exception.
def not_found(response)
NotFound.new.tap { |exception| exception.response = response }
end
# Internal: Returns a RateLimitEnforced exception with a retry after
# field.
def rate_limit_enforced(response)
retry_after_seconds = response[:headers]['Retry-After']
RateLimitEnforced.new(retry_after_seconds).tap do |exception|
exception.response = response
end
end
# Internal: Returns a ServerError exception with a unique phrase.
def server_error(response)
phrase = body(response).fetch('errors', []).first['phrase']
ServerError.new(phrase).tap do |exception|
exception.response = response
end
end
# Internal: Returns an APIError exception.
def api_error(response)
APIError.new.tap { |exception| exception.response = response }
end
# Internal: Parser a response body from JSON.
def body(response)
MultiJson.load(response[:body])
end
def recover_response(response)
r = response.dup.tap { |res| res[:body] = body(response) }
Response.new(OpenStruct.new(env: OpenStruct.new(r)))
end
end
end
end
asana-0.6.0/lib/asana/resource_includes/ 0000755 0001750 0001750 00000000000 12775347346 017214 5 ustar pravi pravi asana-0.6.0/lib/asana/resource_includes/events.rb 0000644 0001750 0001750 00000006675 12775347346 021063 0 ustar pravi pravi require_relative 'event'
module Asana
module Resources
# Public: An infinite collection of events.
#
# Since they are infinite, if you want to filter or do other collection
# operations without blocking indefinitely you should call #lazy on them to
# turn them into a lazy collection.
#
# Examples:
#
# # Subscribes to an event stream and blocks indefinitely, printing
# # information of every event as it comes in.
# events = Events.new(resource: 'someresourceID', client: client)
# events.each do |event|
# puts [event.type, event.action]
# end
#
# # Lazily filters events as they come in and prints them.
# events = Events.new(resource: 'someresourceID', client: client)
# events.lazy.select { |e| e.type == 'task' }.each do |event|
# puts [event.type, event.action]
# end
#
class Events
include Enumerable
# Public: Initializes a new Events instance, subscribed to a resource ID.
#
# resource - [String] a resource ID. Can be a task id or a workspace id.
# client - [Asana::Client] a client to perform the requests.
# wait - [Integer] the number of seconds to wait between each poll.
# options - [Hash] the request I/O options
def initialize(resource: required('resource'),
client: required('client'),
wait: 1, options: {})
@resource = resource
@client = client
@events = []
@wait = wait
@options = options
@sync = nil
@last_poll = nil
end
# Public: Iterates indefinitely over all events happening to a particular
# resource from the @sync timestamp or from now if it is nil.
def each(&block)
if block
loop do
poll if @events.empty?
event = @events.shift
yield event if event
end
else
to_enum
end
end
private
# Internal: Polls and fetches all events that have occurred since the sync
# token was created. Updates the sync token as it comes back from the
# response.
#
# If we polled less than @wait seconds ago, we don't do anything.
#
# Notes:
#
# On the first request, the sync token is not passed (because it is
# nil). The response will be the same as for an expired sync token, and
# will include a new valid sync token.
#
# If the sync token is too old (which may happen from time to time)
# the API will return a `412 Precondition Failed` error, and include
# a fresh `sync` token in the response.
def poll
rate_limiting do
body = @client.get('/events',
params: params,
options: @options).body
@sync = body['sync']
@events += body.fetch('data', []).map do |event_data|
Event.new(event_data, client: @client)
end
end
end
# Internal: Returns the formatted params for the poll request.
def params
{ resource: @resource, sync: @sync }.reject { |_, v| v.nil? }
end
# Internal: Executes a block if at least @wait seconds have passed since
# @last_poll.
def rate_limiting(&block)
return if @last_poll && Time.now - @last_poll <= @wait
block.call.tap { @last_poll = Time.now }
end
end
end
end
asana-0.6.0/lib/asana/resource_includes/resource.rb 0000644 0001750 0001750 00000005516 12775347346 021377 0 ustar pravi pravi require_relative 'registry'
require_relative 'response_helper'
module Asana
module Resources
# Public: The base resource class which provides some sugar over common
# resource functionality.
class Resource
include ResponseHelper
extend ResponseHelper
def self.inherited(base)
Registry.register(base)
end
def initialize(data, client: required('client'))
@_client = client
@_data = data
data.each do |k, v|
instance_variable_set(:"@#{k}", v) if respond_to?(k)
end
end
# If it has findById, it implements #refresh
def refresh
if self.class.respond_to?(:find_by_id)
self.class.find_by_id(client, id)
else
fail "#{self.class.name} does not respond to #find_by_id"
end
end
# Internal: Proxies method calls to the data, wrapping it accordingly and
# caching the result by defining a real reader method.
#
# Returns the value for the requested property.
#
# Raises a NoMethodError if the property doesn't exist.
def method_missing(m, *args)
super unless respond_to_missing?(m, *args)
cache(m, wrapped(to_h[m.to_s]))
end
# Internal: Guard for the method_missing proxy. Checks if the resource
# actually has a specific piece of data at all.
#
# Returns true if the resource has the property, false otherwise.
def respond_to_missing?(m, *)
to_h.key?(m.to_s)
end
# Public:
# Returns the raw Hash representation of the data.
def to_h
@_data
end
def to_s
attrs = to_h.map { |k, _| "#{k}: #{public_send(k).inspect}" }.join(', ')
"#"
end
alias_method :inspect, :to_s
private
# Internal: The Asana::Client instance.
def client
@_client
end
# Internal: Caches a property and a value by defining a reader method for
# it.
#
# property - [#to_s] the property
# value - [Object] the corresponding value
#
# Returns the value.
def cache(property, value)
field = :"@#{property}"
instance_variable_set(field, value)
define_singleton_method(property) { instance_variable_get(field) }
value
end
# Internal: Wraps a value in a more useful class if possible, namely a
# Resource or a Collection.
#
# Returns the wrapped value or the plain value if it couldn't be wrapped.
def wrapped(value)
case value
when Hash then Resource.new(value, client: client)
when Array then value.map(&method(:wrapped))
else value
end
end
def refresh_with(data)
self.class.new(data, client: @client)
end
end
end
end
asana-0.6.0/lib/asana/resource_includes/event.rb 0000644 0001750 0001750 00000003723 12775347346 020667 0 ustar pravi pravi require_relative 'events'
module Asana
module Resources
# An _event_ is an object representing a change to a resource that was
# observed by an event subscription.
#
# In general, requesting events on a resource is faster and subject to
# higher rate limits than requesting the resource itself. Additionally,
# change events bubble up - listening to events on a project would include
# when stories are added to tasks in the project, even on subtasks.
#
# Establish an initial sync token by making a request with no sync token.
# The response will be a `412` error - the same as if the sync token had
# expired.
#
# Subsequent requests should always provide the sync token from the
# immediately preceding call.
#
# Sync tokens may not be valid if you attempt to go 'backward' in the
# history by requesting previous tokens, though re-requesting the current
# sync token is generally safe, and will always return the same results.
#
# When you receive a `412 Precondition Failed` error, it means that the sync
# token is either invalid or expired. If you are attempting to keep a set of
# data in sync, this signals you may need to re-crawl the data.
#
# Sync tokens always expire after 24 hours, but may expire sooner, depending
# on load on the service.
class Event < Resource
attr_reader :type
class << self
# Returns the plural name of the resource.
def plural_name
'events'
end
# Public: Returns an infinite collection of events on a particular
# resource.
#
# client - [Asana::Client] the client to perform the requests.
# id - [String] the id of the resource to get events from.
# wait - [Integer] the number of seconds to wait between each poll.
def for(client, id, wait: 1)
Events.new(resource: id, client: client, wait: wait)
end
end
end
end
end
asana-0.6.0/lib/asana/resource_includes/registry.rb 0000644 0001750 0001750 00000003504 12775347346 021413 0 ustar pravi pravi require_relative 'resource'
require 'set'
module Asana
module Resources
# Internal: Global registry of Resource subclasses. It provides lookup from
# singular and plural names to the actual class objects.
#
# Examples
#
# class Unicorn < Asana::Resources::Resource
# path '/unicorns'
# end
#
# Registry.lookup(:unicorn) # => Unicorn
# Registry.lookup_many(:unicorns) # => Unicorn
#
module Registry
class << self
# Public: Registers a new resource class.
#
# resource_klass - [Class] the resource class.
#
# Returns nothing.
def register(resource_klass)
resources << resource_klass
end
# Public: Looks up a resource class by its singular name.
#
# singular_name - [#to_s] the name of the resource, e.g :unicorn.
#
# Returns the resource class or {Asana::Resources::Resource}.
def lookup(singular_name)
resources.detect do |klass|
klass.singular_name.to_s == singular_name.to_s
end || Resource
end
# Public: Looks up a resource class by its plural name.
#
# plural_name - [#to_s] the plural name of the resource, e.g :unicorns.
#
# Returns the resource class or {Asana::Resources::Resource}.
def lookup_many(plural_name)
resources.detect do |klass|
klass.plural_name.to_s == plural_name.to_s
end || Resource
end
# Internal: A set of Resource classes.
#
# Returns the Set, defaulting to the empty set.
#
# Note: this object is a mutable singleton, so it should not be accessed
# from multiple threads.
def resources
@resources ||= Set.new
end
end
end
end
end
asana-0.6.0/lib/asana/resource_includes/event_subscription.rb 0000644 0001750 0001750 00000000622 12775347346 023466 0 ustar pravi pravi require_relative 'events'
module Asana
module Resources
# Public: Mixin to enable a resource with the ability to fetch events about
# itself.
module EventSubscription
# Public: Returns an infinite collection of events on the resource.
def events(wait: 1, options: {})
Events.new(resource: id, client: client, wait: wait, options: options)
end
end
end
end
asana-0.6.0/lib/asana/resource_includes/attachment_uploading.rb 0000644 0001750 0001750 00000002374 12775347346 023741 0 ustar pravi pravi module Asana
module Resources
# Internal: Mixin to add the ability to upload an attachment to a specific
# Asana resource (a Task, really).
module AttachmentUploading
# Uploads a new attachment to the resource.
#
# filename - [String] the absolute path of the file to upload.
# mime - [String] the MIME type of the file
# options - [Hash] the request I/O options
# data - [Hash] extra attributes to post
#
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def attach(filename: required('filename'),
mime: required('mime'),
options: {}, **data)
path = File.expand_path(filename)
unless File.exist?(path)
fail ArgumentError, "file #{filename} doesn't exist"
end
upload = Faraday::UploadIO.new(path, mime)
response = client.post("/#{self.class.plural_name}/#{id}/attachments",
body: data,
upload: upload,
options: options)
Attachment.new(parse(response).first, client: client)
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
end
end
end
asana-0.6.0/lib/asana/resource_includes/collection.rb 0000644 0001750 0001750 00000004150 12775347346 021674 0 ustar pravi pravi require_relative 'response_helper'
module Asana
module Resources
# Public: Represents a paginated collection of Asana resources.
class Collection
include Enumerable
include ResponseHelper
attr_reader :elements
# Public: Initializes a collection representing a page of resources of a
# given type.
#
# (elements, extra) - [Array] an (String, Hash) tuple coming from the
# response parser.
# type - [Class] the type of resource that the collection
# contains. Defaults to the generic Resource.
# client - [Asana::Client] the client to perform requests.
def initialize((elements, extra),
type: Resource,
client: required('client'))
@elements = elements.map { |elem| type.new(elem, client: client) }
@type = type
@next_page_data = extra['next_page']
@client = client
end
# Public: Iterates over the elements of the collection.
def each(&block)
if block
@elements.each(&block)
(next_page || []).each(&block)
else
to_enum
end
end
# Public: Returns the size of the collection.
def size
to_a.size
end
alias_method :length, :size
# Public: Returns a String representation of the collection.
def to_s
"# " \
"[#{@elements.map(&:inspect).join(', ')}" +
(@next_page_data ? ', ...' : '') + ']>'
end
alias_method :inspect, :to_s
# Public: Returns a new Asana::Resources::Collection with the next page
# or nil if there are no more pages. Caches the result.
def next_page
if defined?(@next_page)
@next_page
else
@next_page = if @next_page_data
response = parse(@client.get(@next_page_data['path']))
self.class.new(response, type: @type, client: @client)
end
end
end
end
end
end
asana-0.6.0/lib/asana/resource_includes/response_helper.rb 0000644 0001750 0001750 00000000570 12775347346 022740 0 ustar pravi pravi module Asana
module Resources
# Internal: A helper to make response body parsing easier.
module ResponseHelper
def parse(response)
data = response.body.fetch('data') do
fail("Unexpected response body: #{response.body}")
end
extra = response.body.reject { |k, _| k == 'data' }
[data, extra]
end
end
end
end
asana-0.6.0/lib/asana/client/ 0000755 0001750 0001750 00000000000 12775347346 014755 5 ustar pravi pravi asana-0.6.0/lib/asana/client/configuration.rb 0000644 0001750 0001750 00000012421 12775347346 020151 0 ustar pravi pravi module Asana
class Client
# Internal: Represents a configuration DSL for an Asana::Client.
#
# Examples
#
# config = Configuration.new
# config.authentication :access_token, 'personal_access_token'
# config.adapter :typhoeus
# config.configure_faraday { |conn| conn.use MyMiddleware }
# config.to_h
# # => { authentication: #,
# faraday_adapter: :typhoeus,
# faraday_configuration: # }
#
class Configuration
# Public: Initializes an empty configuration object.
def initialize
@configuration = {}
end
# Public: Sets an authentication strategy.
#
# type - [:oauth2, :api_token] the kind of authentication strategy to use
# value - [::OAuth2::AccessToken, String, Hash] the configuration for the
# chosen authentication strategy.
#
# Returns nothing.
#
# Raises ArgumentError if the arguments are invalid.
def authentication(type, value)
auth = case type
when :oauth2 then oauth2(value)
when :access_token then from_bearer_token(value)
else error "unsupported authentication type #{type}"
end
@configuration[:authentication] = auth
end
# Public: Sets a custom network adapter for Faraday.
#
# adapter - [Symbol, Proc] the adapter.
#
# Returns nothing.
def faraday_adapter(adapter)
@configuration[:faraday_adapter] = adapter
end
# Public: Sets a custom configuration block for the Faraday connection.
#
# config - [Proc] the configuration block.
#
# Returns nothing.
def configure_faraday(&config)
@configuration[:faraday_configuration] = config
end
# Public: Configures the client in debug mode, which will print verbose
# information on STDERR.
#
# Returns nothing.
def debug_mode
@configuration[:debug_mode] = true
end
# Public:
# Returns the configuration [Hash].
def to_h
@configuration
end
private
# Internal: Configures an OAuth2 authentication strategy from either an
# OAuth2 access token object, or a plain refresh token, or a plain bearer
# token.
#
# value - [::OAuth::AccessToken, String] the value to configure the
# strategy from.
#
# Returns [Asana::Authentication::OAuth2::AccessTokenAuthentication,
# Asana::Authentication::OAuth2::BearerTokenAuthentication]
# the OAuth2 authentication strategy.
#
# Raises ArgumentError if the OAuth2 configuration arguments are invalid.
#
# rubocop:disable Metrics/MethodLength
def oauth2(value)
case value
when ::OAuth2::AccessToken
from_access_token(value)
when ->(v) { v.is_a?(Hash) && v[:refresh_token] }
from_refresh_token(value)
when ->(v) { v.is_a?(Hash) && v[:bearer_token] }
from_bearer_token(value[:bearer_token])
else
error 'Invalid OAuth2 configuration: pass in either an ' \
'::OAuth2::AccessToken object of your own or a hash ' \
'containing :refresh_token or :bearer_token.'
end
end
# Internal: Configures an OAuth2 AccessTokenAuthentication strategy.
#
# access_token - [::OAuth2::AccessToken] the OAuth2 access token object
#
# Returns a [Authentication::OAuth2::AccessTokenAuthentication] strategy.
def from_access_token(access_token)
Authentication::OAuth2::AccessTokenAuthentication
.new(access_token)
end
# Internal: Configures an OAuth2 AccessTokenAuthentication strategy.
#
# hash - The configuration hash:
# :refresh_token - [String] the OAuth2 refresh token
# :client_id - [String] the OAuth2 client id
# :client_secret - [String] the OAuth2 client secret
# :redirect_uri - [String] the OAuth2 redirect URI
#
# Returns a [Authentication::OAuth2::AccessTokenAuthentication] strategy.
def from_refresh_token(hash)
refresh_token, client_id, client_secret, redirect_uri =
requiring(hash, :refresh_token, :client_id,
:client_secret, :redirect_uri)
Authentication::OAuth2::AccessTokenAuthentication
.from_refresh_token(refresh_token,
client_id: client_id,
client_secret: client_secret,
redirect_uri: redirect_uri)
end
# Internal: Configures an OAuth2 BearerTokenAuthentication strategy.
#
# bearer_token - [String] the plain OAuth2 bearer token
#
# Returns a [Authentication::OAuth2::BearerTokenAuthentication] strategy.
def from_bearer_token(bearer_token)
Authentication::OAuth2::BearerTokenAuthentication
.new(bearer_token)
end
def requiring(hash, *keys)
missing_keys = keys.select { |k| !hash.key?(k) }
missing_keys.any? && error("Missing keys: #{missing_keys.join(', ')}")
keys.map { |k| hash[k] }
end
def error(msg)
fail ArgumentError, msg
end
end
end
end
asana-0.6.0/lib/asana/resources.rb 0000644 0001750 0001750 00000000641 12775347346 016037 0 ustar pravi pravi require_relative 'resource_includes/resource'
require_relative 'resource_includes/collection'
Dir[File.join(File.dirname(__FILE__), 'resource_includes', '*.rb')]
.each { |resource| require resource }
Dir[File.join(File.dirname(__FILE__), 'resources', '*.rb')]
.each { |resource| require resource }
module Asana
# Public: Contains all the resources that the Asana API can return.
module Resources
end
end
asana-0.6.0/lib/asana/errors.rb 0000644 0001750 0001750 00000005714 12775347346 015347 0 ustar pravi pravi module Asana
# Public: Defines the different errors that the Asana API may throw, which the
# client code may want to catch.
module Errors
# Public: A generic, catch-all API error. It contains the whole response
# object for debugging purposes.
#
# Note: This exception should never be raised when there exists a more
# specific subclass.
APIError = Class.new(StandardError) do
attr_accessor :response
def to_s
'An unknown API error ocurred.'
end
end
# Public: A 401 error. Raised when the credentials used are invalid and the
# user could not be authenticated.
NotAuthorized = Class.new(APIError) do
def to_s
'A valid API key was not provided with the request, so the API could '\
'not associate a user with the request.'
end
end
# Public: A 403 error. Raised when the user doesn't have permission to
# access the requested resource or to perform the requested action on it.
Forbidden = Class.new(APIError) do
def to_s
'The API key and request syntax was valid but the server is refusing '\
'to complete the request. This can happen if you try to read or write '\
'to objects or properties that the user does not have access to.'
end
end
# Public: A 404 error. Raised when the requested resource doesn't exist.
NotFound = Class.new(APIError) do
def to_s
'Either the request method and path supplied do not specify a known '\
'action in the API, or the object specified by the request does not '\
'exist.'
end
end
# Public: A 500 error. Raised when there is a problem in the Asana API
# server. It contains a unique phrase that can be used to identify the
# problem when contacting developer support.
ServerError = Class.new(APIError) do
attr_accessor :phrase
def initialize(phrase)
@phrase = phrase
end
def to_s
"There has been an error on Asana's end. Use this unique phrase to "\
'identify the problem when contacting developer support: ' +
%("#{@phrase}")
end
end
# Public: A 400 error. Raised when the request was malformed or missing some
# parameters. It contains a list of errors indicating the specific problems.
InvalidRequest = Class.new(APIError) do
attr_accessor :errors
def initialize(errors)
@errors = errors
end
def to_s
errors.join(', ')
end
end
# Public: A 429 error. Raised when the Asana API enforces rate-limiting on
# the client to avoid overload. It contains the number of seconds to wait
# before retrying the operation.
RateLimitEnforced = Class.new(APIError) do
attr_accessor :retry_after_seconds
def initialize(retry_after_seconds)
@retry_after_seconds = retry_after_seconds
end
def to_s
"Retry your request after #{@retry_after_seconds} seconds."
end
end
end
end
asana-0.6.0/lib/asana/http_client.rb 0000644 0001750 0001750 00000012626 12775347346 016350 0 ustar pravi pravi require 'faraday'
require 'faraday_middleware'
require 'faraday_middleware/multi_json'
require_relative 'http_client/error_handling'
require_relative 'http_client/environment_info'
require_relative 'http_client/response'
module Asana
# Internal: Wrapper over Faraday that abstracts authentication, request
# parsing and common options.
class HttpClient
# Internal: The API base URI.
BASE_URI = 'https://app.asana.com/api/1.0'
# Public: Initializes an HttpClient to make requests to the Asana API.
#
# authentication - [Asana::Authentication] An authentication strategy.
# adapter - [Symbol, Proc] A Faraday adapter, eiter a Symbol for
# registered adapters or a Proc taking a builder for a
# custom one. Defaults to Faraday.default_adapter.
# user_agent - [String] The user agent. Defaults to "ruby-asana vX.Y.Z".
# config - [Proc] An optional block that yields the Faraday builder
# object for customization.
def initialize(authentication: required('authentication'),
adapter: nil,
user_agent: nil,
debug_mode: false,
&config)
@authentication = authentication
@adapter = adapter || Faraday.default_adapter
@environment_info = EnvironmentInfo.new(user_agent)
@debug_mode = debug_mode
@config = config
end
# Public: Performs a GET request against the API.
#
# resource_uri - [String] the resource URI relative to the base Asana API
# URL, e.g "/users/me".
# params - [Hash] the request parameters
# options - [Hash] the request I/O options
#
# Returns an [Asana::HttpClient::Response] if everything went well.
# Raises [Asana::Errors::APIError] if anything went wrong.
def get(resource_uri, params: {}, options: {})
opts = options.reduce({}) do |acc, (k, v)|
acc.tap do |hash|
hash[:"opt_#{k}"] = v.is_a?(Array) ? v.join(',') : v
end
end
perform_request(:get, resource_uri, params.merge(opts))
end
# Public: Performs a PUT request against the API.
#
# resource_uri - [String] the resource URI relative to the base Asana API
# URL, e.g "/users/me".
# body - [Hash] the body to PUT.
# options - [Hash] the request I/O options
#
# Returns an [Asana::HttpClient::Response] if everything went well.
# Raises [Asana::Errors::APIError] if anything went wrong.
def put(resource_uri, body: {}, options: {})
params = { data: body }.merge(options.empty? ? {} : { options: options })
perform_request(:put, resource_uri, params)
end
# Public: Performs a POST request against the API.
#
# resource_uri - [String] the resource URI relative to the base Asana API
# URL, e.g "/tags".
# body - [Hash] the body to POST.
# upload - [Faraday::UploadIO] an upload object to post as multipart.
# Defaults to nil.
# options - [Hash] the request I/O options
#
# Returns an [Asana::HttpClient::Response] if everything went well.
# Raises [Asana::Errors::APIError] if anything went wrong.
def post(resource_uri, body: {}, upload: nil, options: {})
params = { data: body }.merge(options.empty? ? {} : { options: options })
if upload
perform_request(:post, resource_uri, params.merge(file: upload)) do |c|
c.request :multipart
end
else
perform_request(:post, resource_uri, params)
end
end
# Public: Performs a DELETE request against the API.
#
# resource_uri - [String] the resource URI relative to the base Asana API
# URL, e.g "/tags".
#
# Returns an [Asana::HttpClient::Response] if everything went well.
# Raises [Asana::Errors::APIError] if anything went wrong.
def delete(resource_uri)
perform_request(:delete, resource_uri)
end
private
def connection(&request_config)
Faraday.new do |builder|
@authentication.configure(builder)
@environment_info.configure(builder)
request_config.call(builder) if request_config
configure_format(builder)
add_middleware(builder)
@config.call(builder) if @config
use_adapter(builder, @adapter)
end
end
def perform_request(method, resource_uri, body = {}, &request_config)
handling_errors do
url = BASE_URI + resource_uri
log_request(method, url, body) if @debug_mode
Response.new(connection(&request_config).public_send(method, url, body))
end
end
def configure_format(builder)
builder.request :multi_json
builder.response :multi_json
end
def add_middleware(builder)
builder.use Faraday::Response::RaiseError
builder.use FaradayMiddleware::FollowRedirects
end
def use_adapter(builder, adapter)
case adapter
when Symbol
builder.adapter(adapter)
when Proc
adapter.call(builder)
end
end
def handling_errors(&request)
ErrorHandling.handle(&request)
end
def log_request(method, url, body)
STDERR.puts format('[%s] %s %s (%s)',
self.class,
method.to_s.upcase,
url,
body.inspect)
end
end
end
asana-0.6.0/lib/asana/version.rb 0000644 0001750 0001750 00000000116 12775347346 015507 0 ustar pravi pravi #:nodoc:
module Asana
# Public: Version of the gem.
VERSION = '0.6.0'
end
asana-0.6.0/lib/asana/resources/ 0000755 0001750 0001750 00000000000 12775347346 015511 5 ustar pravi pravi asana-0.6.0/lib/asana/resources/task.rb 0000644 0001750 0001750 00000036566 12775347346 017020 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# The _task_ is the basic object around which many operations in Asana are
# centered. In the Asana application, multiple tasks populate the middle pane
# according to some view parameters, and the set of selected tasks determines
# the more detailed information presented in the details pane.
class Task < Resource
include AttachmentUploading
include EventSubscription
attr_reader :id
attr_reader :assignee
attr_reader :assignee_status
attr_reader :created_at
attr_reader :completed
attr_reader :completed_at
attr_reader :custom_fields
attr_reader :due_on
attr_reader :due_at
attr_reader :external
attr_reader :followers
attr_reader :hearted
attr_reader :hearts
attr_reader :modified_at
attr_reader :name
attr_reader :notes
attr_reader :num_hearts
attr_reader :projects
attr_reader :parent
attr_reader :workspace
attr_reader :memberships
attr_reader :tags
class << self
# Returns the plural name of the resource.
def plural_name
'tasks'
end
# Creating a new task is as easy as POSTing to the `/tasks` endpoint
# with a data block containing the fields you'd like to set on the task.
# Any unspecified fields will take on default values.
#
# Every task is required to be created in a specific workspace, and this
# workspace cannot be changed once set. The workspace need not be set
# explicitly if you specify `projects` or a `parent` task instead.
#
# `projects` can be a comma separated list of projects, or just a single
# project the task should belong to.
#
# workspace - [Id] The workspace to create a task in.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def create(client, workspace: nil, options: {}, **data)
with_params = data.merge(workspace: workspace).reject { |_,v| v.nil? || Array(v).empty? }
self.new(parse(client.post("/tasks", body: with_params, options: options)).first, client: client)
end
# Creating a new task is as easy as POSTing to the `/tasks` endpoint
# with a data block containing the fields you'd like to set on the task.
# Any unspecified fields will take on default values.
#
# Every task is required to be created in a specific workspace, and this
# workspace cannot be changed once set. The workspace need not be set
# explicitly if you specify a `project` or a `parent` task instead.
#
# workspace - [Id] The workspace to create a task in.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def create_in_workspace(client, workspace: required("workspace"), options: {}, **data)
self.new(parse(client.post("/workspaces/#{workspace}/tasks", body: data, options: options)).first, client: client)
end
# Returns the complete task record for a single task.
#
# id - [Id] The task to get.
# options - [Hash] the request I/O options.
def find_by_id(client, id, options: {})
self.new(parse(client.get("/tasks/#{id}", options: options)).first, client: client)
end
# Returns the compact task records for all tasks within the given project,
# ordered by their priority within the project.
#
# projectId - [Id] The project in which to search for tasks.
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_project(client, projectId: required("projectId"), per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/projects/#{projectId}/tasks", params: params, options: options)), type: self, client: client)
end
# Returns the compact task records for all tasks with the given tag.
#
# tag - [Id] The tag in which to search for tasks.
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_tag(client, tag: required("tag"), per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tags/#{tag}/tasks", params: params, options: options)), type: self, client: client)
end
# Returns the compact task records for some filtered set of tasks. Use one
# or more of the parameters provided to filter the tasks returned. You must
# specify a `project` or `tag` if you do not specify `assignee` and `workspace`.
#
# assignee - [String] The assignee to filter tasks on.
# project - [Id] The project to filter tasks on.
# workspace - [Id] The workspace or organization to filter tasks on.
# completed_since - [String] Only return tasks that are either incomplete or that have been
# completed since this time.
#
# modified_since - [String] Only return tasks that have been modified since the given time.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
# Notes:
#
# If you specify `assignee`, you must also specify the `workspace` to filter on.
#
# If you specify `workspace`, you must also specify the `assignee` to filter on.
#
# A task is considered "modified" if any of its properties change,
# or associations between it and other objects are modified (e.g.
# a task being added to a project). A task is not considered modified
# just because another object it is associated with (e.g. a subtask)
# is modified. Actions that count as modifying the task include
# assigning, renaming, completing, and adding stories.
def find_all(client, assignee: nil, project: nil, workspace: nil, completed_since: nil, modified_since: nil, per_page: 20, options: {})
params = { assignee: assignee, project: project, workspace: workspace, completed_since: completed_since, modified_since: modified_since, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tasks", params: params, options: options)), type: self, client: client)
end
end
# A specific, existing task can be updated by making a PUT request on the
# URL for that task. Only the fields provided in the `data` block will be
# updated; any unspecified fields will remain unchanged.
#
# When using this method, it is best to specify only those fields you wish
# to change, or else you may overwrite changes made by another user since
# you last retrieved the task.
#
# Returns the complete updated task record.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def update(options: {}, **data)
refresh_with(parse(client.put("/tasks/#{id}", body: data, options: options)).first)
end
# A specific, existing task can be deleted by making a DELETE request on the
# URL for that task. Deleted tasks go into the "trash" of the user making
# the delete request. Tasks can be recovered from the trash within a period
# of 30 days; afterward they are completely removed from the system.
#
# Returns an empty data record.
def delete()
client.delete("/tasks/#{id}") && true
end
# Adds each of the specified followers to the task, if they are not already
# following. Returns the complete, updated record for the affected task.
#
# followers - [Array] An array of followers to add to the task.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_followers(followers: required("followers"), options: {}, **data)
with_params = data.merge(followers: followers).reject { |_,v| v.nil? || Array(v).empty? }
refresh_with(parse(client.post("/tasks/#{id}/addFollowers", body: with_params, options: options)).first)
end
# Removes each of the specified followers from the task if they are
# following. Returns the complete, updated record for the affected task.
#
# followers - [Array] An array of followers to remove from the task.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def remove_followers(followers: required("followers"), options: {}, **data)
with_params = data.merge(followers: followers).reject { |_,v| v.nil? || Array(v).empty? }
refresh_with(parse(client.post("/tasks/#{id}/removeFollowers", body: with_params, options: options)).first)
end
# Returns a compact representation of all of the projects the task is in.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def projects(per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tasks/#{id}/projects", params: params, options: options)), type: Project, client: client)
end
# Adds the task to the specified project, in the optional location
# specified. If no location arguments are given, the task will be added to
# the beginning of the project.
#
# `addProject` can also be used to reorder a task within a project that
# already contains it.
#
# Returns an empty data block.
#
# project - [Id] The project to add the task to.
# insert_after - [Id] A task in the project to insert the task after, or `null` to
# insert at the beginning of the list.
#
# insert_before - [Id] A task in the project to insert the task before, or `null` to
# insert at the end of the list.
#
# section - [Id] A section in the project to insert the task into. The task will be
# inserted at the top of the section.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_project(project: required("project"), insert_after: nil, insert_before: nil, section: nil, options: {}, **data)
with_params = data.merge(project: project, insert_after: insert_after, insert_before: insert_before, section: section).reject { |_,v| v.nil? || Array(v).empty? }
client.post("/tasks/#{id}/addProject", body: with_params, options: options) && true
end
# Removes the task from the specified project. The task will still exist
# in the system, but it will not be in the project anymore.
#
# Returns an empty data block.
#
# project - [Id] The project to remove the task from.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def remove_project(project: required("project"), options: {}, **data)
with_params = data.merge(project: project).reject { |_,v| v.nil? || Array(v).empty? }
client.post("/tasks/#{id}/removeProject", body: with_params, options: options) && true
end
# Returns a compact representation of all of the tags the task has.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def tags(per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tasks/#{id}/tags", params: params, options: options)), type: Tag, client: client)
end
# Adds a tag to a task. Returns an empty data block.
#
# tag - [Id] The tag to add to the task.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_tag(tag: required("tag"), options: {}, **data)
with_params = data.merge(tag: tag).reject { |_,v| v.nil? || Array(v).empty? }
client.post("/tasks/#{id}/addTag", body: with_params, options: options) && true
end
# Removes a tag from the task. Returns an empty data block.
#
# tag - [Id] The tag to remove from the task.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def remove_tag(tag: required("tag"), options: {}, **data)
with_params = data.merge(tag: tag).reject { |_,v| v.nil? || Array(v).empty? }
client.post("/tasks/#{id}/removeTag", body: with_params, options: options) && true
end
# Returns a compact representation of all of the subtasks of a task.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def subtasks(per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tasks/#{id}/subtasks", params: params, options: options)), type: self.class, client: client)
end
# Creates a new subtask and adds it to the parent task. Returns the full record
# for the newly created subtask.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_subtask(options: {}, **data)
self.class.new(parse(client.post("/tasks/#{id}/subtasks", body: data, options: options)).first, client: client)
end
# Changes the parent of a task. Each task may only be a subtask of a single
# parent, or no parent task at all. Returns an empty data block.
#
# parent - [Id] The new parent of the task, or `null` for no parent.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def set_parent(parent: required("parent"), options: {}, **data)
with_params = data.merge(parent: parent).reject { |_,v| v.nil? || Array(v).empty? }
client.post("/tasks/#{id}/setParent", body: with_params, options: options) && true
end
# Returns a compact representation of all of the stories on the task.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def stories(per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tasks/#{id}/stories", params: params, options: options)), type: Story, client: client)
end
# Adds a comment to a task. The comment will be authored by the
# currently authenticated user, and timestamped when the server receives
# the request.
#
# Returns the full record for the new story added to the task.
#
# text - [String] The plain text of the comment to add.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_comment(text: required("text"), options: {}, **data)
with_params = data.merge(text: text).reject { |_,v| v.nil? || Array(v).empty? }
Story.new(parse(client.post("/tasks/#{id}/stories", body: with_params, options: options)).first, client: client)
end
end
end
end asana-0.6.0/lib/asana/resources/project.rb 0000644 0001750 0001750 00000031201 12775347346 017501 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# A _project_ represents a prioritized list of tasks in Asana. It exists in a
# single workspace or organization and is accessible to a subset of users in
# that workspace or organization, depending on its permissions.
#
# Projects in organizations are shared with a single team. You cannot currently
# change the team of a project via the API. Non-organization workspaces do not
# have teams and so you should not specify the team of project in a
# regular workspace.
class Project < Resource
include EventSubscription
attr_reader :name
attr_reader :id
attr_reader :owner
attr_reader :current_status
attr_reader :due_date
attr_reader :created_at
attr_reader :modified_at
attr_reader :archived
attr_reader :public
attr_reader :members
attr_reader :followers
attr_reader :custom_field_settings
attr_reader :color
attr_reader :notes
attr_reader :workspace
attr_reader :team
class << self
# Returns the plural name of the resource.
def plural_name
'projects'
end
# Creates a new project in a workspace or team.
#
# Every project is required to be created in a specific workspace or
# organization, and this cannot be changed once set. Note that you can use
# the `workspace` parameter regardless of whether or not it is an
# organization.
#
# If the workspace for your project _is_ an organization, you must also
# supply a `team` to share the project with.
#
# Returns the full record of the newly created project.
#
# workspace - [Id] The workspace or organization to create the project in.
# team - [Id] If creating in an organization, the specific team to create the
# project in.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def create(client, workspace: required("workspace"), team: nil, options: {}, **data)
with_params = data.merge(workspace: workspace, team: team).reject { |_,v| v.nil? || Array(v).empty? }
self.new(parse(client.post("/projects", body: with_params, options: options)).first, client: client)
end
# If the workspace for your project _is_ an organization, you must also
# supply a `team` to share the project with.
#
# Returns the full record of the newly created project.
#
# workspace - [Id] The workspace or organization to create the project in.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def create_in_workspace(client, workspace: required("workspace"), options: {}, **data)
self.new(parse(client.post("/workspaces/#{workspace}/projects", body: data, options: options)).first, client: client)
end
# Creates a project shared with the given team.
#
# Returns the full record of the newly created project.
#
# team - [Id] The team to create the project in.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def create_in_team(client, team: required("team"), options: {}, **data)
self.new(parse(client.post("/teams/#{team}/projects", body: data, options: options)).first, client: client)
end
# Returns the complete project record for a single project.
#
# id - [Id] The project to get.
# options - [Hash] the request I/O options.
def find_by_id(client, id, options: {})
self.new(parse(client.get("/projects/#{id}", options: options)).first, client: client)
end
# Returns the compact project records for some filtered set of projects.
# Use one or more of the parameters provided to filter the projects returned.
#
# workspace - [Id] The workspace or organization to filter projects on.
# team - [Id] The team to filter projects on.
# archived - [Boolean] Only return projects whose `archived` field takes on the value of
# this parameter.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_all(client, workspace: nil, team: nil, archived: nil, per_page: 20, options: {})
params = { workspace: workspace, team: team, archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/projects", params: params, options: options)), type: self, client: client)
end
# Returns the compact project records for all projects in the workspace.
#
# workspace - [Id] The workspace or organization to find projects in.
# archived - [Boolean] Only return projects whose `archived` field takes on the value of
# this parameter.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_workspace(client, workspace: required("workspace"), archived: nil, per_page: 20, options: {})
params = { archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/workspaces/#{workspace}/projects", params: params, options: options)), type: self, client: client)
end
# Returns the compact project records for all projects in the team.
#
# team - [Id] The team to find projects in.
# archived - [Boolean] Only return projects whose `archived` field takes on the value of
# this parameter.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_team(client, team: required("team"), archived: nil, per_page: 20, options: {})
params = { archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/teams/#{team}/projects", params: params, options: options)), type: self, client: client)
end
end
# A specific, existing project can be updated by making a PUT request on the
# URL for that project. Only the fields provided in the `data` block will be
# updated; any unspecified fields will remain unchanged.
#
# When using this method, it is best to specify only those fields you wish
# to change, or else you may overwrite changes made by another user since
# you last retrieved the task.
#
# Returns the complete updated project record.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def update(options: {}, **data)
refresh_with(parse(client.put("/projects/#{id}", body: data, options: options)).first)
end
# A specific, existing project can be deleted by making a DELETE request
# on the URL for that project.
#
# Returns an empty data record.
def delete()
client.delete("/projects/#{id}") && true
end
# Returns compact records for all sections in the specified project.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def sections(per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/projects/#{id}/sections", params: params, options: options)), type: Resource, client: client)
end
# Returns the compact task records for all tasks within the given project,
# ordered by their priority within the project. Tasks can exist in more than one project at a time.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def tasks(per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/projects/#{id}/tasks", params: params, options: options)), type: Task, client: client)
end
# Adds the specified list of users as followers to the project. Followers are a subset of members, therefore if
# the users are not already members of the project they will also become members as a result of this operation.
# Returns the updated project record.
#
# followers - [Array] An array of followers to add to the project.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_followers(followers: required("followers"), options: {}, **data)
with_params = data.merge(followers: followers).reject { |_,v| v.nil? || Array(v).empty? }
refresh_with(parse(client.post("/projects/#{id}/addFollowers", body: with_params, options: options)).first)
end
# Removes the specified list of users from following the project, this will not affect project membership status.
# Returns the updated project record.
#
# followers - [Array] An array of followers to remove from the project.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def remove_followers(followers: required("followers"), options: {}, **data)
with_params = data.merge(followers: followers).reject { |_,v| v.nil? || Array(v).empty? }
refresh_with(parse(client.post("/projects/#{id}/removeFollowers", body: with_params, options: options)).first)
end
# Adds the specified list of users as members of the project. Returns the updated project record.
#
# members - [Array] An array of members to add to the project.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_members(members: required("members"), options: {}, **data)
with_params = data.merge(members: members).reject { |_,v| v.nil? || Array(v).empty? }
refresh_with(parse(client.post("/projects/#{id}/addMembers", body: with_params, options: options)).first)
end
# Removes the specified list of members from the project. Returns the updated project record.
#
# members - [Array] An array of members to remove from the project.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def remove_members(members: required("members"), options: {}, **data)
with_params = data.merge(members: members).reject { |_,v| v.nil? || Array(v).empty? }
refresh_with(parse(client.post("/projects/#{id}/removeMembers", body: with_params, options: options)).first)
end
# Create a new custom field setting on the project.
#
# custom_field - [Id] The id of the custom field to associate with this project.
# is_important - [Boolean] Whether this field should be considered important to this project.
#
# insert_before - [Id] An id of a Custom Field Settings on this project, before which the new Custom Field Settings will be added.
# `insert_before` and `insert_after` parameters cannot both be specified.
#
# insert_after - [Id] An id of a Custom Field Settings on this project, after which the new Custom Field Settings will be added.
# `insert_before` and `insert_after` parameters cannot both be specified.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_custom_field_setting(custom_field: required("custom_field"), is_important: nil, insert_before: nil, insert_after: nil, options: {}, **data)
with_params = data.merge(custom_field: custom_field, is_important: is_important, insert_before: insert_before, insert_after: insert_after).reject { |_,v| v.nil? || Array(v).empty? }
Resource.new(parse(client.post("/projects/#{id}/addCustomFieldSetting", body: with_params, options: options)).first, client: client)
end
# Remove a custom field setting on the project.
#
# custom_field - [Id] The id of the custom field to remove from this project.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def remove_custom_field_setting(custom_field: nil, options: {}, **data)
with_params = data.merge(custom_field: custom_field).reject { |_,v| v.nil? || Array(v).empty? }
Resource.new(parse(client.post("/projects/#{id}/removeCustomFieldSetting", body: with_params, options: options)).first, client: client)
end
end
end
end asana-0.6.0/lib/asana/resources/workspace.rb 0000644 0001750 0001750 00000013247 12775347346 020043 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# A _workspace_ is the highest-level organizational unit in Asana. All projects
# and tasks have an associated workspace.
#
# An _organization_ is a special kind of workspace that represents a company.
# In an organization, you can group your projects into teams. You can read
# more about how organizations work on the Asana Guide.
# To tell if your workspace is an organization or not, check its
# `is_organization` property.
#
# Over time, we intend to migrate most workspaces into organizations and to
# release more organization-specific functionality. We may eventually deprecate
# using workspace-based APIs for organizations. Currently, and until after
# some reasonable grace period following any further announcements, you can
# still reference organizations in any `workspace` parameter.
class Workspace < Resource
attr_reader :id
attr_reader :name
attr_reader :is_organization
class << self
# Returns the plural name of the resource.
def plural_name
'workspaces'
end
# Returns the full workspace record for a single workspace.
#
# id - [Id] Globally unique identifier for the workspace or organization.
#
# options - [Hash] the request I/O options.
def find_by_id(client, id, options: {})
self.new(parse(client.get("/workspaces/#{id}", options: options)).first, client: client)
end
# Returns the compact records for all workspaces visible to the authorized user.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_all(client, per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/workspaces", params: params, options: options)), type: self, client: client)
end
end
# A specific, existing workspace can be updated by making a PUT request on
# the URL for that workspace. Only the fields provided in the data block
# will be updated; any unspecified fields will remain unchanged.
#
# Currently the only field that can be modified for a workspace is its `name`.
#
# Returns the complete, updated workspace record.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def update(options: {}, **data)
refresh_with(parse(client.put("/workspaces/#{id}", body: data, options: options)).first)
end
# Retrieves objects in the workspace based on an auto-completion/typeahead
# search algorithm. This feature is meant to provide results quickly, so do
# not rely on this API to provide extremely accurate search results. The
# result set is limited to a single page of results with a maximum size,
# so you won't be able to fetch large numbers of results.
#
# type - [Enum] The type of values the typeahead should return.
# Note that unlike in the names of endpoints, the types listed here are
# in singular form (e.g. `task`). Using multiple types is not yet supported.
#
# query - [String] The string that will be used to search for relevant objects. If an
# empty string is passed in, the API will currently return an empty
# result set.
#
# count - [Number] The number of results to return. The default is `20` if this
# parameter is omitted, with a minimum of `1` and a maximum of `100`.
# If there are fewer results found than requested, all will be returned.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def typeahead(type: required("type"), query: nil, count: nil, per_page: 20, options: {})
params = { type: type, query: query, count: count, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/workspaces/#{id}/typeahead", params: params, options: options)), type: Resource, client: client)
end
# The user can be referenced by their globally unique user ID or their email address.
# Returns the full user record for the invited user.
#
# user - [String] An identifier for the user. Can be one of an email address,
# the globally unique identifier for the user, or the keyword `me`
# to indicate the current user making the request.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_user(user: required("user"), options: {}, **data)
with_params = data.merge(user: user).reject { |_,v| v.nil? || Array(v).empty? }
User.new(parse(client.post("/workspaces/#{id}/addUser", body: with_params, options: options)).first, client: client)
end
# The user making this call must be an admin in the workspace.
# Returns an empty data record.
#
# user - [String] An identifier for the user. Can be one of an email address,
# the globally unique identifier for the user, or the keyword `me`
# to indicate the current user making the request.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def remove_user(user: required("user"), options: {}, **data)
with_params = data.merge(user: user).reject { |_,v| v.nil? || Array(v).empty? }
client.post("/workspaces/#{id}/removeUser", body: with_params, options: options) && true
end
end
end
end asana-0.6.0/lib/asana/resources/user.rb 0000644 0001750 0001750 00000005606 12775347346 017023 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# A _user_ object represents an account in Asana that can be given access to
# various workspaces, projects, and tasks.
#
# Like other objects in the system, users are referred to by numerical IDs.
# However, the special string identifier `me` can be used anywhere
# a user ID is accepted, to refer to the current authenticated user.
class User < Resource
attr_reader :id
attr_reader :name
attr_reader :email
attr_reader :photo
attr_reader :workspaces
class << self
# Returns the plural name of the resource.
def plural_name
'users'
end
# Returns the full user record for the currently authenticated user.
#
# options - [Hash] the request I/O options.
def me(client, options: {})
Resource.new(parse(client.get("/users/me", options: options)).first, client: client)
end
# Returns the full user record for the single user with the provided ID.
#
# id - [String] An identifier for the user. Can be one of an email address,
# the globally unique identifier for the user, or the keyword `me`
# to indicate the current user making the request.
#
# options - [Hash] the request I/O options.
def find_by_id(client, id, options: {})
self.new(parse(client.get("/users/#{id}", options: options)).first, client: client)
end
# Returns the user records for all users in the specified workspace or
# organization.
#
# workspace - [Id] The workspace in which to get users.
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_workspace(client, workspace: required("workspace"), per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/workspaces/#{workspace}/users", params: params, options: options)), type: self, client: client)
end
# Returns the user records for all users in all workspaces and organizations
# accessible to the authenticated user. Accepts an optional workspace ID
# parameter.
#
# workspace - [Id] The workspace or organization to filter users on.
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_all(client, workspace: nil, per_page: 20, options: {})
params = { workspace: workspace, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/users", params: params, options: options)), type: self, client: client)
end
end
end
end
end asana-0.6.0/lib/asana/resources/tag.rb 0000644 0001750 0001750 00000013340 12775347346 016612 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# A _tag_ is a label that can be attached to any task in Asana. It exists in a
# single workspace or organization.
#
# Tags have some metadata associated with them, but it is possible that we will
# simplify them in the future so it is not encouraged to rely too heavily on it.
# Unlike projects, tags do not provide any ordering on the tasks they
# are associated with.
class Tag < Resource
attr_reader :id
attr_reader :created_at
attr_reader :followers
attr_reader :name
attr_reader :color
attr_reader :notes
attr_reader :workspace
class << self
# Returns the plural name of the resource.
def plural_name
'tags'
end
# Creates a new tag in a workspace or organization.
#
# Every tag is required to be created in a specific workspace or
# organization, and this cannot be changed once set. Note that you can use
# the `workspace` parameter regardless of whether or not it is an
# organization.
#
# Returns the full record of the newly created tag.
#
# workspace - [Id] The workspace or organization to create the tag in.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def create(client, workspace: required("workspace"), options: {}, **data)
with_params = data.merge(workspace: workspace).reject { |_,v| v.nil? || Array(v).empty? }
self.new(parse(client.post("/tags", body: with_params, options: options)).first, client: client)
end
# Creates a new tag in a workspace or organization.
#
# Every tag is required to be created in a specific workspace or
# organization, and this cannot be changed once set. Note that you can use
# the `workspace` parameter regardless of whether or not it is an
# organization.
#
# Returns the full record of the newly created tag.
#
# workspace - [Id] The workspace or organization to create the tag in.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def create_in_workspace(client, workspace: required("workspace"), options: {}, **data)
self.new(parse(client.post("/workspaces/#{workspace}/tags", body: data, options: options)).first, client: client)
end
# Returns the complete tag record for a single tag.
#
# id - [Id] The tag to get.
# options - [Hash] the request I/O options.
def find_by_id(client, id, options: {})
self.new(parse(client.get("/tags/#{id}", options: options)).first, client: client)
end
# Returns the compact tag records for some filtered set of tags.
# Use one or more of the parameters provided to filter the tags returned.
#
# workspace - [Id] The workspace or organization to filter tags on.
# team - [Id] The team to filter tags on.
# archived - [Boolean] Only return tags whose `archived` field takes on the value of
# this parameter.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_all(client, workspace: nil, team: nil, archived: nil, per_page: 20, options: {})
params = { workspace: workspace, team: team, archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tags", params: params, options: options)), type: self, client: client)
end
# Returns the compact tag records for all tags in the workspace.
#
# workspace - [Id] The workspace or organization to find tags in.
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_workspace(client, workspace: required("workspace"), per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/workspaces/#{workspace}/tags", params: params, options: options)), type: self, client: client)
end
end
# Updates the properties of a tag. Only the fields provided in the `data`
# block will be updated; any unspecified fields will remain unchanged.
#
# When using this method, it is best to specify only those fields you wish
# to change, or else you may overwrite changes made by another user since
# you last retrieved the task.
#
# Returns the complete updated tag record.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def update(options: {}, **data)
refresh_with(parse(client.put("/tags/#{id}", body: data, options: options)).first)
end
# A specific, existing tag can be deleted by making a DELETE request
# on the URL for that tag.
#
# Returns an empty data record.
def delete()
client.delete("/tags/#{id}") && true
end
# Returns the compact task records for all tasks with the given tag.
# Tasks can have more than one tag at a time.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def get_tasks_with_tag(per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tags/#{id}/tasks", params: params, options: options)), type: Task, client: client)
end
end
end
end asana-0.6.0/lib/asana/resources/custom_field_settings.rb 0000644 0001750 0001750 00000003242 12775347346 022434 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# Custom fields are attached to a particular project with the Custom
# Field Settings resource. This resource both represents the many-to-many join
# of the Custom Field and Project as well as stores information that is relevant to that
# particular pairing; for instance, the `is_important` property determines
# some possible application-specific handling of that custom field (see below)
class CustomFieldSetting < Resource
attr_reader :id
attr_reader :created_at
attr_reader :is_important
attr_reader :project
attr_reader :custom_field
class << self
# Returns the plural name of the resource.
def plural_name
'custom_field_settings'
end
# Returns a list of all of the custom fields settings on a project, in compact form. Note that, as in all queries to collections which return compact representation, `opt_fields` and `opt_expand` can be used to include more data than is returned in the compact representation. See the getting started guide on [input/output options](/developers/documentation/getting-started/input-output-options) for more information.
#
# project - [Id] The ID of the project for which to list custom field settings
# options - [Hash] the request I/O options.
def find_by_project(client, project: required("project"), options: {})
Resource.new(parse(client.get("/projects/#{project}/custom_field_settings", options: options)).first, client: client)
end
end
end
end
end asana-0.6.0/lib/asana/resources/webhook.rb 0000644 0001750 0001750 00000012507 12775347346 017501 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# Webhooks allow an application to be notified of changes. This is in addition
# to the ability to fetch those changes directly as
# [Events](/developers/api-reference/events) - in fact, Webhooks are just a way
# to receive Events via HTTP POST at the time they occur instead of polling for
# them. For services accessible via HTTP this is often vastly more convenient,
# and if events are not too frequent can be significantly more efficient.
#
# In both cases, however, changes are represented as Event objects - refer to
# the [Events documentation](/developers/api-reference/events) for more
# information on what data these events contain.
#
# **NOTE:** While Webhooks send arrays of Event objects to their target, the
# Event objects themselves contain *only IDs*, rather than the actual resource
# they are referencing. So while a normal event you receive via GET /events
# would look like this:
#
# {\
# "resource": {\
# "id": 1337,\
# "name": "My Task"\
# },\
# "parent": null,\
# "created_at": "2013-08-21T18:20:37.972Z",\
# "user": {\
# "id": 1123,\
# "name": "Tom Bizarro"\
# },\
# "action": "changed",\
# "type": "task"\
# }
#
# In a Webhook payload you would instead receive this:
#
# {\
# "resource": 1337,\
# "parent": null,\
# "created_at": "2013-08-21T18:20:37.972Z",\
# "user": 1123,\
# "action": "changed",\
# "type": "task"\
# }
#
# Webhooks themselves contain only the information necessary to deliver the
# events to the desired target as they are generated.
class Webhook < Resource
attr_reader :id
attr_reader :resource
attr_reader :target
attr_reader :active
attr_reader :created_at
attr_reader :last_success_at
attr_reader :last_failure_at
attr_reader :last_failure_content
class << self
# Returns the plural name of the resource.
def plural_name
'webhooks'
end
# Establishing a webhook is a two-part process. First, a simple HTTP POST
# similar to any other resource creation. Since you could have multiple
# webhooks we recommend specifying a unique local id for each target.
#
# Next comes the confirmation handshake. When a webhook is created, we will
# send a test POST to the `target` with an `X-Hook-Secret` header as
# described in the
# [Resthooks Security documentation](http://resthooks.org/docs/security/).
# The target must respond with a `200 OK` and a matching `X-Hook-Secret`
# header to confirm that this webhook subscription is indeed expected.
#
# If you do not acknowledge the webhook's confirmation handshake it will
# fail to setup, and you will receive an error in response to your attempt
# to create it. This means you need to be able to receive and complete the
# webhook *while* the POST request is in-flight.
#
# resource - [Id] A resource ID to subscribe to. The resource can be a task or project.
#
# target - [String] The URL to receive the HTTP POST.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def create(client, resource: required("resource"), target: required("target"), options: {}, **data)
with_params = data.merge(resource: resource, target: target).reject { |_,v| v.nil? || Array(v).empty? }
self.new(parse(client.post("/webhooks", body: with_params, options: options)).first, client: client)
end
# Returns the compact representation of all webhooks your app has
# registered for the authenticated user in the given workspace.
#
# workspace - [Id] The workspace to query for webhooks in.
#
# resource - [Id] Only return webhooks for the given resource.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def get_all(client, workspace: required("workspace"), resource: nil, per_page: 20, options: {})
params = { workspace: workspace, resource: resource, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/webhooks", params: params, options: options)), type: self, client: client)
end
# Returns the full record for the given webhook.
#
# id - [Id] The webhook to get.
#
# options - [Hash] the request I/O options.
def get_by_id(client, id, options: {})
self.new(parse(client.get("/webhooks/#{id}", options: options)).first, client: client)
end
end
# This method permanently removes a webhook. Note that it may be possible
# to receive a request that was already in flight after deleting the
# webhook, but no further requests will be issued.
def delete_by_id()
self.class.new(parse(client.delete("/webhooks/#{id}")).first, client: client)
end
end
end
end asana-0.6.0/lib/asana/resources/custom_fields.rb 0000644 0001750 0001750 00000003234 12775347346 020700 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# Custom Fields store the metadata that is used in order to add user-specified
# information to tasks in Asana. Be sure to reference the [Custom
# Fields](/developers/documentation/getting-started/custom-fields) developer
# documentation for more information about how custom fields relate to various
# resources in Asana.
class CustomField < Resource
attr_reader :id
attr_reader :created_at
attr_reader :name
attr_reader :type
attr_reader :enum_options
attr_reader :precision
class << self
# Returns the plural name of the resource.
def plural_name
'custom_fields'
end
# Returns the complete definition of a custom field's metadata.
#
# id - [Id] Globally unique identifier for the custom field.
#
# options - [Hash] the request I/O options.
def find_by_id(client, id, options: {})
self.new(parse(client.get("/custom_fields/#{id}", options: options)).first, client: client)
end
# Returns a list of the compact representation of all of the custom fields in a workspace.
#
# workspace - [Id] The workspace or organization to find custom field definitions in.
# options - [Hash] the request I/O options.
def find_by_workspace(client, workspace: required("workspace"), options: {})
Resource.new(parse(client.get("/workspaces/#{workspace}/custom_fields", options: options)).first, client: client)
end
end
end
end
end asana-0.6.0/lib/asana/resources/attachment.rb 0000644 0001750 0001750 00000003203 12775347346 020164 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# An _attachment_ object represents any file attached to a task in Asana,
# whether it's an uploaded file or one associated via a third-party service
# such as Dropbox or Google Drive.
class Attachment < Resource
attr_reader :id
attr_reader :created_at
attr_reader :download_url
attr_reader :host
attr_reader :name
attr_reader :parent
attr_reader :view_url
class << self
# Returns the plural name of the resource.
def plural_name
'attachments'
end
# Returns the full record for a single attachment.
#
# id - [Id] Globally unique identifier for the attachment.
#
# options - [Hash] the request I/O options.
def find_by_id(client, id, options: {})
self.new(parse(client.get("/attachments/#{id}", options: options)).first, client: client)
end
# Returns the compact records for all attachments on the task.
#
# task - [Id] Globally unique identifier for the task.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_task(client, task: required("task"), per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tasks/#{task}/attachments", params: params, options: options)), type: self, client: client)
end
end
end
end
end asana-0.6.0/lib/asana/resources/team.rb 0000644 0001750 0001750 00000011012 12775347346 016757 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# A _team_ is used to group related projects and people together within an
# organization. Each project in an organization is associated with a team.
class Team < Resource
attr_reader :id
attr_reader :name
class << self
# Returns the plural name of the resource.
def plural_name
'teams'
end
# Returns the full record for a single team.
#
# id - [Id] Globally unique identifier for the team.
#
# options - [Hash] the request I/O options.
def find_by_id(client, id, options: {})
self.new(parse(client.get("/teams/#{id}", options: options)).first, client: client)
end
# Returns the compact records for all teams in the organization visible to
# the authorized user.
#
# organization - [Id] Globally unique identifier for the workspace or organization.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_organization(client, organization: required("organization"), per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/organizations/#{organization}/teams", params: params, options: options)), type: self, client: client)
end
# Returns the compact records for all teams to which user is assigned.
#
# user - [String] An identifier for the user. Can be one of an email address,
# the globally unique identifier for the user, or the keyword `me`
# to indicate the current user making the request.
#
# organization - [Id] The workspace or organization to filter teams on.
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_user(client, user: required("user"), organization: nil, per_page: 20, options: {})
params = { organization: organization, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/users/#{user}/teams", params: params, options: options)), type: self, client: client)
end
end
# Returns the compact records for all users that are members of the team.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def users(per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/teams/#{id}/users", params: params, options: options)), type: User, client: client)
end
# The user making this call must be a member of the team in order to add others.
# The user to add must exist in the same organization as the team in order to be added.
# The user to add can be referenced by their globally unique user ID or their email address.
# Returns the full user record for the added user.
#
# user - [String] An identifier for the user. Can be one of an email address,
# the globally unique identifier for the user, or the keyword `me`
# to indicate the current user making the request.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def add_user(user: required("user"), options: {}, **data)
with_params = data.merge(user: user).reject { |_,v| v.nil? || Array(v).empty? }
User.new(parse(client.post("/teams/#{id}/addUser", body: with_params, options: options)).first, client: client)
end
# The user to remove can be referenced by their globally unique user ID or their email address.
# Removes the user from the specified team. Returns an empty data record.
#
# user - [String] An identifier for the user. Can be one of an email address,
# the globally unique identifier for the user, or the keyword `me`
# to indicate the current user making the request.
#
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def remove_user(user: required("user"), options: {}, **data)
with_params = data.merge(user: user).reject { |_,v| v.nil? || Array(v).empty? }
client.post("/teams/#{id}/removeUser", body: with_params, options: options) && true
end
end
end
end asana-0.6.0/lib/asana/resources/story.rb 0000644 0001750 0001750 00000005412 12775347346 017220 0 ustar pravi pravi ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
### edit it manually.
module Asana
module Resources
# A _story_ represents an activity associated with an object in the Asana
# system. Stories are generated by the system whenever users take actions such
# as creating or assigning tasks, or moving tasks between projects. _Comments_
# are also a form of user-generated story.
#
# Stories are a form of history in the system, and as such they are read-only.
# Once generated, it is not possible to modify a story.
class Story < Resource
attr_reader :id
attr_reader :created_at
attr_reader :created_by
attr_reader :hearted
attr_reader :hearts
attr_reader :num_hearts
attr_reader :text
attr_reader :html_text
attr_reader :target
attr_reader :source
attr_reader :type
class << self
# Returns the plural name of the resource.
def plural_name
'stories'
end
# Returns the compact records for all stories on the task.
#
# task - [Id] Globally unique identifier for the task.
#
# per_page - [Integer] the number of records to fetch per page.
# options - [Hash] the request I/O options.
def find_by_task(client, task: required("task"), per_page: 20, options: {})
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
Collection.new(parse(client.get("/tasks/#{task}/stories", params: params, options: options)), type: self, client: client)
end
# Returns the full record for a single story.
#
# id - [Id] Globally unique identifier for the story.
#
# options - [Hash] the request I/O options.
def find_by_id(client, id, options: {})
self.new(parse(client.get("/stories/#{id}", options: options)).first, client: client)
end
# Adds a comment to a task. The comment will be authored by the
# currently authenticated user, and timestamped when the server receives
# the request.
#
# Returns the full record for the new story added to the task.
#
# task - [Id] Globally unique identifier for the task.
#
# text - [String] The plain text of the comment to add.
# options - [Hash] the request I/O options.
# data - [Hash] the attributes to post.
def create_on_task(client, task: required("task"), text: required("text"), options: {}, **data)
with_params = data.merge(text: text).reject { |_,v| v.nil? || Array(v).empty? }
self.new(parse(client.post("/tasks/#{task}/stories", body: with_params, options: options)).first, client: client)
end
end
end
end
end asana-0.6.0/lib/asana/authentication.rb 0000644 0001750 0001750 00000000311 12775347346 017036 0 ustar pravi pravi require_relative 'authentication/oauth2'
require_relative 'authentication/token_authentication'
module Asana
# Public: Authentication strategies for the Asana API.
module Authentication
end
end
asana-0.6.0/lib/asana/authentication/ 0000755 0001750 0001750 00000000000 12775347346 016516 5 ustar pravi pravi asana-0.6.0/lib/asana/authentication/oauth2.rb 0000644 0001750 0001750 00000003416 12775347346 020251 0 ustar pravi pravi require_relative 'oauth2/bearer_token_authentication'
require_relative 'oauth2/access_token_authentication'
require_relative 'oauth2/client'
module Asana
module Authentication
# Public: Deals with OAuth2 authentication. Contains a function to get an
# access token throught a browserless authentication flow, needed for some
# applications such as CLI applications.
module OAuth2
module_function
# Public: Retrieves an access token through an offline authentication
# flow. If your application can receive HTTP requests, you might want to
# opt for a browser-based flow and use the omniauth-asana gem instead.
#
# Your registered application's redirect_uri should be exactly
# "urn:ietf:wg:oauth:2.0:oob".
#
# client_id - [String] the client id of the registered Asana API
# application.
# client_secret - [String] the client secret of the registered Asana API
# application.
#
# Returns an ::OAuth2::AccessToken object.
#
# Note: This function reads from STDIN and writes to STDOUT. It is meant
# to be used only within the context of a CLI application.
def offline_flow(client_id: required('client_id'),
client_secret: required('client_secret'))
client = Client.new(client_id: client_id,
client_secret: client_secret,
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob')
STDOUT.puts '1. Go to the following URL to authorize the ' \
" application: #{client.authorize_url}"
STDOUT.puts '2. Paste the authorization code here: '
auth_code = STDIN.gets.chomp
client.token_from_auth_code(auth_code)
end
end
end
end
asana-0.6.0/lib/asana/authentication/oauth2/ 0000755 0001750 0001750 00000000000 12775347346 017720 5 ustar pravi pravi asana-0.6.0/lib/asana/authentication/oauth2/client.rb 0000644 0001750 0001750 00000003654 12775347346 021533 0 ustar pravi pravi require 'oauth2'
module Asana
module Authentication
module OAuth2
# Public: Deals with the details of obtaining an OAuth2 authorization URL
# and obtaining access tokens from either authorization codes or refresh
# tokens.
class Client
# Public: Initializes a new client with client credentials associated
# with a registered Asana API application.
#
# client_id - [String] a client id from the registered application
# client_secret - [String] a client secret from the registered
# application
# redirect_uri - [String] a redirect uri from the registered
# application
def initialize(client_id: required('client_id'),
client_secret: required('client_secret'),
redirect_uri: required('redirect_uri'))
@client = ::OAuth2::Client.new(client_id, client_secret,
site: 'https://app.asana.com',
authorize_url: '/-/oauth_authorize',
token_url: '/-/oauth_token')
@redirect_uri = redirect_uri
end
# Public:
# Returns the [String] OAuth2 authorize URL.
def authorize_url
@client.auth_code.authorize_url(redirect_uri: @redirect_uri)
end
# Public: Retrieves a token from an authorization code.
#
# Returns the [::OAuth2::AccessToken] token.
def token_from_auth_code(auth_code)
@client.auth_code.get_token(auth_code, redirect_uri: @redirect_uri)
end
# Public: Retrieves a token from a refresh token.
#
# Returns the refreshed [::OAuth2::AccessToken] token.
def token_from_refresh_token(token)
::OAuth2::AccessToken.new(@client, '', refresh_token: token).refresh!
end
end
end
end
end
asana-0.6.0/lib/asana/authentication/oauth2/bearer_token_authentication.rb 0000644 0001750 0001750 00000002127 12775347346 026006 0 ustar pravi pravi module Asana
module Authentication
module OAuth2
# Public: A mechanism to authenticate with an OAuth2 bearer token obtained
# somewhere, for instance through omniauth-asana.
#
# Note: This authentication mechanism doesn't support token refreshing. If
# you'd like refreshing and you have a refresh token as well as a bearer
# token, you can generate a proper access token with
# {AccessTokenAuthentication.from_refresh_token}.
class BearerTokenAuthentication
# Public: Initializes a new BearerTokenAuthentication with a plain
# bearer token.
#
# bearer_token - [String] a plain bearer token.
def initialize(bearer_token)
@token = bearer_token
end
# Public: Configures a Faraday connection injecting its token as an
# OAuth2 bearer token.
#
# connection - [Faraday::Connection] the Faraday connection instance.
#
# Returns nothing.
def configure(connection)
connection.request :oauth2, @token
end
end
end
end
end
asana-0.6.0/lib/asana/authentication/oauth2/access_token_authentication.rb 0000644 0001750 0001750 00000004067 12775347346 026014 0 ustar pravi pravi module Asana
module Authentication
module OAuth2
# Public: A mechanism to authenticate with an OAuth2 access token (a
# bearer token and a refresh token) or just a refresh token.
class AccessTokenAuthentication
# Public: Builds an AccessTokenAuthentication from a refresh token and
# client credentials, by refreshing into a new one.
#
# refresh_token - [String] a refresh token
# client_id - [String] the client id of the registered Asana API
# Application.
# client_secret - [String] the client secret of the registered Asana API
# Application.
# redirect_uri - [String] the redirect uri of the registered Asana API
# Application.
#
# Returns an [AccessTokenAuthentication] instance with a refreshed
# access token.
def self.from_refresh_token(refresh_token,
client_id: required('client_id'),
client_secret: required('client_secret'),
redirect_uri: required('redirect_uri'))
client = Client.new(client_id: client_id,
client_secret: client_secret,
redirect_uri: redirect_uri)
new(client.token_from_refresh_token(refresh_token))
end
# Public: Initializes a new AccessTokenAuthentication.
#
# access_token - [::OAuth2::AccessToken] An ::OAuth2::AccessToken
# object.
def initialize(access_token)
@token = access_token
end
# Public: Configures a Faraday connection injecting a bearer token,
# auto-refreshing it when needed.
#
# connection - [Faraday::Connection] the Faraday connection instance.
#
# Returns nothing.
def configure(connection)
@token = @token.refresh! if @token.expired?
connection.request :oauth2, @token.token
end
end
end
end
end
asana-0.6.0/lib/asana/authentication/token_authentication.rb 0000644 0001750 0001750 00000000764 12775347346 023271 0 ustar pravi pravi module Asana
module Authentication
# Public: Represents an API token authentication mechanism.
class TokenAuthentication
def initialize(token)
@token = token
end
# Public: Configures a Faraday connection injecting its token as
# basic auth.
#
# builder - [Faraday::Connection] the Faraday connection instance.
#
# Returns nothing.
def configure(connection)
connection.basic_auth(@token, '')
end
end
end
end
asana-0.6.0/lib/asana/ruby2_0_0_compatibility.rb 0000644 0001750 0001750 00000000127 12775347346 020456 0 ustar pravi pravi def required(name)
fail(ArgumentError, "#{name} is a required keyword argument")
end
asana-0.6.0/lib/asana.rb 0000644 0001750 0001750 00000000454 12775347346 014027 0 ustar pravi pravi require 'asana/ruby2_0_0_compatibility'
require 'asana/authentication'
require 'asana/resources'
require 'asana/client'
require 'asana/errors'
require 'asana/http_client'
require 'asana/version'
# Public: Top-level namespace of the Asana API Ruby client.
module Asana
include Asana::Resources
end
asana-0.6.0/Gemfile 0000644 0001750 0001750 00000001266 12775347346 013146 0 ustar pravi pravi source 'https://rubygems.org'
# Specify your gem's dependencies in asana.gemspec
gemspec
group :tools do
# Currently we need to pin the version of Rubocop, due to an incompatibility
# with rubocop-rspec. However, this also solves the problem that Rubocop
# routinely adds new checks which can cause our build to "break" even when no
# changes have been made. In this situation it's better to intentionally
# upgrade Rubocop and fix issues at that time.
gem 'rubocop', '~> 0.35.0'
gem 'rubocop-rspec', '~> 1.2.0'
gem 'guard'
gem 'guard-rspec'
gem 'guard-rubocop'
gem 'guard-yard'
gem 'yard'
gem 'yard-tomdoc'
gem 'byebug'
gem 'simplecov', require: false
end
asana-0.6.0/.yardopts 0000644 0001750 0001750 00000000124 12775347346 013511 0 ustar pravi pravi --title "Asana API Ruby Client"
--readme README.md
--plugin tomdoc
lib
- LICENSE.txt asana-0.6.0/.rspec 0000644 0001750 0001750 00000000075 12775347346 012765 0 ustar pravi pravi --color
--require spec_helper
--require asana
--order random
asana-0.6.0/.codeclimate.yml 0000644 0001750 0001750 00000000101 12775347346 014710 0 ustar pravi pravi languages:
Ruby: true
exclude_paths:
- "lib/asana/resources/*"
asana-0.6.0/.rubocop.yml 0000644 0001750 0001750 00000000371 12775347346 014121 0 ustar pravi pravi AllCops:
Include:
- '**/Rakefile'
Exclude:
- 'bin/**/*'
- 'examples/**/*'
- 'lib/asana/resources/*'
- 'spec/templates/unicorn.rb'
- 'spec/templates/world.rb'
- 'test.rb'
LineLength:
Max: 120
require: rubocop-rspec
asana-0.6.0/Guardfile 0000644 0001750 0001750 00000005277 12775347346 013506 0 ustar pravi pravi # A sample Guardfile
# More info at https://github.com/guard/guard#readme
## Uncomment and set this to only include directories you want to watch
# directories %w(app lib config test spec features)
## Uncomment to clear the screen before every task
# clearing :on
## Guard internally checks for changes in the Guardfile and exits.
## If you want Guard to automatically start up again, run guard in a
## shell loop, e.g.:
##
## $ while bundle exec guard; do echo "Restarting Guard..."; done
##
## Note: if you are using the `directories` clause above and you are not
## watching the project directory ('.'), then you will want to move
## the Guardfile to a watched dir and symlink it back, e.g.
#
# $ mkdir config
# $ mv Guardfile config/
# $ ln -s config/Guardfile .
#
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
# Note: The cmd option is now required due to the increasing number of ways
# rspec may be run, below are examples of the most common uses.
# * bundler: 'bundle exec rspec'
# * bundler binstubs: 'bin/rspec'
# * spring: 'bin/rspec' (This will use spring if running and you have
# installed the spring binstubs per the docs)
# * zeus: 'zeus rspec' (requires the server to be started separately)
# * 'just' rspec: 'rspec'
guard :rspec, cmd: "bundle exec rspec" do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
# Feel free to open issues for suggestions and improvements
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
# Rails files
rails = dsl.rails(view_extensions: %w(erb haml slim))
dsl.watch_spec_files_for(rails.app_files)
dsl.watch_spec_files_for(rails.views)
watch(rails.controllers) do |m|
[
rspec.spec.("routing/#{m[1]}_routing"),
rspec.spec.("controllers/#{m[1]}_controller"),
rspec.spec.("acceptance/#{m[1]}")
]
end
# Rails config changes
watch(rails.spec_helper) { rspec.spec_dir }
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
# Capybara features specs
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
# Turnip features and steps
watch(%r{^spec/acceptance/(.+)\.feature$})
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
end
end
guard :rubocop do
watch(%r{.+\.rb$})
watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
end
guard 'yard' do
watch(%r{app/.+\.rb})
watch(%r{lib/.+\.rb})
watch(%r{ext/.+\.c})
end