asana 0.0.6 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +9 -9
- data/.codeclimate.yml +4 -0
- data/.gitignore +12 -20
- data/.rspec +4 -0
- data/.rubocop.yml +18 -0
- data/.travis.yml +12 -0
- data/.yardopts +5 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +17 -0
- data/Guardfile +85 -4
- data/LICENSE.txt +21 -0
- data/README.md +264 -135
- data/Rakefile +62 -7
- data/asana.gemspec +27 -21
- data/examples/Gemfile +6 -0
- data/examples/Gemfile.lock +56 -0
- data/examples/api_token.rb +21 -0
- data/examples/cli_app.rb +25 -0
- data/examples/events.rb +38 -0
- data/examples/omniauth_integration.rb +54 -0
- data/lib/asana.rb +8 -11
- data/lib/asana/authentication.rb +8 -0
- data/lib/asana/authentication/oauth2.rb +42 -0
- data/lib/asana/authentication/oauth2/access_token_authentication.rb +51 -0
- data/lib/asana/authentication/oauth2/bearer_token_authentication.rb +32 -0
- data/lib/asana/authentication/oauth2/client.rb +50 -0
- data/lib/asana/authentication/token_authentication.rb +20 -0
- data/lib/asana/client.rb +124 -0
- data/lib/asana/client/configuration.rb +165 -0
- data/lib/asana/errors.rb +90 -0
- data/lib/asana/http_client.rb +155 -0
- data/lib/asana/http_client/environment_info.rb +53 -0
- data/lib/asana/http_client/error_handling.rb +103 -0
- data/lib/asana/http_client/response.rb +32 -0
- data/lib/asana/resources.rb +11 -0
- data/lib/asana/resources/attachment.rb +44 -0
- data/lib/asana/resources/attachment_uploading.rb +33 -0
- data/lib/asana/resources/collection.rb +68 -0
- data/lib/asana/resources/event.rb +49 -0
- data/lib/asana/resources/event_subscription.rb +12 -0
- data/lib/asana/resources/events.rb +101 -0
- data/lib/asana/resources/project.rb +145 -19
- data/lib/asana/resources/registry.rb +62 -0
- data/lib/asana/resources/resource.rb +103 -0
- data/lib/asana/resources/response_helper.rb +14 -0
- data/lib/asana/resources/story.rb +58 -7
- data/lib/asana/resources/tag.rb +111 -19
- data/lib/asana/resources/task.rb +284 -57
- data/lib/asana/resources/team.rb +55 -0
- data/lib/asana/resources/user.rb +65 -10
- data/lib/asana/resources/workspace.rb +79 -34
- data/lib/asana/ruby2_0_0_compatibility.rb +3 -0
- data/lib/asana/version.rb +3 -1
- data/lib/templates/index.js +8 -0
- data/lib/templates/resource.ejs +225 -0
- data/package.json +7 -0
- metadata +91 -51
- data/LICENSE +0 -22
- data/lib/asana/config.rb +0 -23
- data/lib/asana/resource.rb +0 -52
- data/spec/asana/resources/project_spec.rb +0 -63
- data/spec/asana/resources/story_spec.rb +0 -39
- data/spec/asana/resources/tag_spec.rb +0 -63
- data/spec/asana/resources/task_spec.rb +0 -95
- data/spec/asana/resources/user_spec.rb +0 -64
- data/spec/asana/resources/workspace_spec.rb +0 -108
- data/spec/spec_helper.rb +0 -9
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZjVmMmMzNGZiODUxMDA4ODlkODg3NTBhMDNlZTVjYjdmMWU3YjUxYQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
7
|
-
|
6
|
+
ZTI3MWMwYzg5ZDkyNTIzYmUxYmE0NDZmNDJhM2M3ODFmNzdlNGRlYQ==
|
7
|
+
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NDg1YjlmOGQwMjk5MjJmZDllNTc2NDE0ZTQxNWYwYTZlZTg1NWVjYjg4NTRi
|
10
|
+
YTJkMTMyNjYxNjI5NThlMTQ1ZWExZmM2M2MwN2QwYTJmMDQ3NWZiMWE5ZTNl
|
11
|
+
ZmQ2OWYzMGM0ZjMwY2M4NDM4YWQ5NDk0NTYyMDIxMjI1M2VmMzQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MmQ1ZTI4ODRjYjk0MWFiNGMxZWM3ZTkyNDk0OWJkNTk5NzJkMDYxYWFiOTdh
|
14
|
+
ZDEyOGIxZDU5YzQxMjU0NmNlYjJhZDkzZDBkYmY2NGUxMWMyN2FhOGU2MmQ2
|
15
|
+
NzBkYzc1M2EwYWI1MTI5YzhhZmU0MjRiMzY4Nzg1NzVhNjZkZmE=
|
data/.codeclimate.yml
ADDED
data/.gitignore
CHANGED
@@ -1,20 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
lib/bundler/man
|
14
|
-
pkg
|
15
|
-
rdoc
|
16
|
-
spec/fixtures/cassettes
|
17
|
-
spec/reports
|
18
|
-
test/tmp
|
19
|
-
test/version_tmp
|
20
|
-
tmp
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/Gemfile.lock
|
4
|
+
/_yardoc/
|
5
|
+
/coverage/
|
6
|
+
/doc/
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/tmp/
|
10
|
+
/bin/
|
11
|
+
test.rb
|
12
|
+
/node_modules/
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
AllCops:
|
2
|
+
Include:
|
3
|
+
- '**/Rakefile'
|
4
|
+
Exclude:
|
5
|
+
- 'bin/**/*'
|
6
|
+
- 'examples/**/*'
|
7
|
+
- 'lib/asana/resources/attachment.rb'
|
8
|
+
- 'lib/asana/resources/project.rb'
|
9
|
+
- 'lib/asana/resources/story.rb'
|
10
|
+
- 'lib/asana/resources/tag.rb'
|
11
|
+
- 'lib/asana/resources/task.rb'
|
12
|
+
- 'lib/asana/resources/team.rb'
|
13
|
+
- 'lib/asana/resources/user.rb'
|
14
|
+
- 'lib/asana/resources/workspace.rb'
|
15
|
+
- 'spec/templates/unicorn.rb'
|
16
|
+
- 'spec/templates/world.rb'
|
17
|
+
- 'test.rb'
|
18
|
+
require: rubocop-rspec
|
data/.travis.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.2.2
|
4
|
+
- 2.0.0
|
5
|
+
deploy:
|
6
|
+
provider: rubygems
|
7
|
+
api_key:
|
8
|
+
secure: ZFzExKt6auBOcQyg8955GwlZW2OaS64Q+XHGSay2MzjALw1iiy5uuMdwkueCKrGWSv6/eBe9jjsmYBe3OfkUIpIBbUacnbEYeaC70AyucNjvFrrl0YVYHb7neojarJUmKz9bz9Pkju/jdxksaYaj58xfq5YPQDfjFtdmylvuNEYpujT6goPEbxG4U4PpIhhQOZRDRXXAPS+f7jHejTSK06kvJjiJw0d51VJtBbp+0TKNKL6BDKdOKjKeHuebuUmSw8crDyaYdnwYwmNg1cJrGOv2t76M08zoKkkIO2lwPMHisi1/+cbVcZfxM4SfdHJeU6cQuRdb0uCUbbj6GsGwT8vWP2mGUrLe4UV/GfZDmvK3MKeKIlkgig31a3Qny9yjn8EjSnKHYuHBbJvPQDPPpFUfgEneUxn2t4P6m+epkd1gldWqTWf8mhMR/6xAFT4s+BaxnMMJsTC3Ea+dZZ30EqCw/kx5B2Z1KVLgsxHeMN/Q+AeOcbOvlGDsFL0Mjk/PqDTW1AWKLs/D1ohcxjSmlNJGWR6JHa/Ei0GqjDE2+/ZGsKsRfcDD4kU5qnKdqdzDlbL3cL4tChzuWVcguYdrg1yZzqPrCPzmy+2D7Hphyaj9CPKEh7qwT+IQU5o/V2peOJUjKrMlJS4gFq6MvTDh5U59J88Kkg72DXhcEUcySkU=
|
9
|
+
gem: asana
|
10
|
+
on:
|
11
|
+
tags: true
|
12
|
+
repo: Asana/ruby-asana
|
data/.yardopts
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
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.
|
4
|
+
|
5
|
+
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.
|
6
|
+
|
7
|
+
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.
|
8
|
+
|
9
|
+
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.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
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/)
|
data/Gemfile
CHANGED
@@ -2,3 +2,20 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in asana.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
group :tools do
|
7
|
+
gem 'rubocop'
|
8
|
+
gem 'rubocop-rspec'
|
9
|
+
|
10
|
+
gem 'guard'
|
11
|
+
gem 'guard-rspec'
|
12
|
+
gem 'guard-rubocop'
|
13
|
+
gem 'guard-yard'
|
14
|
+
|
15
|
+
gem 'yard'
|
16
|
+
gem 'yard-tomdoc'
|
17
|
+
|
18
|
+
gem 'byebug'
|
19
|
+
|
20
|
+
gem 'simplecov', require: false
|
21
|
+
end
|
data/Guardfile
CHANGED
@@ -1,5 +1,86 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features)
|
6
|
+
|
7
|
+
## Uncomment to clear the screen before every task
|
8
|
+
# clearing :on
|
9
|
+
|
10
|
+
## Guard internally checks for changes in the Guardfile and exits.
|
11
|
+
## If you want Guard to automatically start up again, run guard in a
|
12
|
+
## shell loop, e.g.:
|
13
|
+
##
|
14
|
+
## $ while bundle exec guard; do echo "Restarting Guard..."; done
|
15
|
+
##
|
16
|
+
## Note: if you are using the `directories` clause above and you are not
|
17
|
+
## watching the project directory ('.'), then you will want to move
|
18
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
19
|
+
#
|
20
|
+
# $ mkdir config
|
21
|
+
# $ mv Guardfile config/
|
22
|
+
# $ ln -s config/Guardfile .
|
23
|
+
#
|
24
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
25
|
+
|
26
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
27
|
+
# rspec may be run, below are examples of the most common uses.
|
28
|
+
# * bundler: 'bundle exec rspec'
|
29
|
+
# * bundler binstubs: 'bin/rspec'
|
30
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
31
|
+
# installed the spring binstubs per the docs)
|
32
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
33
|
+
# * 'just' rspec: 'rspec'
|
34
|
+
|
35
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
36
|
+
require "guard/rspec/dsl"
|
37
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
38
|
+
|
39
|
+
# Feel free to open issues for suggestions and improvements
|
40
|
+
|
41
|
+
# RSpec files
|
42
|
+
rspec = dsl.rspec
|
43
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
44
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
45
|
+
watch(rspec.spec_files)
|
46
|
+
|
47
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
48
|
+
|
49
|
+
# Rails files
|
50
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
51
|
+
dsl.watch_spec_files_for(rails.app_files)
|
52
|
+
dsl.watch_spec_files_for(rails.views)
|
53
|
+
|
54
|
+
watch(rails.controllers) do |m|
|
55
|
+
[
|
56
|
+
rspec.spec.("routing/#{m[1]}_routing"),
|
57
|
+
rspec.spec.("controllers/#{m[1]}_controller"),
|
58
|
+
rspec.spec.("acceptance/#{m[1]}")
|
59
|
+
]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Rails config changes
|
63
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
64
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
65
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
66
|
+
|
67
|
+
# Capybara features specs
|
68
|
+
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
|
69
|
+
|
70
|
+
# Turnip features and steps
|
71
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
72
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
73
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
guard :rubocop do
|
78
|
+
watch(%r{.+\.rb$})
|
79
|
+
watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
|
80
|
+
end
|
81
|
+
|
82
|
+
guard 'yard' do
|
83
|
+
watch(%r{app/.+\.rb})
|
84
|
+
watch(%r{lib/.+\.rb})
|
85
|
+
watch(%r{ext/.+\.c})
|
5
86
|
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Asana, Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,16 +1,23 @@
|
|
1
1
|
# Asana
|
2
2
|
|
3
|
-
|
4
|
-
[
|
5
|
-
|
3
|
+
[![Build Status](https://travis-ci.org/Asana/ruby-asana.svg)](https://travis-ci.org/Asana/ruby-asana)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/Asana/ruby-asana/badges/gpa.svg)](https://codeclimate.com/github/Asana/ruby-asana)
|
5
|
+
[![Dependency Status](https://gemnasium.com/Asana/ruby-asana.svg)](https://gemnasium.com/Asana/ruby-asana)
|
6
6
|
|
7
|
-
|
7
|
+
|
8
|
+
A Ruby client for the 1.0 version of the Asana API.
|
9
|
+
|
10
|
+
Supported rubies:
|
11
|
+
|
12
|
+
* MRI 2.0.0 up to 2.2.x stable
|
8
13
|
|
9
14
|
## Installation
|
10
15
|
|
11
16
|
Add this line to your application's Gemfile:
|
12
17
|
|
13
|
-
|
18
|
+
```ruby
|
19
|
+
gem 'ruby-asana'
|
20
|
+
```
|
14
21
|
|
15
22
|
And then execute:
|
16
23
|
|
@@ -18,208 +25,330 @@ And then execute:
|
|
18
25
|
|
19
26
|
Or install it yourself as:
|
20
27
|
|
21
|
-
$ gem install asana
|
28
|
+
$ gem install ruby-asana
|
22
29
|
|
23
30
|
## Usage
|
24
31
|
|
25
|
-
|
32
|
+
To do anything, you'll need always an instance of `Asana::Client` configured
|
33
|
+
with your preferred authentication method (see the Authentication section below
|
34
|
+
for more complex scenarios) and other options.
|
26
35
|
|
36
|
+
The most minimal example would be as follows:
|
27
37
|
|
28
38
|
```ruby
|
29
|
-
|
30
|
-
|
39
|
+
require 'asana'
|
40
|
+
|
41
|
+
client = Asana::Client.new do |c|
|
42
|
+
c.authentication :api_token, 'my_api_token'
|
31
43
|
end
|
44
|
+
|
45
|
+
client.workspaces.find_all.first
|
32
46
|
```
|
33
47
|
|
34
|
-
|
48
|
+
A full-blown customized client using OAuth2 wih a previously obtained refresh
|
49
|
+
token, Typhoeus as a Faraday adapter, a custom user agent and custom Faraday
|
50
|
+
middleware:
|
35
51
|
|
36
52
|
```ruby
|
37
|
-
|
53
|
+
require 'asana'
|
54
|
+
|
55
|
+
client = Asana::Client.new do |c|
|
56
|
+
c.authentication :oauth2,
|
57
|
+
refresh_token: 'abc',
|
58
|
+
client_id: 'bcd',
|
59
|
+
client_secret: 'cde',
|
60
|
+
redirect_uri: 'http://example.org/auth'
|
61
|
+
c.faraday_adapter :typhoeus
|
62
|
+
c.configure_faraday { |conn| conn.use SomeFaradayMiddleware }
|
63
|
+
end
|
64
|
+
|
65
|
+
workspace = client.workspaces.find_by_id(12)
|
66
|
+
workspace.users
|
67
|
+
# => #<Asana::Collection<User> ...>
|
68
|
+
client.tags.create_in_workspace(workspace: workspace.id, name: 'foo')
|
69
|
+
# => #<Asana::Tag id: ..., name: "foo">
|
38
70
|
```
|
39
71
|
|
40
|
-
|
72
|
+
All resources are exposed as methods on the `Asana::Client` instance. Check out
|
73
|
+
the [documentation for each of them][docs].
|
74
|
+
|
75
|
+
### Authentication
|
41
76
|
|
42
|
-
|
43
|
-
> various workspaces, projects, and tasks.
|
44
|
-
>
|
45
|
-
> Like other objects in the system, users are referred to by numerical IDs.
|
46
|
-
> However, the special string identifier me can be used anywhere a user ID is
|
47
|
-
> accepted, to refer to the current authenticated user.
|
77
|
+
This gem supports authenticating against the Asana API with either an API token or through OAuth2.
|
48
78
|
|
49
|
-
|
50
|
-
the API.
|
79
|
+
#### API Token
|
51
80
|
|
52
81
|
```ruby
|
53
|
-
|
54
|
-
|
82
|
+
Asana::Client.new do |c|
|
83
|
+
c.authentication :api_token, 'my_api_token'
|
84
|
+
end
|
85
|
+
```
|
55
86
|
|
56
|
-
|
57
|
-
user = Asana::User.find(:user_id)
|
87
|
+
#### OAuth2
|
58
88
|
|
59
|
-
|
60
|
-
user = Asana::User.me
|
89
|
+
Authenticating through OAuth2 is preferred. There are many ways you can do this.
|
61
90
|
|
62
|
-
|
63
|
-
workspace = Asana::Workspace.find(:workspace_id)
|
64
|
-
users = workspace.users
|
65
|
-
```
|
91
|
+
##### With a plain bearer token (doesn't support auto-refresh)
|
66
92
|
|
67
|
-
|
93
|
+
If you have a plain bearer token obtained somewhere else and you don't mind not
|
94
|
+
having your token auto-refresh, you can authenticate with it as follows:
|
68
95
|
|
69
|
-
|
70
|
-
|
96
|
+
```ruby
|
97
|
+
Asana::Client.new do |c|
|
98
|
+
c.authentication :oauth2, bearer_token: 'my_bearer_token'
|
99
|
+
end
|
100
|
+
```
|
71
101
|
|
72
|
-
|
102
|
+
##### With a refresh token and client credentials
|
103
|
+
|
104
|
+
If you obtained a refresh token, you can use it together with your client
|
105
|
+
credentials to authenticate:
|
73
106
|
|
74
107
|
```ruby
|
75
|
-
|
76
|
-
|
108
|
+
Asana::Client.new do |c|
|
109
|
+
c.authentication :oauth2,
|
110
|
+
refresh_token: 'abc',
|
111
|
+
client_id: 'bcd',
|
112
|
+
client_secret: 'cde',
|
113
|
+
redirect_uri: 'http://example.org/auth'
|
114
|
+
end
|
115
|
+
```
|
77
116
|
|
78
|
-
|
79
|
-
workspace = Asana::Workspace.find(:workspace_id)
|
117
|
+
##### With an ::OAuth2::AccessToken object (from `omniauth-asana` for example)
|
80
118
|
|
81
|
-
|
82
|
-
|
119
|
+
If you use `omniauth-asana` or a browser-based OAuth2 authentication strategy in
|
120
|
+
general, possibly because your application is a web application, you can reuse
|
121
|
+
those credentials to authenticate with this API client. Here's how to do it from
|
122
|
+
the callback method:
|
83
123
|
|
84
|
-
|
85
|
-
|
124
|
+
```ruby
|
125
|
+
# assuming we're using Sinatra and omniauth-asana
|
126
|
+
get '/auth/:name/callback' do
|
127
|
+
creds = request.env["omniauth.auth"]["credentials"].tap { |h| h.delete('expires') }
|
128
|
+
strategy = request.env["omniauth.strategy"]
|
129
|
+
|
130
|
+
# We need to refresh the omniauth OAuth2 token
|
131
|
+
access_token = OAuth2::AccessToken.from_hash(strategy.client, creds).refresh!
|
132
|
+
|
133
|
+
$client = Asana::Client.new do |c|
|
134
|
+
c.authentication :oauth2, access_token
|
135
|
+
end
|
136
|
+
|
137
|
+
redirect '/'
|
138
|
+
end
|
139
|
+
```
|
86
140
|
|
87
|
-
|
88
|
-
users = workspace.users
|
141
|
+
See `examples/omniauth_integration.rb` for a working example of this.
|
89
142
|
|
90
|
-
|
91
|
-
tags = workspace.tags
|
143
|
+
##### Using an OAuth2 offline authentication flow (for CLI applications)
|
92
144
|
|
93
|
-
|
94
|
-
|
145
|
+
If your application can't receive HTTP requests and thus you can't use
|
146
|
+
`omniauth-asana`, for example if it's a CLI application, you can authenticate as
|
147
|
+
follows:
|
95
148
|
|
96
|
-
|
97
|
-
|
149
|
+
```ruby
|
150
|
+
access_token = Asana::Authentication::OAuth2.offline_flow(client_id: ...,
|
151
|
+
client_secret: ...)
|
152
|
+
client = Asana::Client.new do |c|
|
153
|
+
c.authentication :oauth2, access_token
|
154
|
+
end
|
98
155
|
|
99
|
-
|
100
|
-
workspace.create_tag(:name => 'Programming')
|
156
|
+
client.tasks.find_by_id(12)
|
101
157
|
```
|
102
158
|
|
103
|
-
|
159
|
+
This will print an authorization URL on STDOUT, and block until you paste in the
|
160
|
+
authorization code, which you can get by visiting that URL and granting the
|
161
|
+
necessary permissions.
|
104
162
|
|
105
|
-
|
106
|
-
> single workspace and is accessible to a subset of users in that workspace
|
107
|
-
> depending on its permissions.
|
163
|
+
### Pagination
|
108
164
|
|
109
|
-
|
165
|
+
Whenever you ask for a collection of resources, you can provide a number of
|
166
|
+
results per page to fetch, between 1 and 100. If you don't provide any, it
|
167
|
+
defaults to 20.
|
110
168
|
|
111
169
|
```ruby
|
112
|
-
|
113
|
-
|
170
|
+
my_tasks = client.tasks.find_by_tag(tag: tag_id, per_page: 5)
|
171
|
+
# => #<Asana::Collection<Task> ...>
|
172
|
+
```
|
173
|
+
|
174
|
+
An `Asana::Collection` is a paginated collection -- it holds the first
|
175
|
+
`per_page` results, and a reference to the next page if any.
|
114
176
|
|
115
|
-
|
116
|
-
|
177
|
+
When you iterate an `Asana::Collection`, it'll transparently keep fetching all
|
178
|
+
the pages, and caching them along the way:
|
117
179
|
|
118
|
-
|
119
|
-
|
120
|
-
|
180
|
+
```ruby
|
181
|
+
my_tasks.size # => 23, not 5
|
182
|
+
my_tasks.take(14)
|
183
|
+
# => [#<Asana::Task ...>, #<Asana::Task ...>, ... until 14]
|
184
|
+
```
|
121
185
|
|
122
|
-
|
123
|
-
|
186
|
+
#### Manual pagination
|
187
|
+
|
188
|
+
If you only want to deal with one page at a time and manually paginate, you can
|
189
|
+
get the elements of the current page with `#elements` and ask for the next page
|
190
|
+
with `#next_page`, which will return an `Asana::Collection` with the next page
|
191
|
+
of elements:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
my_tasks.elements # => [#<Asana::Task ...>, #<Asana::Task ...>, ... until 5]
|
195
|
+
my_tasks.next_page # => #<Asana::Collection ...>
|
124
196
|
```
|
125
197
|
|
126
|
-
|
198
|
+
#### Lazy pagination
|
127
199
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
200
|
+
Because an `Asana::Collection` represents the entire collection, it is often
|
201
|
+
handy to just take what you need from it, rather than let it fetch all its
|
202
|
+
contents from the network. You can accomplish this by turning it into a lazy
|
203
|
+
collection with `#lazy`:
|
132
204
|
|
133
205
|
```ruby
|
134
|
-
#
|
135
|
-
|
136
|
-
|
206
|
+
# let my_tasks be an Asana::Collection of 10 pages of 100 elements each
|
207
|
+
my_tasks.lazy.drop(120).take(15).to_a
|
208
|
+
# Fetches only 2 pages, enough to get elements 120 to 135
|
209
|
+
# => [#<Asana::Task ...>, #<Asana::Task ...>, ...]
|
210
|
+
```
|
137
211
|
|
138
|
-
|
139
|
-
workspace = Asana::Workspace.find(:workspace_id)
|
140
|
-
tasks = workspace.tasks
|
212
|
+
### Error handling
|
141
213
|
|
142
|
-
|
143
|
-
|
144
|
-
|
214
|
+
In any request against the Asana API, there a number of errors that could
|
215
|
+
arise. Those are well documented in the [Asana API Documentation][apidocs], and
|
216
|
+
are represented as exceptions under the namespace `Asana::Errors`.
|
145
217
|
|
146
|
-
|
147
|
-
|
148
|
-
stories = task.stories
|
218
|
+
All errors are subclasses of `Asana::Errors::APIError`, so make sure to rescue
|
219
|
+
instances of this class if you want to handle them yourself.
|
149
220
|
|
150
|
-
|
151
|
-
workspace.create_task(:name => 'Get milk from the grocery store')
|
221
|
+
### I/O options
|
152
222
|
|
153
|
-
|
154
|
-
|
223
|
+
All requests (except `DELETE`) accept extra I/O options
|
224
|
+
[as documented in the API docs][io]. Just pass an extra `options` hash to any
|
225
|
+
request:
|
155
226
|
|
156
|
-
|
157
|
-
|
227
|
+
```ruby
|
228
|
+
client.tasks.find_by_id(12, options: { expand: ['workspace'] })
|
158
229
|
```
|
159
230
|
|
160
|
-
###
|
231
|
+
### Attachment uploading
|
161
232
|
|
162
|
-
|
163
|
-
|
164
|
-
>
|
165
|
-
> Tags have some metadata associated with them, but it is possible that we will
|
166
|
-
> simplify them in the future so it is not encouraged to rely too heavily on
|
167
|
-
> it. Unlike projects, tags do not provide any ordering on the tasks they are
|
168
|
-
> associated with.
|
233
|
+
To attach a file to a task or a project, you just need its absolute path on your
|
234
|
+
filesystem and its MIME type, and the file will be uploaded for you:
|
169
235
|
|
170
236
|
```ruby
|
171
|
-
|
172
|
-
|
173
|
-
|
237
|
+
task = client.tasks.find_by_id(12)
|
238
|
+
attachment = task.attach(filename: '/absolute/path/to/my/file.png',
|
239
|
+
mime: 'image/png')
|
240
|
+
attachment.name # => 'file.png'
|
241
|
+
```
|
242
|
+
|
243
|
+
### Event streams
|
174
244
|
|
175
|
-
|
176
|
-
|
177
|
-
tasks = tag.tasks
|
245
|
+
To subscribe to an event stream of a task or a project, just call `#events` on
|
246
|
+
it:
|
178
247
|
|
179
|
-
|
180
|
-
|
248
|
+
```ruby
|
249
|
+
task = client.tasks.find_by_id(12)
|
250
|
+
task.events # => #<Asana::Events ...>
|
181
251
|
|
182
|
-
#
|
183
|
-
|
184
|
-
tag.modify(:name => 'Development')
|
252
|
+
# You can do the same with only the task id:
|
253
|
+
events = client.events.for(task.id)
|
185
254
|
```
|
186
255
|
|
187
|
-
|
256
|
+
An `Asana::Events` object is an infinite collection of `Asana::Event`
|
257
|
+
instances. Be warned that if you call `#each` on it, it will block forever!
|
258
|
+
|
259
|
+
Note that, by default, an event stream will wait at least 1 second between
|
260
|
+
polls, but that's configurable with the `wait` parameter:
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
# wait at least 3 and a half seconds between each poll to the API
|
264
|
+
task.events(wait: 3.5) # => #<Asana::Events ...>
|
265
|
+
```
|
188
266
|
|
189
|
-
|
190
|
-
|
191
|
-
> such as creating or assigning tasks, or moving tasks between projects.
|
192
|
-
> Comments are also a form of user-generated story.
|
267
|
+
There are some interesting things you can do with an event stream, as it is a
|
268
|
+
normal Ruby Enumerable. Read below to get some ideas.
|
193
269
|
|
194
|
-
|
270
|
+
#### Subscribe to the event stream with a callback, polling every 2 seconds
|
195
271
|
|
196
272
|
```ruby
|
197
|
-
#
|
198
|
-
|
199
|
-
|
200
|
-
|
273
|
+
# Run this in another thread so that we don't block forever
|
274
|
+
events = client.tasks.find_by_id(12).events(wait: 2)
|
275
|
+
Thread.new do
|
276
|
+
events.each do |event|
|
277
|
+
notify_someone "New event arrived! #{event}"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
```
|
281
|
+
|
282
|
+
#### Make the stream lazy and filter it by a specific pattern
|
201
283
|
|
202
|
-
|
203
|
-
|
284
|
+
To do that we need to call `#lazy` on the `Events` instance, just like with any
|
285
|
+
other `Enumerable`.
|
204
286
|
|
205
|
-
|
206
|
-
|
287
|
+
```ruby
|
288
|
+
events = client.tasks.find_by_id(12).events
|
289
|
+
only_change_events = events.lazy.select { |event| event.action == 'changed' }
|
290
|
+
Thread.new do
|
291
|
+
only_change_events.each do |event|
|
292
|
+
notify_someone "New change event arrived! #{event}"
|
293
|
+
end
|
294
|
+
end
|
207
295
|
```
|
208
296
|
|
297
|
+
## Development
|
298
|
+
|
299
|
+
You'll need Ruby 2.1+ and Node v0.10.26+ / NPM 1.4.3+ installed.
|
300
|
+
|
301
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
302
|
+
`bin/console` for an interactive prompt that will allow you to experiment.
|
303
|
+
|
304
|
+
Run the build with `rake`. This is equivalent to:
|
305
|
+
|
306
|
+
$ rake spec && rake rubocop && rake yard
|
307
|
+
|
308
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
309
|
+
|
310
|
+
## Releasing a new version
|
311
|
+
|
312
|
+
To release a new version, run either of these commands:
|
313
|
+
|
314
|
+
rake bump:patch
|
315
|
+
rake bump:minor
|
316
|
+
rake bump:major
|
317
|
+
|
318
|
+
This will: update `lib/asana/version.rb`, commit and tag the commit. Then you
|
319
|
+
just need to `push --tags` to let Travis build and release the new version to
|
320
|
+
Rubygems:
|
321
|
+
|
322
|
+
git push --tags
|
323
|
+
|
324
|
+
### Code generation
|
325
|
+
|
326
|
+
The specific Asana resource classes (`Tag`, `Workspace`, `Task`, etc) are
|
327
|
+
generated code, hence they shouldn't be modified by hand. The code that
|
328
|
+
generates it lives in `lib/templates/resource.ejs`, and is tested by generating
|
329
|
+
`spec/templates/unicorn.rb` and running `spec/templates/unicorn_spec.rb` as part
|
330
|
+
of the build.
|
331
|
+
|
332
|
+
If you wish to make changes on the code generation script:
|
333
|
+
|
334
|
+
1. Add/modify a spec on `spec/templates/unicorn_spec.rb`
|
335
|
+
2. Add your new feature or change to `lib/templates/resource.ejs`
|
336
|
+
3. Run `rake` or, more granularly, `rake codegen && rspec
|
337
|
+
spec/templates/unicorn_spec.rb`
|
338
|
+
|
339
|
+
Once you're sure your code works, submit a pull request and ask the maintainer
|
340
|
+
to make a release, as they'll need to run a release script from the
|
341
|
+
[asana-api-meta][meta] repository.
|
342
|
+
|
209
343
|
## Contributing
|
210
344
|
|
211
|
-
1. Fork it
|
345
|
+
1. Fork it ( https://github.com/[my-github-username]/asana/fork )
|
212
346
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
213
|
-
3. Commit your changes (`git commit -am '
|
347
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
214
348
|
4. Push to the branch (`git push origin my-new-feature`)
|
215
|
-
5. Create new Pull Request
|
216
|
-
|
217
|
-
[
|
218
|
-
[
|
219
|
-
[
|
220
|
-
[
|
221
|
-
[Workspaces]: http://developer.asana.com/documentation/#workspaces
|
222
|
-
[Projects]: http://developer.asana.com/documentation/#projects
|
223
|
-
[Tasks]: http://developer.asana.com/documentation/#tasks
|
224
|
-
[Stories]: http://developer.asana.com/documentation/#stories
|
225
|
-
[Tags]: http://developer.asana.com/documentation/#tags
|
349
|
+
5. Create a new Pull Request
|
350
|
+
|
351
|
+
[apidocs]: https://asana.com/developers
|
352
|
+
[io]: https://asana.com/developers/documentation/getting-started/input-output-options
|
353
|
+
[docs]: https://asana.github.com/ruby-asana
|
354
|
+
[meta]: https://github.com/asana/asana-api-meta
|