From f9bb48c63df71475ad0335812474448f684ac10b Mon Sep 17 00:00:00 2001 From: Winnie Hellmann <winnie@gitlab.com> Date: Wed, 16 Oct 2019 06:30:29 +0000 Subject: [PATCH] Move out_of_office? from reviewer roulette to Teammate class --- Dangerfile | 1 + lib/gitlab/danger/request_helper.rb | 23 +++++++++ lib/gitlab/danger/roulette.rb | 33 +++---------- lib/gitlab/danger/teammate.rb | 14 ++++++ spec/lib/gitlab/danger/teammate_spec.rb | 66 ++++++++++++++++++++++++- 5 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 lib/gitlab/danger/request_helper.rb diff --git a/Dangerfile b/Dangerfile index 228190cd530a1..b65a9074078a0 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative 'lib/gitlab_danger' +require_relative 'lib/gitlab/danger/request_helper' danger.import_plugin('danger/plugins/helper.rb') danger.import_plugin('danger/plugins/roulette.rb') diff --git a/lib/gitlab/danger/request_helper.rb b/lib/gitlab/danger/request_helper.rb new file mode 100644 index 0000000000000..06da4ed9ad36b --- /dev/null +++ b/lib/gitlab/danger/request_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'net/http' +require 'json' + +module Gitlab + module Danger + module RequestHelper + HTTPError = Class.new(RuntimeError) + + # @param [String] url + def self.http_get_json(url) + rsp = Net::HTTP.get_response(URI.parse(url)) + + unless rsp.is_a?(Net::HTTPOK) + raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}" + end + + JSON.parse(rsp.body) + end + end + end +end diff --git a/lib/gitlab/danger/roulette.rb b/lib/gitlab/danger/roulette.rb index 25de0a87c9dfa..0700a72c918cd 100644 --- a/lib/gitlab/danger/roulette.rb +++ b/lib/gitlab/danger/roulette.rb @@ -1,16 +1,11 @@ # frozen_string_literal: true -require 'net/http' -require 'json' -require 'cgi' - require_relative 'teammate' module Gitlab module Danger module Roulette ROULETTE_DATA_URL = 'https://about.gitlab.com/roulette.json' - HTTPError = Class.new(RuntimeError) # Looks up the current list of GitLab team members and parses it into a # useful form @@ -19,7 +14,7 @@ module Roulette def team @team ||= begin - data = http_get_json(ROULETTE_DATA_URL) + data = Gitlab::Danger::RequestHelper.http_get_json(ROULETTE_DATA_URL) data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) } rescue JSON::ParserError raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}" @@ -44,6 +39,7 @@ def new_random(seed) # Known issue: If someone is rejected due to OOO, and then becomes not OOO, the # selection will change on next spin + # @param [Array<Teammate>] people def spin_for_person(people, random:) people.shuffle(random: random) .find(&method(:valid_person?)) @@ -51,32 +47,17 @@ def spin_for_person(people, random:) private + # @param [Teammate] person + # @return [Boolean] def valid_person?(person) - !mr_author?(person) && !out_of_office?(person) + !mr_author?(person) && !person.out_of_office? end + # @param [Teammate] person + # @return [Boolean] def mr_author?(person) person.username == gitlab.mr_author end - - def out_of_office?(person) - username = CGI.escape(person.username) - api_endpoint = "https://gitlab.com/api/v4/users/#{username}/status" - response = http_get_json(api_endpoint) - response["message"]&.match?(/OOO/i) - rescue HTTPError, JSON::ParserError - false # this is no worse than not checking for OOO - end - - def http_get_json(url) - rsp = Net::HTTP.get_response(URI.parse(url)) - - unless rsp.is_a?(Net::HTTPSuccess) - raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}" - end - - JSON.parse(rsp.body) - end end end end diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb index 4ad66f61c2b30..c0a2d909f69be 100644 --- a/lib/gitlab/danger/teammate.rb +++ b/lib/gitlab/danger/teammate.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'cgi' + module Gitlab module Danger class Teammate @@ -34,6 +36,18 @@ def maintainer?(project, category, labels) has_capability?(project, category, :maintainer, labels) end + def status + api_endpoint = "https://gitlab.com/api/v4/users/#{CGI.escape(username)}/status" + @status ||= Gitlab::Danger::RequestHelper.http_get_json(api_endpoint) + rescue Gitlab::Danger::RequestHelper::HTTPError, JSON::ParserError + nil # better no status than a crashing Danger + end + + # @return [Boolean] + def out_of_office? + status&.dig("message")&.match?(/OOO/i) || false + end + private def has_capability?(project, category, kind, labels) diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb index ca036390bde09..36486cbbc7d0d 100644 --- a/spec/lib/gitlab/danger/teammate_spec.rb +++ b/spec/lib/gitlab/danger/teammate_spec.rb @@ -2,11 +2,13 @@ require 'fast_spec_helper' +require 'rspec-parameterized' + require 'gitlab/danger/teammate' describe Gitlab::Danger::Teammate do - subject { described_class.new(options) } - let(:options) { { 'projects' => projects, 'role' => role } } + subject { described_class.new(options.stringify_keys) } + let(:options) { { username: 'luigi', projects: projects, role: role } } let(:projects) { { project => capabilities } } let(:role) { 'Engineer, Manage' } let(:labels) { [] } @@ -95,4 +97,64 @@ expect(subject.maintainer?(project, :frontend, labels)).to be_falsey end end + + describe '#status' do + let(:capabilities) { ['dish washing'] } + + context 'with empty cache' do + context 'for successful request' do + it 'returns the response' do + mock_status = double(does_not: 'matter') + expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json) + .and_return(mock_status) + + expect(subject.status).to be mock_status + end + end + + context 'for failing request' do + it 'returns nil' do + expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json) + .and_raise(Gitlab::Danger::RequestHelper::HTTPError.new) + + expect(subject.status).to be nil + end + end + end + + context 'with filled cache' do + it 'returns the cached response' do + mock_status = double(does_not: 'matter') + expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json) + .and_return(mock_status) + subject.status + + expect(Gitlab::Danger::RequestHelper).not_to receive(:http_get_json) + expect(subject.status).to be mock_status + end + end + end + + describe '#out_of_office?' do + using RSpec::Parameterized::TableSyntax + + let(:capabilities) { ['dry head'] } + + where(:status, :result) do + nil | false + {} | false + { message: 'dear reader' } | false + { message: 'OOO: massage' } | true + { message: 'love it SOOO much' } | true + end + + with_them do + before do + expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json) + .and_return(status&.stringify_keys) + end + + it { expect(subject.out_of_office?).to be result } + end + end end -- GitLab