tracker_api 1.7.1 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
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