lib/danger/plugins/roulette.rb in gitlab-dangerfiles-1.1.1 vs lib/danger/plugins/roulette.rb in gitlab-dangerfiles-2.0.0

- old
+ new

@@ -14,27 +14,36 @@ INCLUDE_TIMEZONE_FOR_CATEGORY = { database: false, }.freeze Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role, :timezone_experiment) + HTTPError = Class.new(StandardError) + # Finds the +Gitlab::Dangerfiles::Teammate+ object whose username matches the MR author username. + # + # @return [Gitlab::Dangerfiles::Teammate] def team_mr_author - team.find { |person| person.username == helper.mr_author } + company_members.find { |person| person.username == helper.mr_author } end # Assigns GitLab team members to be reviewer and maintainer - # for each change category that a Merge Request contains. + # for the given +categories+. # + # @param project [String] A project path. + # @param categories [Array<Symbol>] An array of categories symbols. + # @param timezone_experiment [Boolean] Whether to select reviewers based in timezone or not. + # # @return [Array<Spin>] def spin(project, categories = [nil], timezone_experiment: false) spins = categories.sort.map do |category| including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(category, timezone_experiment) spin_for_category(project, category, timezone_experiment: including_timezone) end backend_spin = spins.find { |spin| spin.category == :backend } + frontend_spin = spins.find { |spin| spin.category == :frontend } spins.each do |spin| including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(spin.category, timezone_experiment) case spin.category when :qa @@ -59,68 +68,36 @@ # Fetch an already picked backend maintainer, or pick one otherwise spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer end when :product_intelligence spin.optional_role = :maintainer + + if spin.maintainer.nil? + # Fetch an already picked maintainer, or pick one otherwise + spin.maintainer = backend_spin&.maintainer || frontend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer + end end end spins end - # Looks up the current list of GitLab team members and parses it into a - # useful form - # - # @return [Array<Teammate>] - def team - @team ||= begin - data = helper.http_get_json(ROULETTE_DATA_URL) - data.map { |hash| Gitlab::Dangerfiles::Teammate.new(hash) } - rescue JSON::ParserError - raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}" - end - end - - # Like +team+, but only returns teammates in the current project, based on - # project_name. - # - # @return [Array<Teammate>] - def project_team(project_name) - team.select { |member| member.in_project?(project_name) } - rescue => err - warn("Reviewer roulette failed to load team data: #{err.message}") - [] - end - - # 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:, timezone_experiment: false) - shuffled_people = people.shuffle(random: random) - - if timezone_experiment - shuffled_people.find(&method(:valid_person_with_timezone?)) - else - shuffled_people.find(&method(:valid_person?)) - end - end - private - # @param [Teammate] person + # @param [Gitlab::Dangerfiles::Teammate] person # @return [Boolean] def valid_person?(person) !mr_author?(person) && person.available end - # @param [Teammate] person + # @param [Gitlab::Dangerfiles::Teammate] person # @return [Boolean] def valid_person_with_timezone?(person) valid_person?(person) && HOURS_WHEN_PERSON_CAN_BE_PICKED.cover?(person.local_hour) end - # @param [Teammate] person + # @param [Gitlab::Dangerfiles::Teammate] person # @return [Boolean] def mr_author?(person) person.username == helper.mr_author end @@ -132,10 +109,26 @@ team.select do |member| member.public_send("#{role}?", project, category, helper.mr_labels) # rubocop:disable GitlabSecurity/PublicSend end end + # Known issue: If someone is rejected due to OOO, and then becomes not OOO, the + # selection will change on next spin. + # + # @param [Array<Gitlab::Dangerfiles::Teammate>] people + # + # @return [Gitlab::Dangerfiles::Teammate] + def spin_for_person(people, random:, timezone_experiment: false) + shuffled_people = people.shuffle(random: random) + + if timezone_experiment + shuffled_people.find(&method(:valid_person_with_timezone?)) + else + shuffled_people.find(&method(:valid_person?)) + end + end + def spin_for_category(project, category, timezone_experiment: false) team = project_team(project) reviewers, traintainers, maintainers = %i[reviewer traintainer maintainer].map do |role| spin_role_for_category(team, role, project, category) @@ -148,8 +141,47 @@ reviewer = spin_for_person(weighted_reviewers, random: random, timezone_experiment: timezone_experiment) maintainer = spin_for_person(weighted_maintainers, random: random, timezone_experiment: timezone_experiment) Spin.new(category, reviewer, maintainer, false, timezone_experiment) + end + + # Fetches the given +url+ and parse its response as JSON. + # + # @param [String] url + # + # @return [Hash, Array] + def 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 + + # Looks up the current list of GitLab team members and parses it into a + # useful form. + # + # @return [Array<Gitlab::Dangerfiles::Teammate>] + def company_members + @company_members ||= begin + data = http_get_json(ROULETTE_DATA_URL) + data.map { |hash| Gitlab::Dangerfiles::Teammate.new(hash) } + rescue JSON::ParserError + raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}" + end + end + + # Like +team+, but only returns teammates in the current project, based on + # project_name. + # + # @return [Array<Gitlab::Dangerfiles::Teammate>] + def project_team(project_name) + company_members.select { |member| member.in_project?(project_name) } + rescue => err + warn("Reviewer roulette failed to load team data: #{err.message}") + [] end end end