Dangerfile 4.09 KB
Newer Older
Nick Thomas's avatar
Nick Thomas committed
1 2
# frozen_string_literal: true

3 4
require 'digest/md5'

Nick Thomas's avatar
Nick Thomas committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
MESSAGE = <<MARKDOWN
## Reviewer roulette

Changes that require review have been detected! A merge request is normally
reviewed by both a reviewer and a maintainer in its primary category (e.g.
~frontend or ~backend), and by a maintainer in all other categories.
MARKDOWN

CATEGORY_TABLE_HEADER = <<MARKDOWN

To spread load more evenly across eligible reviewers, Danger has randomly picked
a candidate for each review slot. Feel free to override this selection if you
think someone else would be better-suited, or the chosen person is unavailable.

Once you've decided who will review this merge request, mention them as you
normally would! Danger does not (yet?) automatically notify them for you.

| Category | Reviewer | Maintainer |
| -------- | -------- | ---------- |
MARKDOWN

UNKNOWN_FILES_MESSAGE = <<MARKDOWN

These files couldn't be categorised, so Danger was unable to suggest a reviewer.
Please consider creating a merge request to
[add support](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/danger/helper.rb)
for them.
MARKDOWN

34
def spin_for_category(team, project, category, branch_name)
35 36
  rng = Random.new(Digest::MD5.hexdigest(branch_name).to_i(16))

Nick Thomas's avatar
Nick Thomas committed
37
  reviewers = team.select { |member| member.reviewer?(project, category) }
38
  traintainers = team.select { |member| member.traintainer?(project, category) }
Nick Thomas's avatar
Nick Thomas committed
39 40 41
  maintainers = team.select { |member| member.maintainer?(project, category) }

  # TODO: take CODEOWNERS into account?
42
  # https://gitlab.com/gitlab-org/gitlab-ce/issues/57653
Nick Thomas's avatar
Nick Thomas committed
43

44
  # Make traintainers have triple the chance to be picked as a reviewer
45 46
  reviewer = spin_for_person(reviewers + traintainers + traintainers, random: rng)
  maintainer = spin_for_person(maintainers, random: rng)
Nick Thomas's avatar
Nick Thomas committed
47 48 49 50

  "| #{helper.label_for_category(category)} | #{reviewer&.markdown_name} | #{maintainer&.markdown_name} |"
end

51 52 53 54 55 56 57 58 59
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
# selection will change on next spin
def spin_for_person(people, random:)
  person = nil
  people = people.dup

  people.size.times do
    person = people.sample(random: random)

60
    break person unless out_of_office?(person)
61 62 63

    people -= [person]
  end
64 65

  person
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
end

def out_of_office?(person)
  username = CGI.escape(person.username)
  api_endpoint = "https://gitlab.com/api/v4/users/#{username}/status"
  response = HTTParty.get(api_endpoint) # rubocop:disable Gitlab/HTTParty

  if response.code == 200
    response["message"]&.match(/OOO/i)
  else
    false # this is no worse than not checking for OOO
  end
rescue
  false
end

Nick Thomas's avatar
Nick Thomas committed
82 83 84 85 86 87 88 89 90 91 92
def build_list(items)
  list = items.map { |filename| "* `#{filename}`" }.join("\n")

  if items.size > 10
    "\n<details>\n\n#{list}\n\n</details>"
  else
    list
  end
end

changes = helper.changes_by_category
93

94
# Ignore any files that are known but uncategorized. Prompt for any unknown files
95
changes.delete(:none)
Nick Thomas's avatar
Nick Thomas committed
96 97
categories = changes.keys - [:unknown]

98 99
# Single codebase MRs are reviewed using a slightly different process, so we
# disable the review roulette for such MRs.
100 101 102
# CSS Clean up MRs are reviewed using a slightly different process, so we
# disable the review roulette for such MRs.
if changes.any? && !gitlab.mr_labels.include?('single codebase') && !gitlab.mr_labels.include?('CSS cleanup')
103 104 105 106 107 108
  # Strip leading and trailing CE/EE markers
  canonical_branch_name = gitlab
                            .mr_json['source_branch']
                            .gsub(/^[ce]e-/, '')
                            .gsub(/-[ce]e$/, '')

Nick Thomas's avatar
Nick Thomas committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122
  team =
    begin
      helper.project_team
    rescue => err
      warn("Reviewer roulette failed to load team data: #{err.message}")
      []
    end

  # Exclude the MR author from the team for selection purposes
  team.delete_if { |teammate| teammate.username == gitlab.mr_author }

  project = helper.project_name
  unknown = changes.fetch(:unknown, [])

123
  rows = categories.map { |category| spin_for_category(team, project, category, canonical_branch_name) }
Nick Thomas's avatar
Nick Thomas committed
124 125 126 127 128

  markdown(MESSAGE)
  markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty?
  markdown(UNKNOWN_FILES_MESSAGE + build_list(unknown)) unless unknown.empty?
end