openc-asana 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +4 -0
- data/.gitignore +13 -0
- data/.rspec +4 -0
- data/.rubocop.yml +11 -0
- data/.travis.yml +12 -0
- data/.yardopts +5 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +21 -0
- data/Guardfile +86 -0
- data/LICENSE.txt +21 -0
- data/README.md +355 -0
- data/Rakefile +65 -0
- data/examples/Gemfile +6 -0
- data/examples/Gemfile.lock +59 -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 +12 -0
- 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 +92 -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/resource_includes/attachment_uploading.rb +33 -0
- data/lib/asana/resource_includes/collection.rb +68 -0
- data/lib/asana/resource_includes/event.rb +51 -0
- data/lib/asana/resource_includes/event_subscription.rb +14 -0
- data/lib/asana/resource_includes/events.rb +103 -0
- data/lib/asana/resource_includes/registry.rb +63 -0
- data/lib/asana/resource_includes/resource.rb +103 -0
- data/lib/asana/resource_includes/response_helper.rb +14 -0
- data/lib/asana/resources.rb +14 -0
- data/lib/asana/resources/attachment.rb +44 -0
- data/lib/asana/resources/project.rb +154 -0
- data/lib/asana/resources/story.rb +64 -0
- data/lib/asana/resources/tag.rb +120 -0
- data/lib/asana/resources/task.rb +300 -0
- data/lib/asana/resources/team.rb +55 -0
- data/lib/asana/resources/user.rb +72 -0
- data/lib/asana/resources/workspace.rb +91 -0
- data/lib/asana/ruby2_0_0_compatibility.rb +3 -0
- data/lib/asana/version.rb +5 -0
- data/lib/templates/index.js +8 -0
- data/lib/templates/resource.ejs +225 -0
- data/openc-asana.gemspec +32 -0
- data/package.json +7 -0
- metadata +200 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative 'resource'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Asana
|
5
|
+
module Resources
|
6
|
+
# Internal: Global registry of Resource subclasses. It provides lookup from
|
7
|
+
# singular and plural names to the actual class objects.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# class Unicorn < Asana::Resources::Resource
|
12
|
+
# path '/unicorns'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Registry.lookup(:unicorn) # => Unicorn
|
16
|
+
# Registry.lookup_many(:unicorns) # => Unicorn
|
17
|
+
#
|
18
|
+
module Registry
|
19
|
+
class << self
|
20
|
+
# Public: Registers a new resource class.
|
21
|
+
#
|
22
|
+
# resource_klass - [Class] the resource class.
|
23
|
+
#
|
24
|
+
# Returns nothing.
|
25
|
+
def register(resource_klass)
|
26
|
+
resources << resource_klass
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Looks up a resource class by its singular name.
|
30
|
+
#
|
31
|
+
# singular_name - [#to_s] the name of the resource, e.g :unicorn.
|
32
|
+
#
|
33
|
+
# Returns the resource class or {Asana::Resources::Resource}.
|
34
|
+
def lookup(singular_name)
|
35
|
+
resources.detect do |klass|
|
36
|
+
klass.singular_name.to_s == singular_name.to_s
|
37
|
+
end || Resource
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: Looks up a resource class by its plural name.
|
41
|
+
#
|
42
|
+
# plural_name - [#to_s] the plural name of the resource, e.g :unicorns.
|
43
|
+
#
|
44
|
+
# Returns the resource class or {Asana::Resources::Resource}.
|
45
|
+
def lookup_many(plural_name)
|
46
|
+
resources.detect do |klass|
|
47
|
+
klass.plural_name.to_s == plural_name.to_s
|
48
|
+
end || Resource
|
49
|
+
end
|
50
|
+
|
51
|
+
# Internal: A set of Resource classes.
|
52
|
+
#
|
53
|
+
# Returns the Set, defaulting to the empty set.
|
54
|
+
#
|
55
|
+
# Note: this object is a mutable singleton, so it should not be accessed
|
56
|
+
# from multiple threads.
|
57
|
+
def resources
|
58
|
+
@resources ||= Set.new
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require_relative 'registry'
|
2
|
+
require_relative 'response_helper'
|
3
|
+
|
4
|
+
module Asana
|
5
|
+
module Resources
|
6
|
+
# Public: The base resource class which provides some sugar over common
|
7
|
+
# resource functionality.
|
8
|
+
class Resource
|
9
|
+
include ResponseHelper
|
10
|
+
extend ResponseHelper
|
11
|
+
|
12
|
+
def self.inherited(base)
|
13
|
+
Registry.register(base)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(data, client: required('client'))
|
17
|
+
@_client = client
|
18
|
+
@_data = data
|
19
|
+
data.each do |k, v|
|
20
|
+
instance_variable_set(:"@#{k}", v) if respond_to?(k)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# If it has findById, it implements #refresh
|
25
|
+
def refresh
|
26
|
+
if self.class.respond_to?(:find_by_id)
|
27
|
+
self.class.find_by_id(client, id)
|
28
|
+
else
|
29
|
+
fail "#{self.class.name} does not respond to #find_by_id"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Internal: Proxies method calls to the data, wrapping it accordingly and
|
34
|
+
# caching the result by defining a real reader method.
|
35
|
+
#
|
36
|
+
# Returns the value for the requested property.
|
37
|
+
#
|
38
|
+
# Raises a NoMethodError if the property doesn't exist.
|
39
|
+
def method_missing(m, *args)
|
40
|
+
super unless respond_to_missing?(m, *args)
|
41
|
+
cache(m, wrapped(to_h[m.to_s]))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Internal: Guard for the method_missing proxy. Checks if the resource
|
45
|
+
# actually has a specific piece of data at all.
|
46
|
+
#
|
47
|
+
# Returns true if the resource has the property, false otherwise.
|
48
|
+
def respond_to_missing?(m, *)
|
49
|
+
to_h.key?(m.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public:
|
53
|
+
# Returns the raw Hash representation of the data.
|
54
|
+
def to_h
|
55
|
+
@_data
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
attrs = to_h.map { |k, _| "#{k}: #{public_send(k).inspect}" }.join(', ')
|
60
|
+
"#<Asana::#{self.class.name.split('::').last} #{attrs}>"
|
61
|
+
end
|
62
|
+
|
63
|
+
alias_method :inspect, :to_s
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Internal: The Asana::Client instance.
|
68
|
+
def client
|
69
|
+
@_client
|
70
|
+
end
|
71
|
+
|
72
|
+
# Internal: Caches a property and a value by defining a reader method for
|
73
|
+
# it.
|
74
|
+
#
|
75
|
+
# property - [#to_s] the property
|
76
|
+
# value - [Object] the corresponding value
|
77
|
+
#
|
78
|
+
# Returns the value.
|
79
|
+
def cache(property, value)
|
80
|
+
field = :"@#{property}"
|
81
|
+
instance_variable_set(field, value)
|
82
|
+
define_singleton_method(property) { instance_variable_get(field) }
|
83
|
+
value
|
84
|
+
end
|
85
|
+
|
86
|
+
# Internal: Wraps a value in a more useful class if possible, namely a
|
87
|
+
# Resource or a Collection.
|
88
|
+
#
|
89
|
+
# Returns the wrapped value or the plain value if it couldn't be wrapped.
|
90
|
+
def wrapped(value)
|
91
|
+
case value
|
92
|
+
when Hash then Resource.new(value, client: client)
|
93
|
+
when Array then value.map(&method(:wrapped))
|
94
|
+
else value
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def refresh_with(data)
|
99
|
+
self.class.new(data, client: @client)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Asana
|
2
|
+
module Resources
|
3
|
+
# Internal: A helper to make response body parsing easier.
|
4
|
+
module ResponseHelper
|
5
|
+
def parse(response)
|
6
|
+
data = response.body.fetch('data') do
|
7
|
+
fail("Unexpected response body: #{response.body}")
|
8
|
+
end
|
9
|
+
extra = response.body.reject { |k, _| k == 'data' }
|
10
|
+
[data, extra]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'resource_includes/resource'
|
2
|
+
require_relative 'resource_includes/collection'
|
3
|
+
|
4
|
+
Dir[File.join(File.dirname(__FILE__), 'resource_includes', '*.rb')]
|
5
|
+
.each { |resource| require resource }
|
6
|
+
|
7
|
+
Dir[File.join(File.dirname(__FILE__), 'resources', '*.rb')]
|
8
|
+
.each { |resource| require resource }
|
9
|
+
|
10
|
+
module Asana
|
11
|
+
# Public: Contains all the resources that the Asana API can return.
|
12
|
+
module Resources
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
|
2
|
+
### edit it manually.
|
3
|
+
|
4
|
+
module Asana
|
5
|
+
module Resources
|
6
|
+
# An _attachment_ object represents any file attached to a task in Asana,
|
7
|
+
# whether it's an uploaded file or one associated via a third-party service
|
8
|
+
# such as Dropbox or Google Drive.
|
9
|
+
class Attachment < Resource
|
10
|
+
|
11
|
+
|
12
|
+
attr_reader :id
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Returns the plural name of the resource.
|
16
|
+
def plural_name
|
17
|
+
'attachments'
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the full record for a single attachment.
|
21
|
+
#
|
22
|
+
# id - [Id] Globally unique identifier for the attachment.
|
23
|
+
#
|
24
|
+
# options - [Hash] the request I/O options.
|
25
|
+
def find_by_id(client, id, options: {})
|
26
|
+
|
27
|
+
self.new(parse(client.get("/attachments/#{id}", options: options)).first, client: client)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the compact records for all attachments on the task.
|
31
|
+
#
|
32
|
+
# task - [Id] Globally unique identifier for the task.
|
33
|
+
#
|
34
|
+
# per_page - [Integer] the number of records to fetch per page.
|
35
|
+
# options - [Hash] the request I/O options.
|
36
|
+
def find_by_task(client, task: required("task"), per_page: 20, options: {})
|
37
|
+
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
|
38
|
+
Collection.new(parse(client.get("/tasks/#{task}/attachments", params: params, options: options)), type: self, client: client)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
|
2
|
+
### edit it manually.
|
3
|
+
|
4
|
+
module Asana
|
5
|
+
module Resources
|
6
|
+
# A _project_ represents a prioritized list of tasks in Asana. It exists in a
|
7
|
+
# single workspace or organization and is accessible to a subset of users in
|
8
|
+
# that workspace or organization, depending on its permissions.
|
9
|
+
#
|
10
|
+
# Projects in organizations are shared with a single team. You cannot currently
|
11
|
+
# change the team of a project via the API. Non-organization workspaces do not
|
12
|
+
# have teams and so you should not specify the team of project in a
|
13
|
+
# regular workspace.
|
14
|
+
class Project < Resource
|
15
|
+
|
16
|
+
include EventSubscription
|
17
|
+
|
18
|
+
|
19
|
+
attr_reader :id
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Returns the plural name of the resource.
|
23
|
+
def plural_name
|
24
|
+
'projects'
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates a new project in a workspace or team.
|
28
|
+
#
|
29
|
+
# Every project is required to be created in a specific workspace or
|
30
|
+
# organization, and this cannot be changed once set. Note that you can use
|
31
|
+
# the `workspace` parameter regardless of whether or not it is an
|
32
|
+
# organization.
|
33
|
+
#
|
34
|
+
# If the workspace for your project _is_ an organization, you must also
|
35
|
+
# supply a `team` to share the project with.
|
36
|
+
#
|
37
|
+
# Returns the full record of the newly created project.
|
38
|
+
#
|
39
|
+
# workspace - [Id] The workspace or organization to create the project in.
|
40
|
+
# team - [Id] If creating in an organization, the specific team to create the
|
41
|
+
# project in.
|
42
|
+
#
|
43
|
+
# options - [Hash] the request I/O options.
|
44
|
+
# data - [Hash] the attributes to post.
|
45
|
+
def create(client, workspace: required("workspace"), team: nil, options: {}, **data)
|
46
|
+
with_params = data.merge(workspace: workspace, team: team).reject { |_,v| v.nil? || Array(v).empty? }
|
47
|
+
self.new(parse(client.post("/projects", body: with_params, options: options)).first, client: client)
|
48
|
+
end
|
49
|
+
|
50
|
+
# If the workspace for your project _is_ an organization, you must also
|
51
|
+
# supply a `team` to share the project with.
|
52
|
+
#
|
53
|
+
# Returns the full record of the newly created project.
|
54
|
+
#
|
55
|
+
# workspace - [Id] The workspace or organization to create the project in.
|
56
|
+
# options - [Hash] the request I/O options.
|
57
|
+
# data - [Hash] the attributes to post.
|
58
|
+
def create_in_workspace(client, workspace: required("workspace"), options: {}, **data)
|
59
|
+
|
60
|
+
self.new(parse(client.post("/workspaces/#{workspace}/projects", body: data, options: options)).first, client: client)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Creates a project shared with the given team.
|
64
|
+
#
|
65
|
+
# Returns the full record of the newly created project.
|
66
|
+
#
|
67
|
+
# team - [Id] The team to create the project in.
|
68
|
+
# options - [Hash] the request I/O options.
|
69
|
+
# data - [Hash] the attributes to post.
|
70
|
+
def create_in_team(client, team: required("team"), options: {}, **data)
|
71
|
+
|
72
|
+
self.new(parse(client.post("/teams/#{team}/projects", body: data, options: options)).first, client: client)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the complete project record for a single project.
|
76
|
+
#
|
77
|
+
# id - [Id] The project to get.
|
78
|
+
# options - [Hash] the request I/O options.
|
79
|
+
def find_by_id(client, id, options: {})
|
80
|
+
|
81
|
+
self.new(parse(client.get("/projects/#{id}", options: options)).first, client: client)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the compact project records for some filtered set of projects.
|
85
|
+
# Use one or more of the parameters provided to filter the projects returned.
|
86
|
+
#
|
87
|
+
# workspace - [Id] The workspace or organization to filter projects on.
|
88
|
+
# team - [Id] The team to filter projects on.
|
89
|
+
# archived - [Boolean] Only return projects whose `archived` field takes on the value of
|
90
|
+
# this parameter.
|
91
|
+
#
|
92
|
+
# per_page - [Integer] the number of records to fetch per page.
|
93
|
+
# options - [Hash] the request I/O options.
|
94
|
+
def find_all(client, workspace: nil, team: nil, archived: nil, per_page: 20, options: {})
|
95
|
+
params = { workspace: workspace, team: team, archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
|
96
|
+
Collection.new(parse(client.get("/projects", params: params, options: options)), type: self, client: client)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the compact project records for all projects in the workspace.
|
100
|
+
#
|
101
|
+
# workspace - [Id] The workspace or organization to find projects in.
|
102
|
+
# archived - [Boolean] Only return projects whose `archived` field takes on the value of
|
103
|
+
# this parameter.
|
104
|
+
#
|
105
|
+
# per_page - [Integer] the number of records to fetch per page.
|
106
|
+
# options - [Hash] the request I/O options.
|
107
|
+
def find_by_workspace(client, workspace: required("workspace"), archived: nil, per_page: 20, options: {})
|
108
|
+
params = { archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
|
109
|
+
Collection.new(parse(client.get("/workspaces/#{workspace}/projects", params: params, options: options)), type: self, client: client)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the compact project records for all projects in the team.
|
113
|
+
#
|
114
|
+
# team - [Id] The team to find projects in.
|
115
|
+
# archived - [Boolean] Only return projects whose `archived` field takes on the value of
|
116
|
+
# this parameter.
|
117
|
+
#
|
118
|
+
# per_page - [Integer] the number of records to fetch per page.
|
119
|
+
# options - [Hash] the request I/O options.
|
120
|
+
def find_by_team(client, team: required("team"), archived: nil, per_page: 20, options: {})
|
121
|
+
params = { archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
|
122
|
+
Collection.new(parse(client.get("/teams/#{team}/projects", params: params, options: options)), type: self, client: client)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# A specific, existing project can be updated by making a PUT request on the
|
127
|
+
# URL for that project. Only the fields provided in the `data` block will be
|
128
|
+
# updated; any unspecified fields will remain unchanged.
|
129
|
+
#
|
130
|
+
# When using this method, it is best to specify only those fields you wish
|
131
|
+
# to change, or else you may overwrite changes made by another user since
|
132
|
+
# you last retrieved the task.
|
133
|
+
#
|
134
|
+
# Returns the complete updated project record.
|
135
|
+
#
|
136
|
+
# options - [Hash] the request I/O options.
|
137
|
+
# data - [Hash] the attributes to post.
|
138
|
+
def update(options: {}, **data)
|
139
|
+
|
140
|
+
refresh_with(parse(client.put("/projects/#{id}", body: data, options: options)).first)
|
141
|
+
end
|
142
|
+
|
143
|
+
# A specific, existing project can be deleted by making a DELETE request
|
144
|
+
# on the URL for that project.
|
145
|
+
#
|
146
|
+
# Returns an empty data record.
|
147
|
+
def delete()
|
148
|
+
|
149
|
+
client.delete("/projects/#{id}") && true
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
|
2
|
+
### edit it manually.
|
3
|
+
|
4
|
+
module Asana
|
5
|
+
module Resources
|
6
|
+
# A _story_ represents an activity associated with an object in the Asana
|
7
|
+
# system. Stories are generated by the system whenever users take actions such
|
8
|
+
# as creating or assigning tasks, or moving tasks between projects. _Comments_
|
9
|
+
# are also a form of user-generated story.
|
10
|
+
#
|
11
|
+
# Stories are a form of history in the system, and as such they are read-only.
|
12
|
+
# Once generated, it is not possible to modify a story.
|
13
|
+
class Story < Resource
|
14
|
+
|
15
|
+
|
16
|
+
attr_reader :id
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# Returns the plural name of the resource.
|
20
|
+
def plural_name
|
21
|
+
'stories'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the full record for a single story.
|
25
|
+
#
|
26
|
+
# id - [Id] Globally unique identifier for the team.
|
27
|
+
#
|
28
|
+
# options - [Hash] the request I/O options.
|
29
|
+
def find_by_id(client, id, options: {})
|
30
|
+
|
31
|
+
self.new(parse(client.get("/stories/#{id}", options: options)).first, client: client)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the compact records for all stories on the task.
|
35
|
+
#
|
36
|
+
# task - [Id] Globally unique identifier for the task.
|
37
|
+
#
|
38
|
+
# per_page - [Integer] the number of records to fetch per page.
|
39
|
+
# options - [Hash] the request I/O options.
|
40
|
+
def find_by_task(client, task: required("task"), per_page: 20, options: {})
|
41
|
+
params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
|
42
|
+
Collection.new(parse(client.get("/tasks/#{task}/stories", params: params, options: options)), type: self, client: client)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds a comment to a task. The comment will be authored by the
|
46
|
+
# currently authenticated user, and timestamped when the server receives
|
47
|
+
# the request.
|
48
|
+
#
|
49
|
+
# Returns the full record for the new story added to the task.
|
50
|
+
#
|
51
|
+
# task - [Id] Globally unique identifier for the task.
|
52
|
+
#
|
53
|
+
# text - [String] The plain text of the comment to add.
|
54
|
+
# options - [Hash] the request I/O options.
|
55
|
+
# data - [Hash] the attributes to post.
|
56
|
+
def create_on_task(client, task: required("task"), text: required("text"), options: {}, **data)
|
57
|
+
with_params = data.merge(text: text).reject { |_,v| v.nil? || Array(v).empty? }
|
58
|
+
self.new(parse(client.post("/tasks/#{task}/stories", body: with_params, options: options)).first, client: client)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|