tracker_api 1.7.1 → 1.11.0

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -1
  3. data/README.md +16 -0
  4. data/lib/tracker_api.rb +19 -0
  5. data/lib/tracker_api/client.rb +16 -35
  6. data/lib/tracker_api/endpoints/attachment.rb +38 -0
  7. data/lib/tracker_api/endpoints/attachments.rb +22 -0
  8. data/lib/tracker_api/endpoints/blockers.rb +20 -0
  9. data/lib/tracker_api/endpoints/comment.rb +9 -2
  10. data/lib/tracker_api/endpoints/iteration.rb +35 -0
  11. data/lib/tracker_api/endpoints/release.rb +17 -0
  12. data/lib/tracker_api/endpoints/releases.rb +20 -0
  13. data/lib/tracker_api/endpoints/reviews.rb +21 -0
  14. data/lib/tracker_api/endpoints/search.rb +1 -1
  15. data/lib/tracker_api/endpoints/stories.rb +10 -0
  16. data/lib/tracker_api/error.rb +12 -2
  17. data/lib/tracker_api/file_utility.rb +16 -0
  18. data/lib/tracker_api/resources/activity.rb +1 -1
  19. data/lib/tracker_api/resources/blocker.rb +18 -0
  20. data/lib/tracker_api/resources/comment.rb +35 -0
  21. data/lib/tracker_api/resources/cycle_time_details.rb +21 -0
  22. data/lib/tracker_api/resources/daily_history_container.rb +13 -0
  23. data/lib/tracker_api/resources/epic.rb +9 -0
  24. data/lib/tracker_api/resources/file_attachment.rb +37 -0
  25. data/lib/tracker_api/resources/iteration.rb +14 -0
  26. data/lib/tracker_api/resources/project.rb +13 -0
  27. data/lib/tracker_api/resources/release.rb +29 -0
  28. data/lib/tracker_api/resources/review.rb +19 -0
  29. data/lib/tracker_api/resources/review_type.rb +15 -0
  30. data/lib/tracker_api/resources/story.rb +49 -6
  31. data/lib/tracker_api/version.rb +1 -1
  32. data/lib/virtus/attribute/nullify_blank.rb +1 -1
  33. data/test/client_test.rb +52 -52
  34. data/test/comment_test.rb +46 -4
  35. data/test/error_test.rb +47 -0
  36. data/test/file_attachment_test.rb +19 -0
  37. data/test/iteration_test.rb +31 -0
  38. data/test/minitest_helper.rb +5 -2
  39. data/test/project_test.rb +59 -47
  40. data/test/release_test.rb +22 -0
  41. data/test/story_test.rb +65 -52
  42. data/test/task_test.rb +3 -3
  43. data/test/vcr/cassettes/create_attachments.json +1 -0
  44. data/test/vcr/cassettes/create_comment.json +1 -1
  45. data/test/vcr/cassettes/create_comment_with_attachment.json +1 -0
  46. data/test/vcr/cassettes/create_story_comment.json +1 -1
  47. data/test/vcr/cassettes/delete_an_attachment.json +1 -0
  48. data/test/vcr/cassettes/delete_attachments.json +1 -0
  49. data/test/vcr/cassettes/delete_comment.json +1 -0
  50. data/test/vcr/cassettes/delete_comments.json +1 -0
  51. data/test/vcr/cassettes/get_current_iteration.json +1 -1
  52. data/test/vcr/cassettes/get_cycle_time_details.json +1 -0
  53. data/test/vcr/cassettes/get_daily_history_container.json +1 -0
  54. data/test/vcr/cassettes/get_releases.json +1 -0
  55. data/test/vcr/cassettes/get_story_in_epic.json +1 -1
  56. data/test/vcr/cassettes/get_story_reviews.json +1 -0
  57. data/test/vcr/cassettes/release_stories.json +1 -0
  58. data/test/vcr/cassettes/search_project.json +1 -1
  59. data/test/workspace_test.rb +5 -5
  60. data/tracker_api.gemspec +3 -2
  61. metadata +68 -9
@@ -5,14 +5,24 @@ module TrackerApi
5
5
  def initialize(wrapped_exception)
6
6
  @wrapped_exception = wrapped_exception
7
7
  @response = wrapped_exception.response
8
- message = if wrapped_exception.is_a?(Faraday::Error::ParsingError)
8
+ message = if wrapped_exception.is_a?(Faraday::ParsingError)
9
9
  wrapped_exception.message
10
- elsif wrapped_exception.is_a?(Faraday::Error::ClientError)
10
+ elsif faraday_response_error?(wrapped_exception)
11
11
  wrapped_exception.response.inspect
12
12
  else
13
13
  wrapped_exception.instance_variable_get(:@wrapped_exception).inspect
14
14
  end
15
15
  super(message)
16
16
  end
17
+
18
+ private
19
+
20
+ # faraday 16.0 re-organized their errors. The errors we're interested in,
21
+ # Faraday::ClientError before 16.0 and Faraday::ServerError introduced in
22
+ # 16.0, are represented by this conditional.
23
+ def faraday_response_error?(wrapped_exception)
24
+ wrapped_exception.is_a?(Faraday::Error) &&
25
+ wrapped_exception.respond_to?(:response)
26
+ end
17
27
  end
18
28
  end
@@ -0,0 +1,16 @@
1
+ module TrackerApi
2
+ class FileUtility
3
+ class << self
4
+ def get_file_upload(file)
5
+ mime_type = MimeMagic.by_path(file)
6
+ { :file => Faraday::UploadIO.new(file, mime_type) }
7
+ end
8
+
9
+ def check_files_exist(files)
10
+ files.each do | file |
11
+ raise ArgumentError, 'Attachment file not found.' unless Pathname.new(file).exist?
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Activity
4
- include Virtus.model
4
+ include Shared::Base
5
5
  include Equalizer.new(:guid)
6
6
 
7
7
  attribute :client
@@ -0,0 +1,18 @@
1
+ module TrackerApi
2
+ module Resources
3
+ class Blocker
4
+ include Shared::Base
5
+
6
+ attribute :client
7
+ attribute :project_id, Integer
8
+
9
+ attribute :story_id, Integer
10
+ attribute :person_id, Integer
11
+ attribute :description, String
12
+ attribute :resolved, Boolean
13
+ attribute :created_at, DateTime
14
+ attribute :updated_at, DateTime
15
+ attribute :kind, String
16
+ end
17
+ end
18
+ end
@@ -14,7 +14,10 @@ module TrackerApi
14
14
  attribute :created_at, DateTime
15
15
  attribute :updated_at, DateTime
16
16
  attribute :file_attachment_ids, [Integer]
17
+ attribute :file_attachments, [FileAttachment]
17
18
  attribute :google_attachment_ids, [Integer]
19
+ attribute :file_attachment_ids_to_add, [Integer]
20
+ attribute :file_attachment_ids_to_remove, [Integer]
18
21
  attribute :commit_identifier, String
19
22
  attribute :commit_type, String
20
23
  attribute :kind, String
@@ -24,6 +27,8 @@ module TrackerApi
24
27
 
25
28
  property :id
26
29
  property :text
30
+ collection :file_attachment_ids_to_add
31
+ collection :file_attachment_ids_to_remove
27
32
  end
28
33
 
29
34
  def save
@@ -31,6 +36,36 @@ module TrackerApi
31
36
 
32
37
  Endpoints::Comment.new(client).update(self, UpdateRepresenter.new(Comment.new(self.dirty_attributes)))
33
38
  end
39
+
40
+ def delete
41
+ raise ArgumentError, 'Cannot delete a comment with an unknown story_id.' if story_id.nil?
42
+
43
+ Endpoints::Comment.new(client).delete(self)
44
+ end
45
+
46
+ # @param [Hash] params attributes to create the comment with
47
+ # @return [Comment] newly created Comment
48
+ def create_attachments(params)
49
+ self.file_attachment_ids_to_add = Endpoints::Attachments.new(client).create(self, params[:files]).collect(&:id)
50
+ save
51
+ end
52
+
53
+ def delete_attachments(attachment_ids = nil)
54
+ self.file_attachment_ids_to_remove = attachment_ids || attachments.collect(&:id)
55
+ save
56
+ end
57
+
58
+ # Provides a list of all the attachments on the comment.
59
+ #
60
+ # @reload Boolean to reload the attachments
61
+ # @return [Array[FileAttachments]]
62
+ def attachments(reload: false)
63
+ if !reload && @file_attachments.present?
64
+ @file_attachments
65
+ else
66
+ @file_attachments = Endpoints::Attachment.new(client).get(self)
67
+ end
68
+ end
34
69
  end
35
70
  end
36
71
  end
@@ -0,0 +1,21 @@
1
+ module TrackerApi
2
+ module Resources
3
+ class CycleTimeDetails
4
+ include Shared::Base
5
+
6
+ attribute :project_id, Integer
7
+ attribute :iteration_number, Integer
8
+ attribute :total_cycle_time, Integer
9
+ attribute :started_time, Integer
10
+ attribute :started_count, Integer
11
+ attribute :finished_time, Integer
12
+ attribute :finished_count, Integer
13
+ attribute :delivered_time, Integer
14
+ attribute :delivered_count, Integer
15
+ attribute :rejected_time, Integer
16
+ attribute :rejected_count, Integer
17
+ attribute :story_id, Integer
18
+ attribute :kind, String
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module TrackerApi
2
+ module Resources
3
+ class DailyHistoryContainer
4
+ include Shared::Base
5
+
6
+ attribute :project_id, Integer
7
+ attribute :iteration_number, Integer
8
+ attribute :header, [String]
9
+ attribute :data, [Enumerable]
10
+ attribute :kind, String
11
+ end
12
+ end
13
+ end
@@ -34,6 +34,15 @@ module TrackerApi
34
34
 
35
35
  Endpoints::Epic.new(client).update(self, UpdateRepresenter.new(self))
36
36
  end
37
+
38
+ # @param [Hash] params attributes to create the comment with
39
+ # @return [Comment] newly created Comment
40
+ def create_comment(params)
41
+ files = params.delete(:files)
42
+ comment = Endpoints::Comment.new(client).create(project_id, id, params)
43
+ comment.create_attachments(files: files) if files.present?
44
+ comment
45
+ end
37
46
  end
38
47
  end
39
48
  end
@@ -0,0 +1,37 @@
1
+ module TrackerApi
2
+ module Resources
3
+ class FileAttachment
4
+ include Shared::Base
5
+
6
+ attribute :comment, Comment
7
+
8
+ attribute :id, Integer
9
+ attribute :big_url, String
10
+ attribute :content_type, String
11
+ attribute :created_at, DateTime
12
+ attribute :download_url, String
13
+ attribute :filename, String
14
+ attribute :height, Integer
15
+ attribute :kind, String
16
+ attribute :size, Integer
17
+ attribute :thumbnail_url, String
18
+ attribute :thumbnailable, Boolean
19
+ attribute :uploaded, Boolean
20
+ attribute :uploader_id, Integer
21
+ attribute :width, Integer
22
+
23
+ def delete
24
+ comment.delete_attachments([id])
25
+ end
26
+
27
+ # TODO : Implement download properly.
28
+ # Look at Attchment#download for more details
29
+ # The big_url actually has the AWS S3 link for the file
30
+ # def download
31
+ # file_data = Endpoints::Attachment.new(comment.client).download(download_url)
32
+ # File.open(filename, 'wb') { |fp| fp.write(file_data) }
33
+ # end
34
+ end
35
+ end
36
+ end
37
+
@@ -24,6 +24,20 @@ module TrackerApi
24
24
  def stories=(data)
25
25
  super.each { |s| s.client = client }
26
26
  end
27
+
28
+ # Provides a list of all the cycle_time_details of each story in the iteration.
29
+ #
30
+ # @return [Array[CycleTimeDetails]] array of cycle_time_details of iterations in this project
31
+ def cycle_time_details
32
+ Endpoints::Iteration.new(client).get_analytics_cycle_time_details(project_id, number)
33
+ end
34
+
35
+ # Returns per day information of story points and counts by state for the given iteration.
36
+ #
37
+ # @return [DailyHistoryContainer]
38
+ def get_history
39
+ Endpoints::Iteration.new(client).get_history(project_id, number)
40
+ end
27
41
  end
28
42
  end
29
43
  end
@@ -131,6 +131,19 @@ module TrackerApi
131
131
  Endpoints::Stories.new(client).get(id, params)
132
132
  end
133
133
 
134
+ # Provides a list of all the releases in the project.
135
+ #
136
+ # @param [Hash] params
137
+ # @option params [String] :with_state A release's current_state which all returned releases must match.
138
+ # Valid enumeration values: accepted, delivered, finished, started, rejected, unstarted, unscheduled
139
+ # @option params [Integer] :offset With the first release in your priority list as 0,
140
+ # the index of the first release you want returned.
141
+ # @option params [Integer] :limit The number of releases you want returned.
142
+ # @return [Array[Release]] releases associated with this project
143
+ def releases(params={})
144
+ Endpoints::Releases.new(client).get(id, params)
145
+ end
146
+
134
147
  # Provides a list of all the memberships in the project.
135
148
  #
136
149
  # @param [Hash] params
@@ -0,0 +1,29 @@
1
+ module TrackerApi
2
+ module Resources
3
+ class Release
4
+ include Shared::Base
5
+
6
+ attribute :client
7
+
8
+ attribute :project_id, Integer
9
+ attribute :name, String
10
+ attribute :description, String
11
+ attribute :current_state, String # (accepted, delivered, finished, started, rejected, planned, unstarted, unscheduled)
12
+ attribute :accepted_at, DateTime
13
+ attribute :deadline, DateTime
14
+ attribute :labels, [Label]
15
+ attribute :created_at, DateTime
16
+ attribute :updated_at, DateTime
17
+ attribute :url, String
18
+ attribute :kind, String
19
+
20
+ # Provides a list of all the stories in the release.
21
+ #
22
+ # @param [Hash] params
23
+ # @return [Array[Story]] stories of this release
24
+ def stories(params={})
25
+ Endpoints::Stories.new(client).get_release(project_id, id, params)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module TrackerApi
2
+ module Resources
3
+ class Review
4
+ include Shared::Base
5
+
6
+ attribute :client
7
+
8
+ attribute :id, Integer
9
+ attribute :story_id, Integer
10
+ attribute :review_type_id, Integer
11
+ attribute :reviewer_id, Integer
12
+ attribute :status, String # (unstarted, in_review, pass, revise)
13
+ attribute :created_at, DateTime
14
+ attribute :updated_at, DateTime
15
+ attribute :kind, String
16
+ attribute :review_type, ReviewType
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module TrackerApi
2
+ module Resources
3
+ class ReviewType
4
+ include Shared::Base
5
+
6
+ attribute :id, Integer
7
+ attribute :project_id, Integer
8
+ attribute :name, String
9
+ attribute :hidden, Boolean
10
+ attribute :created_at, DateTime
11
+ attribute :updated_at, DateTime
12
+ attribute :kind, String
13
+ end
14
+ end
15
+ end
@@ -8,6 +8,7 @@ module TrackerApi
8
8
  attribute :accepted_at, DateTime
9
9
  attribute :after_id, Integer
10
10
  attribute :before_id, Integer
11
+ attribute :blockers, [Blocker]
11
12
  attribute :comment_ids, [Integer]
12
13
  attribute :comments, [Comment]
13
14
  attribute :created_at, DateTime
@@ -21,7 +22,7 @@ module TrackerApi
21
22
  attribute :integration_id, Integer
22
23
  attribute :kind, String
23
24
  attribute :label_ids, [Integer]
24
- attribute :labels, [Label], default: nil
25
+ attribute :labels, [Label]
25
26
  attribute :name, String
26
27
  attribute :owned_by_id, Integer # deprecated!
27
28
  attribute :owned_by, Person
@@ -31,6 +32,7 @@ module TrackerApi
31
32
  attribute :project_id, Integer
32
33
  attribute :requested_by, Person
33
34
  attribute :requested_by_id, Integer
35
+ attribute :reviews, [Review]
34
36
  attribute :story_type, String # (feature, bug, chore, release)
35
37
  attribute :task_ids, [Integer]
36
38
  attribute :tasks, [Task]
@@ -54,7 +56,21 @@ module TrackerApi
54
56
  property :deadline
55
57
  property :requested_by_id
56
58
  property :owner_ids, if: ->(_) { !owner_ids.blank? }
57
- collection :labels, class: Label, decorator: Label::UpdateRepresenter, render_empty: true
59
+ property :project_id
60
+
61
+ # Use render_empty: false to address: https://github.com/dashofcode/tracker_api/issues/110
62
+ # - The default value of the labels attribute in Resources::Story is an empty array.
63
+ # - If the value of labels is not change (i.e. not dirty) then when a new Story
64
+ # is created from the dirty attributes in the save method the labels attributes becomes
65
+ # an empty array again. render_empty: false keeps this from rendering in the json passed
66
+ # in the API PUT request. It is is empty then the labels will be cleared.
67
+ # - The next issue is that there is no way to delete all the labels from a Story with
68
+ # the current implementation.
69
+ #
70
+ # NOTE: There are two solutions: 1) remove dirty tracking 2) rewrite without virtus
71
+ # SEE: https://github.com/dashofcode/tracker_api/pull/98
72
+ collection :labels, class: Label, decorator: Label::UpdateRepresenter, render_empty: false
73
+
58
74
  property :integration_id
59
75
  property :external_id
60
76
  end
@@ -104,15 +120,19 @@ module TrackerApi
104
120
  Endpoints::Activity.new(client).get_story(project_id, id, params)
105
121
  end
106
122
 
123
+ def blockers(params = {})
124
+ Endpoints::Blockers.new(client).get(project_id, id, params)
125
+ end
126
+
107
127
  # Provides a list of all the comments on the story.
108
128
  #
109
129
  # @param [Hash] params
110
130
  # @return [Array[Comment]]
111
- def comments(params = {})
112
- if params.blank? && @comments.present?
131
+ def comments(reload: false)
132
+ if !reload && @comments.present?
113
133
  @comments
114
134
  else
115
- @comments = Endpoints::Comments.new(client).get(project_id, id, params)
135
+ @comments = Endpoints::Comments.new(client).get(project_id, id)
116
136
  end
117
137
  end
118
138
 
@@ -152,6 +172,17 @@ module TrackerApi
152
172
  end
153
173
  end
154
174
 
175
+ # Returns the story's original ("undirtied") project_id
176
+ #
177
+ # @return Integer
178
+ def project_id
179
+ if dirty_attributes.key?(:project_id)
180
+ original_attributes[:project_id]
181
+ else
182
+ @project_id
183
+ end
184
+ end
185
+
155
186
  # @param [Hash] params attributes to create the task with
156
187
  # @return [Task] newly created Task
157
188
  def create_task(params)
@@ -161,15 +192,27 @@ module TrackerApi
161
192
  # @param [Hash] params attributes to create the comment with
162
193
  # @return [Comment] newly created Comment
163
194
  def create_comment(params)
164
- Endpoints::Comment.new(client).create(project_id, id, params)
195
+ files = params.delete(:files)
196
+ comment = Endpoints::Comment.new(client).create(project_id, id, params)
197
+ comment.create_attachments(files: files) if files.present?
198
+ comment
165
199
  end
166
200
 
167
201
  # Save changes to an existing Story.
168
202
  def save
169
203
  raise ArgumentError, 'Can not update a story with an unknown project_id.' if project_id.nil?
204
+ return self unless dirty?
170
205
 
171
206
  Endpoints::Story.new(client).update(self, UpdateRepresenter.new(Story.new(self.dirty_attributes)))
172
207
  end
208
+
209
+ def reviews(params = {})
210
+ if params.blank? && @reviews.present?
211
+ @reviews
212
+ else
213
+ @reviews = Endpoints::Reviews.new(client).get(project_id, id, params)
214
+ end
215
+ end
173
216
  end
174
217
  end
175
218
  end