summaryrefslogtreecommitdiff
path: root/danger/roulette/Dangerfile
blob: d83b7788d730459a1f0c99deb24165642c3528a9 (plain)
1
2
3
4
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# frozen_string_literal: true

require 'digest/md5'

REVIEW_ROULETTE_SECTION = <<MARKDOWN
## Reviewer roulette
MARKDOWN

CATEGORY_TABLE = <<MARKDOWN

Changes that require review have been detected!

Please refer to the table below for assigning reviewers and maintainers suggested by Danger in the specified category:

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

POST_TABLE_MESSAGE = <<MARKDOWN

To spread load more evenly across eligible reviewers, Danger has picked a candidate for each
review slot, based on their timezone. Feel free to
[override these selections](https://about.gitlab.com/handbook/engineering/projects/#gitlab)
if you think someone else would be better-suited
or use the [GitLab Review Workload Dashboard](https://gitlab-org.gitlab.io/gitlab-roulette/) to find other available reviewers.

To read more on how to use the reviewer roulette, please take a look at the
[Engineering workflow](https://about.gitlab.com/handbook/engineering/workflow/#basics)
and [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html).
Please consider assigning a reviewer or maintainer who is a
[domain expert](https://about.gitlab.com/handbook/engineering/projects/#gitlab) in the area of the merge request.

Once you've decided who will review this merge request, assign them as a reviewer!
Danger does not automatically notify them for you.
MARKDOWN

NO_SUGGESTIONS = <<MARKDOWN

There are no reviewer and maintainer suggestions for the changes in this MR.
MARKDOWN

UNKNOWN_FILES_MESSAGE = <<MARKDOWN
### Uncategorized files

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

def group_not_available_template(slack_channel, gitlab_group)
  <<~TEMPLATE.strip
    No engineer is available for automated assignment, please reach out to the `#{slack_channel}` Slack channel or mention `#{gitlab_group}` for assistance.
  TEMPLATE
end

OPTIONAL_REVIEW_TEMPLATE = '%{role} review is optional for %{category}'
NOT_AVAILABLE_TEMPLATES = {
  default: 'No %{role} available',
  product_intelligence: group_not_available_template('#g_product_intelligence', '@gitlab-org/growth/product-intelligence/engineers'),
  integrations_be: group_not_available_template('#g_ecosystem_integrations', '@gitlab-org/ecosystem-stage/integrations'),
  integrations_fe: group_not_available_template('#g_ecosystem_integrations', '@gitlab-org/ecosystem-stage/integrations')
}.freeze

def note_for_spins_role(spins, role, category)
  template = NOT_AVAILABLE_TEMPLATES[category] || NOT_AVAILABLE_TEMPLATES[:default]

  spins.each do |spin|
    note = note_for_spin_role(spin, role)

    return note if note
  end

  template % { role: role }
end

def note_for_spin_role(spin, role)
  if spin.optional_role == role
    return OPTIONAL_REVIEW_TEMPLATE % { role: role.capitalize, category: helper.label_for_category(spin.category) }
  end

  spin.public_send(role)&.markdown_name(author: roulette.team_mr_author) # rubocop:disable GitlabSecurity/PublicSend
end

def markdown_row_for_spins(category, spins_array)
  maintainer_note = note_for_spins_role(spins_array, :maintainer, category)
  reviewer_note = note_for_spins_role(spins_array, :reviewer, category)

  "| #{helper.label_for_category(category)} | #{reviewer_note} | #{maintainer_note} |"
end

changes = helper.changes_by_category

# Ignore any files that are known but uncategorized. Prompt for any unknown files
changes.delete(:none)
# To reinstate roulette for documentation, remove this line.
changes.delete(:docs)
# No special review for changelog needed and changelog was never a label.
changes.delete(:changelog)
# No special review for feature flags needed.
changes.delete(:feature_flag)
categories = Set.new(changes.keys - [:unknown])

# Ensure to spin for database reviewer/maintainer when ~database is applied (e.g. to review SQL queries)
categories << :database if helper.mr_labels.include?('database')

# Ensure to spin for UX reviewer when ~UX is applied (e.g. to review changes to the UI) except when it's from wider community contribution where we want to assign from the corresponding group
categories << :ux if helper.mr_labels.include?('UX') && !helper.mr_labels.include?('Community contribution')

# Ensure to spin for Product Intelligence reviewer when ~"product intelligence::review pending" is applied
categories << :product_intelligence if helper.mr_labels.include?("product intelligence::review pending")

# Skip Product intelligence reviews for growth experiment MRs
categories.delete(:product_intelligence) if helper.mr_labels.include?("growth experiment")

if changes.any?
  random_roulette_spins = roulette.spin(nil, categories, timezone_experiment: false)

  rows = random_roulette_spins.map do |spin|
    markdown_row_for_spins(spin.category, [spin])
  end

  markdown(REVIEW_ROULETTE_SECTION)

  if rows.empty?
    markdown(NO_SUGGESTIONS)
  else
    markdown(CATEGORY_TABLE + rows.join("\n"))
    markdown(POST_TABLE_MESSAGE)
  end

  unknown = changes.fetch(:unknown, [])
  markdown(UNKNOWN_FILES_MESSAGE + helper.markdown_list(unknown)) unless unknown.empty?
end