tracker_api 1.8.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +2 -1
  3. data/README.md +4 -0
  4. data/lib/tracker_api.rb +12 -0
  5. data/lib/tracker_api/client.rb +8 -6
  6. data/lib/tracker_api/endpoints/blockers.rb +20 -0
  7. data/lib/tracker_api/endpoints/iteration.rb +35 -0
  8. data/lib/tracker_api/endpoints/release.rb +17 -0
  9. data/lib/tracker_api/endpoints/releases.rb +20 -0
  10. data/lib/tracker_api/endpoints/review.rb +21 -0
  11. data/lib/tracker_api/endpoints/reviews.rb +21 -0
  12. data/lib/tracker_api/endpoints/search.rb +1 -1
  13. data/lib/tracker_api/endpoints/stories.rb +10 -0
  14. data/lib/tracker_api/error.rb +12 -2
  15. data/lib/tracker_api/resources/blocker.rb +18 -0
  16. data/lib/tracker_api/resources/cycle_time_details.rb +21 -0
  17. data/lib/tracker_api/resources/daily_history_container.rb +13 -0
  18. data/lib/tracker_api/resources/iteration.rb +14 -0
  19. data/lib/tracker_api/resources/project.rb +13 -0
  20. data/lib/tracker_api/resources/release.rb +29 -0
  21. data/lib/tracker_api/resources/review.rb +35 -0
  22. data/lib/tracker_api/resources/review_type.rb +15 -0
  23. data/lib/tracker_api/resources/story.rb +26 -0
  24. data/lib/tracker_api/version.rb +1 -1
  25. data/test/client_test.rb +52 -52
  26. data/test/comment_test.rb +13 -13
  27. data/test/error_test.rb +8 -2
  28. data/test/file_attachment_test.rb +4 -4
  29. data/test/iteration_test.rb +31 -0
  30. data/test/minitest_helper.rb +5 -2
  31. data/test/project_test.rb +59 -47
  32. data/test/release_test.rb +22 -0
  33. data/test/review_test.rb +27 -0
  34. data/test/story_test.rb +65 -48
  35. data/test/task_test.rb +3 -3
  36. data/test/vcr/cassettes/create_attachments.json +1 -1
  37. data/test/vcr/cassettes/create_comment_with_attachment.json +1 -1
  38. data/test/vcr/cassettes/delete_an_attachment.json +1 -1
  39. data/test/vcr/cassettes/delete_attachments.json +1 -1
  40. data/test/vcr/cassettes/get_current_iteration.json +1 -1
  41. data/test/vcr/cassettes/get_cycle_time_details.json +1 -0
  42. data/test/vcr/cassettes/get_daily_history_container.json +1 -0
  43. data/test/vcr/cassettes/get_releases.json +1 -0
  44. data/test/vcr/cassettes/get_story_reviews.json +1 -0
  45. data/test/vcr/cassettes/release_stories.json +1 -0
  46. data/test/vcr/cassettes/save_review.json +1 -0
  47. data/test/vcr/cassettes/search_project.json +1 -1
  48. data/test/workspace_test.rb +5 -5
  49. data/tracker_api.gemspec +1 -1
  50. metadata +35 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b93cdb4a9ba207f6a81cdf67cebfddcdf2f3e99b
4
- data.tar.gz: de0f6b60cd0b86cf669fa499efae9acdacd472e5
2
+ SHA256:
3
+ metadata.gz: 7d0c3f349c8d57bc33c44401068ae359892d33a8d8ed2c5c20ed96110fe02b3b
4
+ data.tar.gz: adac66edc9ddf17883734c6908b1cce46cbac8b932702a7c3f31573b0ad38b13
5
5
  SHA512:
6
- metadata.gz: 992fbbca70adb9c0b3ff361eefae0b18d672e74c733d874d8fab8938b336266a8005c8d4c9579a00dfd0ec8d27ef54169174cd2be1c420cf9aa0f3cf9fe0ef0b
7
- data.tar.gz: d265ae95db9afb5e32d795e2beee4bfad199abb37840d00d8661909fefcef6129cd73f1a540275cc8bd9dee1413278da8526b47652c1984d2b4255c64bd89916
6
+ metadata.gz: ea30f0006b6dff8684c3d46381d65ddfc00d3a481fbcea3489f844c7bb2c687b065e618114af8cd4392c63d770fcf1ad1ea86aad1305a5c8fca4f95ba58d7150
7
+ data.tar.gz: e67ea8cd72e9947862bdd58230aecea038e311a14ecb081f65545003f2fb3d7924c680c55a4d34a1711492f9509a2da6c368d1c93689c7b9f92f2a91e8faf267
@@ -5,7 +5,8 @@ rvm:
5
5
  - 2.2
6
6
  - 2.3
7
7
  - 2.4
8
- # - "jruby"
8
+ - 2.5
9
+ - "jruby"
9
10
  # - rbx
10
11
  # - "1.8.7"
11
12
  # uncomment this line if your project needs to run something other than `rake`:
data/README.md CHANGED
@@ -86,6 +86,10 @@ task = story.tasks.first # Get
86
86
  task.complete = true
87
87
  task.save # Mark a task complete
88
88
 
89
+ review = story.reviews.first # Mark a review as complete
90
+ review.status = 'pass'
91
+ review.save
92
+
89
93
  epics = project.epics # Get all epics for a project
90
94
  epic = epics.first
91
95
  label = epic.label # Get an epic's label
@@ -38,8 +38,10 @@ module TrackerApi
38
38
 
39
39
  module Endpoints
40
40
  autoload :Activity, 'tracker_api/endpoints/activity'
41
+ autoload :Blockers, 'tracker_api/endpoints/blockers'
41
42
  autoload :Epic, 'tracker_api/endpoints/epic'
42
43
  autoload :Epics, 'tracker_api/endpoints/epics'
44
+ autoload :Iteration, 'tracker_api/endpoints/iteration'
43
45
  autoload :Iterations, 'tracker_api/endpoints/iterations'
44
46
  autoload :Labels, 'tracker_api/endpoints/labels'
45
47
  autoload :Me, 'tracker_api/endpoints/me'
@@ -62,6 +64,10 @@ module TrackerApi
62
64
  autoload :StoryTransitions, 'tracker_api/endpoints/story_transitions'
63
65
  autoload :Attachment, 'tracker_api/endpoints/attachment'
64
66
  autoload :Attachments, 'tracker_api/endpoints/attachments'
67
+ autoload :Releases, 'tracker_api/endpoints/releases'
68
+ autoload :Release, 'tracker_api/endpoints/release'
69
+ autoload :Review, 'tracker_api/endpoints/review'
70
+ autoload :Reviews, 'tracker_api/endpoints/reviews'
65
71
  end
66
72
 
67
73
  module Resources
@@ -71,6 +77,7 @@ module TrackerApi
71
77
  end
72
78
  autoload :Activity, 'tracker_api/resources/activity'
73
79
  autoload :Account, 'tracker_api/resources/account'
80
+ autoload :Blocker, 'tracker_api/resources/blocker'
74
81
  autoload :Change, 'tracker_api/resources/change'
75
82
  autoload :Epic, 'tracker_api/resources/epic'
76
83
  autoload :EpicsSearchResult, 'tracker_api/resources/epics_search_result'
@@ -93,5 +100,10 @@ module TrackerApi
93
100
  autoload :Webhook, 'tracker_api/resources/webhook'
94
101
  autoload :StoryTransition, 'tracker_api/resources/story_transition'
95
102
  autoload :FileAttachment, 'tracker_api/resources/file_attachment'
103
+ autoload :Release, 'tracker_api/resources/release'
104
+ autoload :CycleTimeDetails, 'tracker_api/resources/cycle_time_details'
105
+ autoload :DailyHistoryContainer, 'tracker_api/resources/daily_history_container'
106
+ autoload :Review, 'tracker_api/resources/review'
107
+ autoload :ReviewType, 'tracker_api/resources/review_type'
96
108
  end
97
109
  end
@@ -25,7 +25,7 @@ module TrackerApi
25
25
  @url = Addressable::URI.parse(url).to_s
26
26
  @api_version = options.fetch(:api_version, '/services/v5')
27
27
  @logger = options.fetch(:logger, ::Logger.new(nil))
28
- adapter = options.fetch(:adapter, :excon)
28
+ adapter = options.fetch(:adapter) { defined?(JRUBY_VERSION) ? :net_http : :excon }
29
29
  connection_options = options.fetch(:connection_options, { ssl: { verify: true } })
30
30
  @auto_paginate = options.fetch(:auto_paginate, true)
31
31
  @token = options[:token]
@@ -36,7 +36,7 @@ module TrackerApi
36
36
  @connection = Faraday.new({ url: @url }.merge(connection_options)) do |builder|
37
37
  # response
38
38
  builder.use Faraday::Response::RaiseError
39
- builder.response :json
39
+ builder.response :json, content_type: /\bjson/ # e.g., 'application/json; charset=utf-8'
40
40
 
41
41
  # request
42
42
  builder.request :multipart
@@ -190,7 +190,8 @@ module TrackerApi
190
190
  opts[:params] = options[:params] || {}
191
191
  opts[:token] = options[:token] || @token
192
192
  headers = { 'User-Agent' => USER_AGENT,
193
- 'X-TrackerToken' => opts.fetch(:token) }.merge(options.fetch(:headers, {}))
193
+ 'X-TrackerToken' => opts.fetch(:token),
194
+ 'Accept' => 'application/json' }.merge(options.fetch(:headers, {}))
194
195
 
195
196
  CONVENIENCE_HEADERS.each do |h|
196
197
  if header = options[h]
@@ -222,11 +223,12 @@ module TrackerApi
222
223
  req.body = body
223
224
  end
224
225
  response
225
- rescue Faraday::Error::ClientError => e
226
- case e.response[:status]
226
+ rescue Faraday::ClientError, Faraday::ServerError => e
227
+ status_code = e.response[:status]
228
+ case status_code
227
229
  when 400..499 then raise TrackerApi::Errors::ClientError.new(e)
228
230
  when 500..599 then raise TrackerApi::Errors::ServerError.new(e)
229
- else raise "Expected 4xx or 5xx HTTP status code"
231
+ else raise "Expected 4xx or 5xx HTTP status code; got #{status_code} instead."
230
232
  end
231
233
  end
232
234
 
@@ -0,0 +1,20 @@
1
+ module TrackerApi
2
+ module Endpoints
3
+ class Blockers
4
+ attr_accessor :client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def get(project_id, story_id, params = {})
11
+ data = client.get("/projects/#{project_id}/stories/#{story_id}/blockers", params: params).body
12
+ raise Errors::UnexpectedData, 'Array of Blockers expected' unless data.is_a? Array
13
+
14
+ data.map do |blocker|
15
+ Resources::Blocker.new({ client: client, project_id: project_id }.merge(blocker))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ module TrackerApi
2
+ module Endpoints
3
+ class Iteration
4
+ attr_accessor :client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def get(project_id, iteration_number)
11
+ data = client.get("/projects/#{project_id}/iterations/#{iteration_number}").body
12
+
13
+ Resources::Iteration.new({ client: client, project_id: project_id }.merge(data))
14
+ end
15
+
16
+ def get_analytics_cycle_time_details(project_id, iteration_number)
17
+ data = client.paginate("/projects/#{project_id}/iterations/#{iteration_number}/analytics/cycle_time_details")
18
+ raise Errors::UnexpectedData, 'Array of cycle time details expected' unless data.is_a? Array
19
+
20
+ data.map do |cycle_time_details|
21
+ Resources::CycleTimeDetails.new(
22
+ { project_id: project_id, iteration_number: iteration_number }.merge(cycle_time_details)
23
+ )
24
+ end
25
+ end
26
+
27
+ def get_history(project_id, iteration_number)
28
+ data = client.get("/projects/#{project_id}/history/iterations/#{iteration_number}/days").body
29
+ raise Errors::UnexpectedData, 'Hash of history data expected' unless data.is_a? Hash
30
+
31
+ Resources::DailyHistoryContainer.new({ project_id: project_id, iteration_number: iteration_number }.merge(data))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ module TrackerApi
2
+ module Endpoints
3
+ class Release
4
+ attr_accessor :client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def get(project_id, id, params={})
11
+ data = client.get("/projects/#{project_id}/releases/#{id}", params: params).body
12
+
13
+ Resources::Release.new({ client: client }.merge(data))
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module TrackerApi
2
+ module Endpoints
3
+ class Releases
4
+ attr_accessor :client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def get(project_id, params={})
11
+ data = client.paginate("/projects/#{project_id}/releases", params: params)
12
+ raise Errors::UnexpectedData, 'Array of releases expected' unless data.is_a? Array
13
+
14
+ data.map do |release|
15
+ Resources::Release.new({ client: client, project_id: project_id }.merge(release))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module TrackerApi
2
+ module Endpoints
3
+ class Review
4
+ attr_accessor :client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def update(review, params = {})
11
+ raise ArgumentError, 'Valid review required to update.' unless review.instance_of?(Resources::Review)
12
+
13
+ data = client.put("/projects/#{review.project_id}/stories/#{review.story_id}/reviews/#{review.id}", params: params).body
14
+
15
+ review.attributes = data
16
+ review.clean!
17
+ review
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module TrackerApi
2
+ module Endpoints
3
+ class Reviews
4
+ attr_accessor :client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def get(project_id, story_id, params={})
11
+ params[:fields] ||= ":default,review_type"
12
+ data = client.paginate("/projects/#{project_id}/stories/#{story_id}/reviews", params: params)
13
+ raise Errors::UnexpectedData, 'Successful responses to this request return an array containing zero or more instances of the review resource. This response was not an array.' unless data.is_a? Array
14
+
15
+ data.map do |review|
16
+ Resources::Review.new({ client: client, project_id: project_id }.merge(review))
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -10,7 +10,7 @@ module TrackerApi
10
10
  def get(project_id, query, options={})
11
11
  raise ArgumentError, 'Valid query string required to search' unless query.is_a?(String)
12
12
 
13
- options.key?(:body) ? options[:body][:query] = query : options[:body] = { query: query }
13
+ options[:params] = { query: query }
14
14
  data = client.get("/projects/#{project_id}/search", options).body
15
15
 
16
16
  raise Errors::UnexpectedData, 'Hash of search results expect' unless data.is_a? Hash
@@ -17,6 +17,16 @@ module TrackerApi
17
17
  Resources::Story.new({ client: client, project_id: project_id }.merge(story))
18
18
  end
19
19
  end
20
+
21
+ def get_release(project_id, release_id, params={})
22
+ data = client.paginate("/projects/#{project_id}/releases/#{release_id}/stories", params: params)
23
+
24
+ raise Errors::UnexpectedData, 'Array of stories expected' unless data.is_a? Array
25
+
26
+ data.map do |story|
27
+ Resources::Story.new({ client: client, project_id: project_id }.merge(story))
28
+ end
29
+ end
20
30
  end
21
31
  end
22
32
  end
@@ -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,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
@@ -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
@@ -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