From d33b0e7ae988bb1c86f972625b26d53b3f42a7a6 Mon Sep 17 00:00:00 2001 From: Michael McCormick Date: Thu, 4 Jun 2020 07:43:23 -0500 Subject: [PATCH] Adds reviews so someone can fetch story.reviews. A review_type is tightly coupled to a review. It is accessible as a field customization for reviews but not accessible via a distinct api endpoint. Since it is difficult to understand a review without the review_type, I made the decision to include default review fields and review_type when calling get story/{story_id}/ reviews. The review types are hydrated automatically. --- lib/tracker_api.rb | 3 +++ lib/tracker_api/endpoints/reviews.rb | 21 +++++++++++++++++++++ lib/tracker_api/resources/review.rb | 19 +++++++++++++++++++ lib/tracker_api/resources/review_type.rb | 15 +++++++++++++++ lib/tracker_api/resources/story.rb | 9 +++++++++ test/story_test.rb | 17 +++++++++++++++++ test/vcr/cassettes/get_story_reviews.json | 1 + 7 files changed, 85 insertions(+) create mode 100644 lib/tracker_api/endpoints/reviews.rb create mode 100644 lib/tracker_api/resources/review.rb create mode 100644 lib/tracker_api/resources/review_type.rb create mode 100644 test/vcr/cassettes/get_story_reviews.json diff --git a/lib/tracker_api.rb b/lib/tracker_api.rb index c4f8be7..5ed81c3 100644 --- a/lib/tracker_api.rb +++ b/lib/tracker_api.rb @@ -64,6 +64,7 @@ module Endpoints autoload :Attachments, 'tracker_api/endpoints/attachments' autoload :Releases, 'tracker_api/endpoints/releases' autoload :Release, 'tracker_api/endpoints/release' + autoload :Reviews, 'tracker_api/endpoints/reviews' end module Resources @@ -96,5 +97,7 @@ module Shared autoload :StoryTransition, 'tracker_api/resources/story_transition' autoload :FileAttachment, 'tracker_api/resources/file_attachment' autoload :Release, 'tracker_api/resources/release' + autoload :Review, 'tracker_api/resources/review' + autoload :ReviewType, 'tracker_api/resources/review_type' end end diff --git a/lib/tracker_api/endpoints/reviews.rb b/lib/tracker_api/endpoints/reviews.rb new file mode 100644 index 0000000..09e4b88 --- /dev/null +++ b/lib/tracker_api/endpoints/reviews.rb @@ -0,0 +1,21 @@ +module TrackerApi + module Endpoints + class Reviews + attr_accessor :client + + def initialize(client) + @client = client + end + + def get(project_id, story_id, params={}) + params[:fields] ||= ":default,review_type" + data = client.paginate("/projects/#{project_id}/stories/#{story_id}/reviews", params: params) + 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 + + data.map do |review| + Resources::Review.new({ client: client, project_id: project_id }.merge(review)) + end + end + end + end +end \ No newline at end of file diff --git a/lib/tracker_api/resources/review.rb b/lib/tracker_api/resources/review.rb new file mode 100644 index 0000000..c37395a --- /dev/null +++ b/lib/tracker_api/resources/review.rb @@ -0,0 +1,19 @@ +module TrackerApi + module Resources + class Review + include Shared::Base + + attribute :client + + attribute :id, Integer + attribute :story_id, Integer + attribute :review_type_id, Integer + attribute :reviewer_id, Integer + attribute :status, String # (unstarted, in_review, pass, revise) + attribute :created_at, DateTime + attribute :updated_at, DateTime + attribute :kind, String + attribute :review_type, ReviewType + end + end +end \ No newline at end of file diff --git a/lib/tracker_api/resources/review_type.rb b/lib/tracker_api/resources/review_type.rb new file mode 100644 index 0000000..894294f --- /dev/null +++ b/lib/tracker_api/resources/review_type.rb @@ -0,0 +1,15 @@ +module TrackerApi + module Resources + class ReviewType + include Shared::Base + + attribute :id, Integer + attribute :project_id, Integer + attribute :name, String + attribute :hidden, Boolean + attribute :created_at, DateTime + attribute :updated_at, DateTime + attribute :kind, String + end + end +end \ No newline at end of file diff --git a/lib/tracker_api/resources/story.rb b/lib/tracker_api/resources/story.rb index c34f41f..b43c841 100644 --- a/lib/tracker_api/resources/story.rb +++ b/lib/tracker_api/resources/story.rb @@ -31,6 +31,7 @@ class Story attribute :project_id, Integer attribute :requested_by, Person attribute :requested_by_id, Integer + attribute :reviews, [Review] attribute :story_type, String # (feature, bug, chore, release) attribute :task_ids, [Integer] attribute :tasks, [Task] @@ -199,6 +200,14 @@ def save Endpoints::Story.new(client).update(self, UpdateRepresenter.new(Story.new(self.dirty_attributes))) end + + def reviews(params = {}) + if params.blank? && @reviews.present? + @reviews + else + @reviews = Endpoints::Reviews.new(client).get(project_id, id, params) + end + end end end end diff --git a/test/story_test.rb b/test/story_test.rb index e6b269e..f06a184 100644 --- a/test/story_test.rb +++ b/test/story_test.rb @@ -302,4 +302,21 @@ end end end + + describe '.reviews' do + it 'gets all reviews (and review_types field by default) for the story' do + VCR.use_cassette('get story reviews', record: :new_episodes) do + story = TrackerApi::Resources::Story.new( client: client, + project_id: project_id, + id: story_id) + + reviews = story.reviews + review = reviews.first + _(review).must_be_instance_of TrackerApi::Resources::Review + _(review.review_type).must_be_instance_of TrackerApi::Resources::ReviewType + _(review.review_type.name).must_equal 'Test (QA)' + _(review.status).must_equal 'unstarted' + end + end + end end diff --git a/test/vcr/cassettes/get_story_reviews.json b/test/vcr/cassettes/get_story_reviews.json new file mode 100644 index 0000000..5b05c66 --- /dev/null +++ b/test/vcr/cassettes/get_story_reviews.json @@ -0,0 +1 @@ +{"http_interactions":[{"request":{"method":"get","uri":"https://www.pivotaltracker.com/services/v5/projects/1027488/stories/66728004/reviews","body":{"encoding":"US-ASCII","string":""},"headers":{"User-Agent":["Ruby/2.7.1 (x86_64-darwin19; ruby) TrackerApi/1.10.0 Faraday/1.0.1"],"X-TrackerToken":["d55c3bc1f74346b843ca84ba340b29bf"],"Accept":["application/json"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"Content-Type":["application/json; charset=utf-8"],"Status":["200 OK"],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Tracker-Project-Version":["599"],"X-Request-Id":["46c972d8-156a-4f99-b355-99b1392fea03"],"ETag":["W/\"75e4521136187daece8228d69d3170e4\""],"X-Frame-Options":["SAMEORIGIN"],"X-Runtime":["0.059777"],"X-Content-Type-Options":["nosniff, nosniff"],"Date":["Wed, 03 Jun 2020 19:09:51 GMT"],"X-Powered-By":["Phusion Passenger"],"Server":["nginx + Phusion Passenger"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Credentials":["false"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["X-TrackerToken,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Tracker-Warn-Unless-Project-Version-Is"],"X-Tracker-Client-Pinger-Interval":["20"],"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],"X-XSS-Protection":["1; mode=block"],"Via":["1.1 google"],"Alt-Svc":["clear"]},"body":{"encoding":"ASCII-8BIT","string":"[{\"kind\":\"review\",\"id\":1129224,\"story_id\":66728004,\"review_type_id\":2293634,\"reviewer_id\":1266314,\"status\":\"unstarted\",\"created_at\":\"2020-06-03T15:55:47Z\",\"updated_at\":\"2020-06-03T15:56:00Z\"},{\"kind\":\"review\",\"id\":1129225,\"story_id\":66728004,\"review_type_id\":2293635,\"status\":\"in_review\",\"created_at\":\"2020-06-03T15:55:53Z\",\"updated_at\":\"2020-06-03T15:56:09Z\"},{\"kind\":\"review\",\"id\":1129226,\"story_id\":66728004,\"review_type_id\":2293636,\"status\":\"pass\",\"created_at\":\"2020-06-03T15:55:55Z\",\"updated_at\":\"2020-06-03T15:56:53Z\"},{\"kind\":\"review\",\"id\":1129227,\"story_id\":66728004,\"review_type_id\":2293637,\"reviewer_id\":1266318,\"status\":\"revise\",\"created_at\":\"2020-06-03T15:56:08Z\",\"updated_at\":\"2020-06-03T15:57:23Z\"}]"}},"recorded_at":"Wed, 03 Jun 2020 19:09:51 GMT"},{"request":{"method":"get","uri":"https://www.pivotaltracker.com/services/v5/projects/1027488/stories/66728004/reviews?fields=%3Adefault%2Creview_type","body":{"encoding":"US-ASCII","string":""},"headers":{"User-Agent":["Ruby/2.7.1 (x86_64-darwin19; ruby) TrackerApi/1.10.0 Faraday/1.0.1"],"X-TrackerToken":["d55c3bc1f74346b843ca84ba340b29bf"],"Accept":["application/json"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"Content-Type":["application/json; charset=utf-8"],"Status":["200 OK"],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Tracker-Project-Version":["599"],"X-Request-Id":["207069c4-0ae1-4f03-8603-fdb9c44cf71c"],"ETag":["W/\"3222abe3914a443abb732d7e97f44aeb\""],"X-Frame-Options":["SAMEORIGIN"],"X-Runtime":["0.027998"],"X-Content-Type-Options":["nosniff, nosniff"],"Date":["Thu, 04 Jun 2020 12:38:07 GMT"],"X-Powered-By":["Phusion Passenger"],"Server":["nginx + Phusion Passenger"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Credentials":["false"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["X-TrackerToken,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Tracker-Warn-Unless-Project-Version-Is"],"X-Tracker-Client-Pinger-Interval":["20"],"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],"X-XSS-Protection":["1; mode=block"],"Via":["1.1 google"],"Alt-Svc":["clear"]},"body":{"encoding":"ASCII-8BIT","string":"[{\"kind\":\"review\",\"review_type\":{\"kind\":\"review_type\",\"id\":2293634,\"project_id\":1027488,\"name\":\"Test (QA)\",\"hidden\":false,\"created_at\":\"2019-02-26T01:59:38Z\",\"updated_at\":\"2019-02-26T01:59:38Z\"},\"id\":1129224,\"story_id\":66728004,\"review_type_id\":2293634,\"reviewer_id\":1266314,\"status\":\"unstarted\",\"created_at\":\"2020-06-03T15:55:47Z\",\"updated_at\":\"2020-06-03T15:56:00Z\"},{\"kind\":\"review\",\"review_type\":{\"kind\":\"review_type\",\"id\":2293635,\"project_id\":1027488,\"name\":\"Design\",\"hidden\":false,\"created_at\":\"2019-02-26T01:59:38Z\",\"updated_at\":\"2019-02-26T01:59:38Z\"},\"id\":1129225,\"story_id\":66728004,\"review_type_id\":2293635,\"status\":\"in_review\",\"created_at\":\"2020-06-03T15:55:53Z\",\"updated_at\":\"2020-06-03T15:56:09Z\"},{\"kind\":\"review\",\"review_type\":{\"kind\":\"review_type\",\"id\":2293636,\"project_id\":1027488,\"name\":\"Code\",\"hidden\":false,\"created_at\":\"2019-02-26T01:59:38Z\",\"updated_at\":\"2019-02-26T01:59:38Z\"},\"id\":1129226,\"story_id\":66728004,\"review_type_id\":2293636,\"status\":\"pass\",\"created_at\":\"2020-06-03T15:55:55Z\",\"updated_at\":\"2020-06-03T15:56:53Z\"},{\"kind\":\"review\",\"review_type\":{\"kind\":\"review_type\",\"id\":2293637,\"project_id\":1027488,\"name\":\"Security\",\"hidden\":false,\"created_at\":\"2019-02-26T01:59:38Z\",\"updated_at\":\"2019-02-26T01:59:38Z\"},\"id\":1129227,\"story_id\":66728004,\"review_type_id\":2293637,\"reviewer_id\":1266318,\"status\":\"revise\",\"created_at\":\"2020-06-03T15:56:08Z\",\"updated_at\":\"2020-06-03T15:57:23Z\"}]"}},"recorded_at":"Thu, 04 Jun 2020 12:38:07 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file