debian-mirror-gitlab/lib/gitlab/danger/roulette.rb

170 lines
5.9 KiB
Ruby
Raw Normal View History

2019-09-04 21:01:54 +05:30
# frozen_string_literal: true
require_relative 'teammate'
2020-11-24 15:15:51 +05:30
require_relative 'request_helper' unless defined?(Gitlab::Danger::RequestHelper)
2021-03-08 18:12:59 +05:30
require_relative 'weightage/reviewers'
require_relative 'weightage/maintainers'
2019-09-04 21:01:54 +05:30
module Gitlab
module Danger
module Roulette
2020-07-28 23:09:34 +05:30
ROULETTE_DATA_URL = 'https://gitlab-org.gitlab.io/gitlab-roulette/roulette.json'
HOURS_WHEN_PERSON_CAN_BE_PICKED = (6..14).freeze
INCLUDE_TIMEZONE_FOR_CATEGORY = {
database: false
}.freeze
2020-06-23 00:09:42 +05:30
2020-10-24 23:57:45 +05:30
Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role, :timezone_experiment)
def team_mr_author
team.find { |person| person.username == mr_author_username }
end
2020-06-23 00:09:42 +05:30
# Assigns GitLab team members to be reviewer and maintainer
# for each change category that a Merge Request contains.
#
# @return [Array<Spin>]
2020-10-24 23:57:45 +05:30
def spin(project, categories, timezone_experiment: false)
2021-02-22 17:27:13 +05:30
spins = categories.sort.map do |category|
2020-07-28 23:09:34 +05:30
including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(category, timezone_experiment)
2020-10-24 23:57:45 +05:30
spin_for_category(project, category, timezone_experiment: including_timezone)
2020-06-23 00:09:42 +05:30
end
2020-10-24 23:57:45 +05:30
backend_spin = spins.find { |spin| spin.category == :backend }
spins.each do |spin|
including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(spin.category, timezone_experiment)
case spin.category
when :qa
# MR includes QA changes, but also other changes, and author isn't an SET
if categories.size > 1 && !team_mr_author&.reviewer?(project, spin.category, [])
spin.optional_role = :maintainer
end
2020-06-23 00:09:42 +05:30
when :test
2020-10-24 23:57:45 +05:30
spin.optional_role = :maintainer
2020-06-23 00:09:42 +05:30
if spin.reviewer.nil?
# Fetch an already picked backend reviewer, or pick one otherwise
2020-10-24 23:57:45 +05:30
spin.reviewer = backend_spin&.reviewer || spin_for_category(project, :backend, timezone_experiment: including_timezone).reviewer
2020-06-23 00:09:42 +05:30
end
when :engineering_productivity
if spin.maintainer.nil?
# Fetch an already picked backend maintainer, or pick one otherwise
2020-10-24 23:57:45 +05:30
spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
2020-06-23 00:09:42 +05:30
end
2021-01-03 14:25:43 +05:30
when :ci_template
if spin.maintainer.nil?
# 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
2020-06-23 00:09:42 +05:30
end
end
2020-10-24 23:57:45 +05:30
spins
2020-06-23 00:09:42 +05:30
end
2019-09-04 21:01:54 +05:30
# Looks up the current list of GitLab team members and parses it into a
# useful form
#
# @return [Array<Teammate>]
def team
@team ||=
begin
2019-12-21 20:55:43 +05:30
data = Gitlab::Danger::RequestHelper.http_get_json(ROULETTE_DATA_URL)
2019-09-04 21:01:54 +05:30
data.map { |hash| ::Gitlab::Danger::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) }
2020-10-24 23:57:45 +05:30
rescue => err
warn("Reviewer roulette failed to load team data: #{err.message}")
[]
2019-09-04 21:01:54 +05:30
end
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
# selection will change on next spin
2019-12-21 20:55:43 +05:30
# @param [Array<Teammate>] people
2020-07-28 23:09:34 +05:30
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
2019-09-04 21:01:54 +05:30
end
private
2019-12-21 20:55:43 +05:30
# @param [Teammate] person
# @return [Boolean]
2019-09-04 21:01:54 +05:30
def valid_person?(person)
2020-07-28 23:09:34 +05:30
!mr_author?(person) && person.available
end
# @param [Teammate] person
# @return [Boolean]
def valid_person_with_timezone?(person)
valid_person?(person) && HOURS_WHEN_PERSON_CAN_BE_PICKED.cover?(person.local_hour)
2019-09-04 21:01:54 +05:30
end
2019-12-21 20:55:43 +05:30
# @param [Teammate] person
# @return [Boolean]
2019-09-04 21:01:54 +05:30
def mr_author?(person)
2020-10-24 23:57:45 +05:30
person.username == mr_author_username
end
def mr_author_username
helper.gitlab_helper&.mr_author || `whoami`
end
def mr_source_branch
return `git rev-parse --abbrev-ref HEAD` unless helper.gitlab_helper&.mr_json
helper.gitlab_helper.mr_json['source_branch']
end
def mr_labels
helper.gitlab_helper&.mr_labels || []
end
def new_random(seed)
Random.new(Digest::MD5.hexdigest(seed).to_i(16))
2019-09-04 21:01:54 +05:30
end
2020-06-23 00:09:42 +05:30
def spin_role_for_category(team, role, project, category)
team.select do |member|
2020-10-24 23:57:45 +05:30
member.public_send("#{role}?", project, category, mr_labels) # rubocop:disable GitlabSecurity/PublicSend
2020-06-23 00:09:42 +05:30
end
end
2020-10-24 23:57:45 +05:30
def spin_for_category(project, category, timezone_experiment: false)
team = project_team(project)
2020-06-23 00:09:42 +05:30
reviewers, traintainers, maintainers =
%i[reviewer traintainer maintainer].map do |role|
spin_role_for_category(team, role, project, category)
end
2020-10-24 23:57:45 +05:30
random = new_random(mr_source_branch)
2021-01-03 14:25:43 +05:30
2021-03-08 18:12:59 +05:30
weighted_reviewers = Weightage::Reviewers.new(reviewers, traintainers).execute
weighted_maintainers = Weightage::Maintainers.new(maintainers).execute
2021-01-03 14:25:43 +05:30
reviewer = spin_for_person(weighted_reviewers, random: random, timezone_experiment: timezone_experiment)
2021-03-08 18:12:59 +05:30
maintainer = spin_for_person(weighted_maintainers, random: random, timezone_experiment: timezone_experiment)
2020-06-23 00:09:42 +05:30
2020-10-24 23:57:45 +05:30
Spin.new(category, reviewer, maintainer, false, timezone_experiment)
2020-06-23 00:09:42 +05:30
end
2019-09-04 21:01:54 +05:30
end
end
end