class Danger::Roulette
Common helper functions for our danger scripts. See Danger::Helper
for more details
Constants
- HOURS_WHEN_PERSON_CAN_BE_PICKED
- HTTPError
- INCLUDE_TIMEZONE_FOR_CATEGORY
- ROULETTE_DATA_URL
- Spin
Public Instance Methods
Assigns GitLab team members to be reviewer and maintainer 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>]
# File lib/danger/plugins/roulette.rb, line 36 def spin(project, categories = [nil], timezone_experiment: false) spins = categories.sort_by(&:to_s).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 # MR includes QA changes, but also other changes, and author isn't an SET if categories.size > 1 && !team_mr_author&.any_capability?(project, spin.category) spin.optional_role = :maintainer end when :test spin.optional_role = :maintainer if spin.reviewer.nil? # Fetch an already picked backend reviewer, or pick one otherwise spin.reviewer = backend_spin&.reviewer || spin_for_category(project, :backend, timezone_experiment: including_timezone).reviewer end when :tooling, :engineering_productivity # Deprecated as of 2.3.0 in favor of tooling 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 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 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
Private Instance Methods
Looks up the current list of GitLab team members and parses it into a useful form.
@return [Array<Gitlab::Dangerfiles::Teammate>]
# File lib/danger/plugins/roulette.rb, line 167 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
Fetches the given url
and parse its response as JSON.
@param [String] url
@return [Hash, Array]
# File lib/danger/plugins/roulette.rb, line 153 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
# File lib/danger/plugins/roulette.rb, line 104 def new_random(seed) Random.new(Digest::MD5.hexdigest(seed).to_i(16)) end
Like team
, but only returns teammates in the current project, based on project_name.
@return [Array<Gitlab::Dangerfiles::Teammate>]
# File lib/danger/plugins/roulette.rb, line 180 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
# File lib/danger/plugins/roulette.rb, line 130 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) end random = new_random(helper.mr_source_branch) weighted_reviewers = Gitlab::Dangerfiles::Weightage::Reviewers.new(reviewers, traintainers).execute weighted_maintainers = Gitlab::Dangerfiles::Weightage::Maintainers.new(maintainers).execute 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
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]
# File lib/danger/plugins/roulette.rb, line 120 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
# File lib/danger/plugins/roulette.rb, line 108 def spin_role_for_category(team, role, project, category) team.select do |member| member.public_send("#{role}?", project, category, helper.mr_labels) # rubocop:disable GitlabSecurity/PublicSend end end
@param [Gitlab::Dangerfiles::Teammate] person @return [Boolean]
# File lib/danger/plugins/roulette.rb, line 88 def valid_person?(person) !mr_author?(person) && person.available end
@param [Gitlab::Dangerfiles::Teammate] person @return [Boolean]
# File lib/danger/plugins/roulette.rb, line 94 def valid_person_with_timezone?(person) valid_person?(person) && HOURS_WHEN_PERSON_CAN_BE_PICKED.cover?(person.local_hour) end