asana 0.0.6 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +9 -9
  2. data/.codeclimate.yml +4 -0
  3. data/.gitignore +12 -20
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +18 -0
  6. data/.travis.yml +12 -0
  7. data/.yardopts +5 -0
  8. data/CODE_OF_CONDUCT.md +13 -0
  9. data/Gemfile +17 -0
  10. data/Guardfile +85 -4
  11. data/LICENSE.txt +21 -0
  12. data/README.md +264 -135
  13. data/Rakefile +62 -7
  14. data/asana.gemspec +27 -21
  15. data/examples/Gemfile +6 -0
  16. data/examples/Gemfile.lock +56 -0
  17. data/examples/api_token.rb +21 -0
  18. data/examples/cli_app.rb +25 -0
  19. data/examples/events.rb +38 -0
  20. data/examples/omniauth_integration.rb +54 -0
  21. data/lib/asana.rb +8 -11
  22. data/lib/asana/authentication.rb +8 -0
  23. data/lib/asana/authentication/oauth2.rb +42 -0
  24. data/lib/asana/authentication/oauth2/access_token_authentication.rb +51 -0
  25. data/lib/asana/authentication/oauth2/bearer_token_authentication.rb +32 -0
  26. data/lib/asana/authentication/oauth2/client.rb +50 -0
  27. data/lib/asana/authentication/token_authentication.rb +20 -0
  28. data/lib/asana/client.rb +124 -0
  29. data/lib/asana/client/configuration.rb +165 -0
  30. data/lib/asana/errors.rb +90 -0
  31. data/lib/asana/http_client.rb +155 -0
  32. data/lib/asana/http_client/environment_info.rb +53 -0
  33. data/lib/asana/http_client/error_handling.rb +103 -0
  34. data/lib/asana/http_client/response.rb +32 -0
  35. data/lib/asana/resources.rb +11 -0
  36. data/lib/asana/resources/attachment.rb +44 -0
  37. data/lib/asana/resources/attachment_uploading.rb +33 -0
  38. data/lib/asana/resources/collection.rb +68 -0
  39. data/lib/asana/resources/event.rb +49 -0
  40. data/lib/asana/resources/event_subscription.rb +12 -0
  41. data/lib/asana/resources/events.rb +101 -0
  42. data/lib/asana/resources/project.rb +145 -19
  43. data/lib/asana/resources/registry.rb +62 -0
  44. data/lib/asana/resources/resource.rb +103 -0
  45. data/lib/asana/resources/response_helper.rb +14 -0
  46. data/lib/asana/resources/story.rb +58 -7
  47. data/lib/asana/resources/tag.rb +111 -19
  48. data/lib/asana/resources/task.rb +284 -57
  49. data/lib/asana/resources/team.rb +55 -0
  50. data/lib/asana/resources/user.rb +65 -10
  51. data/lib/asana/resources/workspace.rb +79 -34
  52. data/lib/asana/ruby2_0_0_compatibility.rb +3 -0
  53. data/lib/asana/version.rb +3 -1
  54. data/lib/templates/index.js +8 -0
  55. data/lib/templates/resource.ejs +225 -0
  56. data/package.json +7 -0
  57. metadata +91 -51
  58. data/LICENSE +0 -22
  59. data/lib/asana/config.rb +0 -23
  60. data/lib/asana/resource.rb +0 -52
  61. data/spec/asana/resources/project_spec.rb +0 -63
  62. data/spec/asana/resources/story_spec.rb +0 -39
  63. data/spec/asana/resources/tag_spec.rb +0 -63
  64. data/spec/asana/resources/task_spec.rb +0 -95
  65. data/spec/asana/resources/user_spec.rb +0 -64
  66. data/spec/asana/resources/workspace_spec.rb +0 -108
  67. data/spec/spec_helper.rb +0 -9
@@ -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
@@ -1,13 +1,64 @@
1
+ ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
2
+ ### edit it manually.
3
+
1
4
  module Asana
2
- class Story < Asana::Resource
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
3
14
 
4
- alias :destroy :method_not_allowed
5
- alias :update :method_not_allowed
6
15
 
7
- def self.all_by_task(*args)
8
- parent_resources :task
9
- all(*args)
10
- end
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: {})
11
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
12
63
  end
13
64
  end
@@ -1,28 +1,120 @@
1
+ ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
2
+ ### edit it manually.
3
+
1
4
  module Asana
2
- class Tag < Asana::Resource
5
+ module Resources
6
+ # A _tag_ is a label that can be attached to any task in Asana. It exists in a
7
+ # single workspace or organization.
8
+ #
9
+ # Tags have some metadata associated with them, but it is possible that we will
10
+ # simplify them in the future so it is not encouraged to rely too heavily on it.
11
+ # Unlike projects, tags do not provide any ordering on the tasks they
12
+ # are associated with.
13
+ class Tag < Resource
3
14
 
4
- alias :create :method_not_allowed
5
- alias :destroy :method_not_allowed
6
15
 
7
- def self.all_by_task(*args)
8
- parent_resources :task
9
- all(*args)
10
- end
16
+ attr_reader :id
11
17
 
12
- def self.all_by_workspace(*args)
13
- parent_resources :workspace
14
- all(*args)
15
- end
18
+ class << self
19
+ # Returns the plural name of the resource.
20
+ def plural_name
21
+ 'tags'
22
+ end
16
23
 
17
- def tasks
18
- Task.all_by_tag(:params => { :tag_id => self.id })
19
- end
24
+ # Creates a new tag in a workspace or organization.
25
+ #
26
+ # Every tag is required to be created in a specific workspace or
27
+ # organization, and this cannot be changed once set. Note that you can use
28
+ # the `workspace` parameter regardless of whether or not it is an
29
+ # organization.
30
+ #
31
+ # Returns the full record of the newly created tag.
32
+ #
33
+ # workspace - [Id] The workspace or organization to create the tag in.
34
+ # options - [Hash] the request I/O options.
35
+ # data - [Hash] the attributes to post.
36
+ def create(client, workspace: required("workspace"), options: {}, **data)
37
+ with_params = data.merge(workspace: workspace).reject { |_,v| v.nil? || Array(v).empty? }
38
+ self.new(parse(client.post("/tags", body: with_params, options: options)).first, client: client)
39
+ end
20
40
 
21
- def modify(modified_fields)
22
- resource = Resource.new(modified_fields)
23
- response = Tag.put(self.id, nil, resource.to_json)
24
- Tag.new(connection.format.decode(response.body))
25
- end
41
+ # Creates a new tag in a workspace or organization.
42
+ #
43
+ # Every tag is required to be created in a specific workspace or
44
+ # organization, and this cannot be changed once set. Note that you can use
45
+ # the `workspace` parameter regardless of whether or not it is an
46
+ # organization.
47
+ #
48
+ # Returns the full record of the newly created tag.
49
+ #
50
+ # workspace - [Id] The workspace or organization to create the tag in.
51
+ # options - [Hash] the request I/O options.
52
+ # data - [Hash] the attributes to post.
53
+ def create_in_workspace(client, workspace: required("workspace"), options: {}, **data)
54
+
55
+ self.new(parse(client.post("/workspaces/#{workspace}/tags", body: data, options: options)).first, client: client)
56
+ end
57
+
58
+ # Returns the complete tag record for a single tag.
59
+ #
60
+ # id - [Id] The tag to get.
61
+ # options - [Hash] the request I/O options.
62
+ def find_by_id(client, id, options: {})
63
+
64
+ self.new(parse(client.get("/tags/#{id}", options: options)).first, client: client)
65
+ end
26
66
 
67
+ # Returns the compact tag records for some filtered set of tags.
68
+ # Use one or more of the parameters provided to filter the tags returned.
69
+ #
70
+ # workspace - [Id] The workspace or organization to filter tags on.
71
+ # team - [Id] The team to filter tags on.
72
+ # archived - [Boolean] Only return tags whose `archived` field takes on the value of
73
+ # this parameter.
74
+ #
75
+ # per_page - [Integer] the number of records to fetch per page.
76
+ # options - [Hash] the request I/O options.
77
+ def find_all(client, workspace: nil, team: nil, archived: nil, per_page: 20, options: {})
78
+ params = { workspace: workspace, team: team, archived: archived, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
79
+ Collection.new(parse(client.get("/tags", params: params, options: options)), type: self, client: client)
80
+ end
81
+
82
+ # Returns the compact tag records for all tags in the workspace.
83
+ #
84
+ # workspace - [Id] The workspace or organization to find tags in.
85
+ # per_page - [Integer] the number of records to fetch per page.
86
+ # options - [Hash] the request I/O options.
87
+ def find_by_workspace(client, workspace: required("workspace"), per_page: 20, options: {})
88
+ params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
89
+ Collection.new(parse(client.get("/workspaces/#{workspace}/tags", params: params, options: options)), type: self, client: client)
90
+ end
91
+ end
92
+
93
+ # Updates the properties of a tag. Only the fields provided in the `data`
94
+ # block will be updated; any unspecified fields will remain unchanged.
95
+ #
96
+ # When using this method, it is best to specify only those fields you wish
97
+ # to change, or else you may overwrite changes made by another user since
98
+ # you last retrieved the task.
99
+ #
100
+ # Returns the complete updated tag record.
101
+ #
102
+ # options - [Hash] the request I/O options.
103
+ # data - [Hash] the attributes to post.
104
+ def update(options: {}, **data)
105
+
106
+ refresh_with(parse(client.put("/tags/#{id}", body: data, options: options)).first)
107
+ end
108
+
109
+ # A specific, existing tag can be deleted by making a DELETE request
110
+ # on the URL for that tag.
111
+ #
112
+ # Returns an empty data record.
113
+ def delete()
114
+
115
+ client.delete("/tags/#{id}") && true
116
+ end
117
+
118
+ end
27
119
  end
28
120
  end
@@ -1,73 +1,300 @@
1
+ ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not
2
+ ### edit it manually.
3
+
1
4
  module Asana
2
- class Task < Asana::Resource
5
+ module Resources
6
+ # The _task_ is the basic object around which many operations in Asana are
7
+ # centered. In the Asana application, multiple tasks populate the middle pane
8
+ # according to some view parameters, and the set of selected tasks determines
9
+ # the more detailed information presented in the details pane.
10
+ class Task < Resource
3
11
 
4
- def self.all_by_project(*args)
5
- parent_resources :project
6
- all(*args)
7
- end
12
+ include AttachmentUploading
8
13
 
9
- def self.all_by_tag(*args)
10
- parent_resources :tag
11
- all(*args)
12
- end
14
+ include EventSubscription
13
15
 
14
- def self.all_by_workspace(*args)
15
- parent_resources :workspace
16
- all(*args)
17
- end
18
16
 
19
- def modify(modified_fields)
20
- resource = Resource.new(modified_fields)
21
- response = Task.put(self.id, nil, resource.to_json)
22
- Task.new(connection.format.decode(response.body))
23
- end
17
+ attr_reader :assignee
24
18
 
25
- def projects
26
- Project.all_by_task(:params => { :task_id => self.id })
27
- end
19
+ attr_reader :assignee_status
28
20
 
29
- def tags
30
- Tag.all_by_task(:params => { :task_id => self.id })
31
- end
21
+ class << self
22
+ # Returns the plural name of the resource.
23
+ def plural_name
24
+ 'tasks'
25
+ end
32
26
 
33
- def add_project(project_id)
34
- path = "#{self.id}/addProject"
35
- resource = Resource.new({:project => project_id})
36
- Task.post(path, nil, resource.to_json)
37
- self
38
- end
27
+ # Creating a new task is as easy as POSTing to the `/tasks` endpoint
28
+ # with a data block containing the fields you'd like to set on the task.
29
+ # Any unspecified fields will take on default values.
30
+ #
31
+ # Every task is required to be created in a specific workspace, and this
32
+ # workspace cannot be changed once set. The workspace need not be set
33
+ # explicitly if you specify a `project` or a `parent` task instead.
34
+ #
35
+ # options - [Hash] the request I/O options.
36
+ # data - [Hash] the attributes to post.
37
+ def create(client, options: {}, **data)
39
38
 
40
- def add_tag(tag_id)
41
- path = "#{self.id}/addTag"
42
- resource = Resource.new({:tag => tag_id})
43
- Task.post(path, nil, resource.to_json)
44
- self
45
- end
39
+ self.new(parse(client.post("/tasks", body: data, options: options)).first, client: client)
40
+ end
46
41
 
47
- def create_story(*args)
48
- path = "#{self.id}/stories"
49
- options = { :task => self.id }
50
- story = Story.new(options.merge(args.first))
51
- response = Task.post(path, nil, story.to_json)
52
- Story.new(connection.format.decode(response.body))
53
- end
42
+ # Creating a new task is as easy as POSTing to the `/tasks` endpoint
43
+ # with a data block containing the fields you'd like to set on the task.
44
+ # Any unspecified fields will take on default values.
45
+ #
46
+ # Every task is required to be created in a specific workspace, and this
47
+ # workspace cannot be changed once set. The workspace need not be set
48
+ # explicitly if you specify a project or a parent task instead.
49
+ #
50
+ # workspace - [Id] The workspace to create a task in.
51
+ # options - [Hash] the request I/O options.
52
+ # data - [Hash] the attributes to post.
53
+ def create_in_workspace(client, workspace: required("workspace"), options: {}, **data)
54
54
 
55
- def stories
56
- Story.all_by_task(:params => { :task_id => self.id })
57
- end
55
+ self.new(parse(client.post("/workspaces/#{workspace}/tasks", body: data, options: options)).first, client: client)
56
+ end
58
57
 
59
- def create_subtask(*args)
60
- path = "#{self.id}/subtasks"
61
- options = { :task => self.id }
62
- task = Task.new(options.merge(args.first))
63
- response = Task.post(path, nil, task.to_json)
64
- Task.new(connection.format.decode(response.body).merge parent: self)
65
- end
58
+ # Returns the complete task record for a single task.
59
+ #
60
+ # id - [Id] The task to get.
61
+ # options - [Hash] the request I/O options.
62
+ def find_by_id(client, id, options: {})
66
63
 
67
- def subtasks
68
- path = "#{self.id}/subtasks"
69
- Task.get(path, nil).map { |subtask| Task.new(subtask.merge(parent: self)) }
70
- end
64
+ self.new(parse(client.get("/tasks/#{id}", options: options)).first, client: client)
65
+ end
66
+
67
+ # Returns the compact task records for all tasks within the given project,
68
+ # ordered by their priority within the project.
69
+ #
70
+ # projectId - [Id] The project in which to search for tasks.
71
+ # per_page - [Integer] the number of records to fetch per page.
72
+ # options - [Hash] the request I/O options.
73
+ def find_by_project(client, projectId: required("projectId"), per_page: 20, options: {})
74
+ params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
75
+ Collection.new(parse(client.get("/projects/#{projectId}/tasks", params: params, options: options)), type: self, client: client)
76
+ end
77
+
78
+ # Returns the compact task records for all tasks with the given tag.
79
+ #
80
+ # tag - [Id] The tag in which to search for tasks.
81
+ # per_page - [Integer] the number of records to fetch per page.
82
+ # options - [Hash] the request I/O options.
83
+ def find_by_tag(client, tag: required("tag"), per_page: 20, options: {})
84
+ params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
85
+ Collection.new(parse(client.get("/tags/#{tag}/tasks", params: params, options: options)), type: self, client: client)
86
+ end
87
+
88
+ # Returns the compact task records for some filtered set of tasks. Use one
89
+ # or more of the parameters provided to filter the tasks returned.
90
+ #
91
+ # assignee - [Id] The assignee to filter tasks on.
92
+ # workspace - [Id] The workspace or organization to filter tasks on.
93
+ # completed_since - [String] Only return tasks that are either incomplete or that have been
94
+ # completed since this time.
95
+ #
96
+ # modified_since - [String] Only return tasks that have been modified since the given time.
97
+ #
98
+ # per_page - [Integer] the number of records to fetch per page.
99
+ # options - [Hash] the request I/O options.
100
+ # Notes:
101
+ #
102
+ # If you specify `assignee`, you must also specify the `workspace` to filter on.
103
+ #
104
+ # If you specify `workspace`, you must also specify the `assignee` to filter on.
105
+ #
106
+ # A task is considered "modified" if any of its properties change,
107
+ # or associations between it and other objects are modified (e.g.
108
+ # a task being added to a project). A task is not considered modified
109
+ # just because another object it is associated with (e.g. a subtask)
110
+ # is modified. Actions that count as modifying the task include
111
+ # assigning, renaming, completing, and adding stories.
112
+ def find_all(client, assignee: nil, workspace: nil, completed_since: nil, modified_since: nil, per_page: 20, options: {})
113
+ params = { assignee: assignee, workspace: workspace, completed_since: completed_since, modified_since: modified_since, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
114
+ Collection.new(parse(client.get("/tasks", params: params, options: options)), type: self, client: client)
115
+ end
116
+ end
117
+
118
+ # A specific, existing task can be updated by making a PUT request on the
119
+ # URL for that task. Only the fields provided in the `data` block will be
120
+ # updated; any unspecified fields will remain unchanged.
121
+ #
122
+ # When using this method, it is best to specify only those fields you wish
123
+ # to change, or else you may overwrite changes made by another user since
124
+ # you last retrieved the task.
125
+ #
126
+ # Returns the complete updated task record.
127
+ #
128
+ # options - [Hash] the request I/O options.
129
+ # data - [Hash] the attributes to post.
130
+ def update(options: {}, **data)
71
131
 
132
+ refresh_with(parse(client.put("/tasks/#{id}", body: data, options: options)).first)
133
+ end
134
+
135
+ # A specific, existing task can be deleted by making a DELETE request on the
136
+ # URL for that task. Deleted tasks go into the "trash" of the user making
137
+ # the delete request. Tasks can be recovered from the trash within a period
138
+ # of 30 days; afterward they are completely removed from the system.
139
+ #
140
+ # Returns an empty data record.
141
+ def delete()
142
+
143
+ client.delete("/tasks/#{id}") && true
144
+ end
145
+
146
+ # Adds each of the specified followers to the task, if they are not already
147
+ # following. Returns the complete, updated record for the affected task.
148
+ #
149
+ # followers - [Array] An array of followers to add to the task.
150
+ # options - [Hash] the request I/O options.
151
+ # data - [Hash] the attributes to post.
152
+ def add_followers(followers: required("followers"), options: {}, **data)
153
+ with_params = data.merge(followers: followers).reject { |_,v| v.nil? || Array(v).empty? }
154
+ refresh_with(parse(client.post("/tasks/#{id}/addFollowers", body: with_params, options: options)).first)
155
+ end
156
+
157
+ # Removes each of the specified followers from the task if they are
158
+ # following. Returns the complete, updated record for the affected task.
159
+ #
160
+ # followers - [Array] An array of followers to remove from the task.
161
+ # options - [Hash] the request I/O options.
162
+ # data - [Hash] the attributes to post.
163
+ def remove_followers(followers: required("followers"), options: {}, **data)
164
+ with_params = data.merge(followers: followers).reject { |_,v| v.nil? || Array(v).empty? }
165
+ refresh_with(parse(client.post("/tasks/#{id}/removeFollowers", body: with_params, options: options)).first)
166
+ end
167
+
168
+ # Returns a compact representation of all of the projects the task is in.
169
+ #
170
+ # per_page - [Integer] the number of records to fetch per page.
171
+ # options - [Hash] the request I/O options.
172
+ def projects(per_page: 20, options: {})
173
+ params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
174
+ Collection.new(parse(client.get("/tasks/#{id}/projects", params: params, options: options)), type: Project, client: client)
175
+ end
176
+
177
+ # Adds the task to the specified project, in the optional location
178
+ # specified. If no location arguments are given, the task will be added to
179
+ # the beginning of the project.
180
+ #
181
+ # `addProject` can also be used to reorder a task within a project that
182
+ # already contains it.
183
+ #
184
+ # Returns an empty data block.
185
+ #
186
+ # project - [Id] The project to add the task to.
187
+ # insertAfter - [Id] A task in the project to insert the task after, or `null` to
188
+ # insert at the beginning of the list.
189
+ #
190
+ # insertBefore - [Id] A task in the project to insert the task before, or `null` to
191
+ # insert at the end of the list.
192
+ #
193
+ # section - [Id] A section in the project to insert the task into. The task will be
194
+ # inserted at the top of the section.
195
+ #
196
+ # options - [Hash] the request I/O options.
197
+ # data - [Hash] the attributes to post.
198
+ def add_project(project: required("project"), insertAfter: nil, insertBefore: nil, section: nil, options: {}, **data)
199
+ with_params = data.merge(project: project, insertAfter: insertAfter, insertBefore: insertBefore, section: section).reject { |_,v| v.nil? || Array(v).empty? }
200
+ client.post("/tasks/#{id}/addProject", body: with_params, options: options) && true
201
+ end
202
+
203
+ # Removes the task from the specified project. The task will still exist
204
+ # in the system, but it will not be in the project anymore.
205
+ #
206
+ # Returns an empty data block.
207
+ #
208
+ # project - [Id] The project to remove the task from.
209
+ # options - [Hash] the request I/O options.
210
+ # data - [Hash] the attributes to post.
211
+ def remove_project(project: required("project"), options: {}, **data)
212
+ with_params = data.merge(project: project).reject { |_,v| v.nil? || Array(v).empty? }
213
+ client.post("/tasks/#{id}/removeProject", body: with_params, options: options) && true
214
+ end
215
+
216
+ # Returns a compact representation of all of the tags the task has.
217
+ #
218
+ # per_page - [Integer] the number of records to fetch per page.
219
+ # options - [Hash] the request I/O options.
220
+ def tags(per_page: 20, options: {})
221
+ params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
222
+ Collection.new(parse(client.get("/tasks/#{id}/tags", params: params, options: options)), type: Tag, client: client)
223
+ end
224
+
225
+ # Adds a tag to a task. Returns an empty data block.
226
+ #
227
+ # tag - [Id] The tag to add to the task.
228
+ # options - [Hash] the request I/O options.
229
+ # data - [Hash] the attributes to post.
230
+ def add_tag(tag: required("tag"), options: {}, **data)
231
+ with_params = data.merge(tag: tag).reject { |_,v| v.nil? || Array(v).empty? }
232
+ client.post("/tasks/#{id}/addTag", body: with_params, options: options) && true
233
+ end
234
+
235
+ # Removes a tag from the task. Returns an empty data block.
236
+ #
237
+ # tag - [Id] The tag to remove from the task.
238
+ # options - [Hash] the request I/O options.
239
+ # data - [Hash] the attributes to post.
240
+ def remove_tag(tag: required("tag"), options: {}, **data)
241
+ with_params = data.merge(tag: tag).reject { |_,v| v.nil? || Array(v).empty? }
242
+ client.post("/tasks/#{id}/removeTag", body: with_params, options: options) && true
243
+ end
244
+
245
+ # Returns a compact representation of all of the subtasks of a task.
246
+ #
247
+ # per_page - [Integer] the number of records to fetch per page.
248
+ # options - [Hash] the request I/O options.
249
+ def subtasks(per_page: 20, options: {})
250
+ params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
251
+ Collection.new(parse(client.get("/tasks/#{id}/subtasks", params: params, options: options)), type: self.class, client: client)
252
+ end
253
+
254
+ # Makes an existing task a subtask of another. Returns an empty data block.
255
+ #
256
+ # subtask - [Id] The subtask to add to the task.
257
+ # options - [Hash] the request I/O options.
258
+ # data - [Hash] the attributes to post.
259
+ def add_subtask(subtask: required("subtask"), options: {}, **data)
260
+ with_params = data.merge(subtask: subtask).reject { |_,v| v.nil? || Array(v).empty? }
261
+ client.post("/tasks/#{id}/subtasks", body: with_params, options: options) && true
262
+ end
263
+
264
+ # Changes the parent of a task. Each task may only be a subtask of a single
265
+ # parent, or no parent task at all. Returns an empty data block.
266
+ #
267
+ # parent - [Id] The new parent of the task, or `null` for no parent.
268
+ # options - [Hash] the request I/O options.
269
+ # data - [Hash] the attributes to post.
270
+ def set_parent(parent: required("parent"), options: {}, **data)
271
+ with_params = data.merge(parent: parent).reject { |_,v| v.nil? || Array(v).empty? }
272
+ client.post("/tasks/#{id}/setParent", body: with_params, options: options) && true
273
+ end
274
+
275
+ # Returns a compact representation of all of the stories on the task.
276
+ #
277
+ # per_page - [Integer] the number of records to fetch per page.
278
+ # options - [Hash] the request I/O options.
279
+ def stories(per_page: 20, options: {})
280
+ params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? }
281
+ Collection.new(parse(client.get("/tasks/#{id}/stories", params: params, options: options)), type: Story, client: client)
282
+ end
283
+
284
+ # Adds a comment to a task. The comment will be authored by the
285
+ # currently authenticated user, and timestamped when the server receives
286
+ # the request.
287
+ #
288
+ # Returns the full record for the new story added to the task.
289
+ #
290
+ # text - [String] The plain text of the comment to add.
291
+ # options - [Hash] the request I/O options.
292
+ # data - [Hash] the attributes to post.
293
+ def add_comment(text: required("text"), options: {}, **data)
294
+ with_params = data.merge(text: text).reject { |_,v| v.nil? || Array(v).empty? }
295
+ Story.new(parse(client.post("/tasks/#{id}/stories", body: with_params, options: options)).first, client: client)
296
+ end
297
+
298
+ end
72
299
  end
73
300
  end