gitlab-dangerfiles 3.1.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +28 -1
- data/lib/danger/plugins/internal/helper.rb +11 -1
- data/lib/danger/plugins/roulette.rb +43 -9
- data/lib/danger/rules/commits_counter/Dangerfile +5 -0
- data/lib/danger/rules/simple_roulette/Dangerfile +22 -15
- data/lib/gitlab/dangerfiles/teammate.rb +16 -1
- data/lib/gitlab/dangerfiles/version.rb +1 -1
- data/lib/gitlab/dangerfiles/weightage/reviewers.rb +20 -10
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d8182a9895b7c4ce505c6c31271030222ef4e3eefaee2569edccfe1a3ce892a
|
4
|
+
data.tar.gz: 32f4cd7af125f0cf2a96f7bb000eb725f2c58a7a5576349fe3905dd93302c53d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdb2baf7afe55ad315269cf4231efa717099dbb931ca5a6f3d7b268d9551b337fa5265627b8e134d739383a4cf72f5bf68588a19010bd4327c04297baaf3a49f
|
7
|
+
data.tar.gz: bdfa7d3753215241101362459e869cf53b1b4f141e96c58f9a31aac02a1dcb4aadae2caf98115704e8814664f0b74c466781aafe8da02d8f0bd00793857ccfb8
|
data/README.md
CHANGED
@@ -172,6 +172,31 @@ include:
|
|
172
172
|
|
173
173
|
See a [real world example](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/merge_requests/105).
|
174
174
|
|
175
|
+
#### Without `Gemfile`
|
176
|
+
|
177
|
+
Danger is a Ruby project and uses [`bundler`](https://bundler.io/) to manage
|
178
|
+
its dependencies. This requires a project to have a `Gemfile` and
|
179
|
+
`Gemfile.lock` commited. This is helpful especially if Danger is also used
|
180
|
+
locally - with `lefthook`, for example.
|
181
|
+
|
182
|
+
In order to skip Ruby and `bundler` dependency in a project, use `bundle` commands directly
|
183
|
+
in the CI configuration:
|
184
|
+
|
185
|
+
```yaml
|
186
|
+
include:
|
187
|
+
- project: 'gitlab-org/quality/pipeline-common'
|
188
|
+
file: '/ci/danger-review.yml'
|
189
|
+
|
190
|
+
danger-review:
|
191
|
+
before_script:
|
192
|
+
- bundle init
|
193
|
+
# For latest version
|
194
|
+
- bundle add gitlab-dangerfiles
|
195
|
+
# OR
|
196
|
+
# For a pinned version
|
197
|
+
- bundle add gitlab-dangerfiles --version 3.1.0
|
198
|
+
```
|
199
|
+
|
175
200
|
## Rake tasks
|
176
201
|
|
177
202
|
You can import this gem's Rake tasks by adding the following to your project's `Rakefile`:
|
@@ -192,7 +217,9 @@ Latest documentation can be found at <https://www.rubydoc.info/gems/gitlab-dange
|
|
192
217
|
|
193
218
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
194
219
|
|
195
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
220
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
221
|
+
|
222
|
+
To release a new version, update the version number in `version.rb`, and get the MR merged by a maintainer. This will be then be packaged into a gem and pushed to [rubygems.org](https://rubygems.org) by the CI/CD.
|
196
223
|
|
197
224
|
## Contributing
|
198
225
|
|
@@ -17,7 +17,7 @@ module Danger
|
|
17
17
|
none: "",
|
18
18
|
qa: "~QA",
|
19
19
|
ux: "~UX",
|
20
|
-
test: "~test
|
20
|
+
test: "~test for `spec/features/*`",
|
21
21
|
# Deprecated as of 2.3.0 in favor of tooling
|
22
22
|
engineering_productivity: '~"Engineering Productivity" for CI, Danger',
|
23
23
|
tooling: '~"maintenance::workflow" / ~"maintenance::pipelines" for CI, Danger',
|
@@ -25,6 +25,7 @@ module Danger
|
|
25
25
|
product_intelligence: '~"product intelligence"',
|
26
26
|
integrations_be: '~"group::integrations" (backend)',
|
27
27
|
integrations_fe: '~"group::integrations" (frontend)',
|
28
|
+
"Authentication and Authorization": '~"group::authentication and authorization"',
|
28
29
|
}.freeze
|
29
30
|
|
30
31
|
# Allows to set specific rule's configuration by passing a block.
|
@@ -299,6 +300,15 @@ module Danger
|
|
299
300
|
gitlab_helper.mr_json["target_branch"]
|
300
301
|
end
|
301
302
|
|
303
|
+
# @return [Hash] +{}+ when not in the CI context, and the merge request approval state otherwise.
|
304
|
+
def mr_approval_state
|
305
|
+
return {} unless ci?
|
306
|
+
|
307
|
+
gitlab_helper.api.merge_request_approval_state(
|
308
|
+
mr_target_project_id, mr_iid
|
309
|
+
)
|
310
|
+
end
|
311
|
+
|
302
312
|
# @return [Boolean] +true+ when not in the CI context, and whether the MR is set to be squashed otherwise.
|
303
313
|
def squash_mr?
|
304
314
|
return true unless ci?
|
@@ -18,11 +18,20 @@ module Danger
|
|
18
18
|
Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role, :timezone_experiment)
|
19
19
|
HTTPError = Class.new(StandardError)
|
20
20
|
|
21
|
+
Approval = Struct.new(:category, :spin) do
|
22
|
+
def self.from_approval_rule(rule, maintainer)
|
23
|
+
category = rule["section"].to_sym
|
24
|
+
spin = Spin.new(category, nil, maintainer, :reviewer)
|
25
|
+
|
26
|
+
new(category, spin)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
21
30
|
# Finds the +Gitlab::Dangerfiles::Teammate+ object whose username matches the MR author username.
|
22
31
|
#
|
23
32
|
# @return [Gitlab::Dangerfiles::Teammate]
|
24
33
|
def team_mr_author
|
25
|
-
@team_mr_author ||=
|
34
|
+
@team_mr_author ||= find_member(helper.mr_author)
|
26
35
|
end
|
27
36
|
|
28
37
|
# Assigns GitLab team members to be reviewer and maintainer
|
@@ -87,6 +96,18 @@ module Danger
|
|
87
96
|
spins
|
88
97
|
end
|
89
98
|
|
99
|
+
def required_approvals
|
100
|
+
approval_rules = helper.mr_approval_state["rules"]
|
101
|
+
|
102
|
+
return [] unless approval_rules
|
103
|
+
|
104
|
+
approval_rules.filter_map do |rule|
|
105
|
+
rule["rule_type"] == "code_owner" &&
|
106
|
+
rule["approvals_required"] > 0 &&
|
107
|
+
Approval.from_approval_rule(rule, spin_for_approver(rule))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
90
111
|
private
|
91
112
|
|
92
113
|
# @param [Gitlab::Dangerfiles::Teammate] person
|
@@ -115,8 +136,8 @@ module Danger
|
|
115
136
|
team_mr_author&.integrations_fe?(project, category, helper.mr_labels)
|
116
137
|
end
|
117
138
|
|
118
|
-
def
|
119
|
-
Random.new(Digest::MD5.hexdigest(
|
139
|
+
def random
|
140
|
+
@random ||= Random.new(Digest::MD5.hexdigest(helper.mr_source_branch).to_i(16))
|
120
141
|
end
|
121
142
|
|
122
143
|
def spin_role_for_category(team, role, project, category)
|
@@ -131,7 +152,7 @@ module Danger
|
|
131
152
|
# @param [Array<Gitlab::Dangerfiles::Teammate>] people
|
132
153
|
#
|
133
154
|
# @return [Gitlab::Dangerfiles::Teammate]
|
134
|
-
def spin_for_person(people,
|
155
|
+
def spin_for_person(people, timezone_experiment: false)
|
135
156
|
shuffled_people = people.shuffle(random: random)
|
136
157
|
|
137
158
|
if timezone_experiment
|
@@ -141,6 +162,14 @@ module Danger
|
|
141
162
|
end
|
142
163
|
end
|
143
164
|
|
165
|
+
def spin_for_approver(rule)
|
166
|
+
approvers = rule["eligible_approvers"].map do |approver|
|
167
|
+
find_member(approver["username"])
|
168
|
+
end
|
169
|
+
|
170
|
+
spin_for_person(approvers)
|
171
|
+
end
|
172
|
+
|
144
173
|
def spin_for_category(project, category, timezone_experiment: false)
|
145
174
|
team = project_team(project)
|
146
175
|
reviewers, traintainers, maintainers =
|
@@ -148,13 +177,11 @@ module Danger
|
|
148
177
|
spin_role_for_category(team, role, project, category)
|
149
178
|
end
|
150
179
|
|
151
|
-
random = new_random(helper.mr_source_branch)
|
152
|
-
|
153
180
|
weighted_reviewers = Gitlab::Dangerfiles::Weightage::Reviewers.new(reviewers, traintainers).execute
|
154
181
|
weighted_maintainers = Gitlab::Dangerfiles::Weightage::Maintainers.new(maintainers).execute
|
155
182
|
|
156
|
-
reviewer = spin_for_person(weighted_reviewers,
|
157
|
-
maintainer = spin_for_person(weighted_maintainers,
|
183
|
+
reviewer = spin_for_person(weighted_reviewers, timezone_experiment: timezone_experiment)
|
184
|
+
maintainer = spin_for_person(weighted_maintainers, timezone_experiment: timezone_experiment)
|
158
185
|
|
159
186
|
Spin.new(category, reviewer, maintainer, false, timezone_experiment)
|
160
187
|
end
|
@@ -187,12 +214,19 @@ module Danger
|
|
187
214
|
end
|
188
215
|
end
|
189
216
|
|
217
|
+
def find_member(username)
|
218
|
+
company_members.find { |person| person.username == username }
|
219
|
+
end
|
220
|
+
|
190
221
|
# Like +team+, but only returns teammates in the current project, based on
|
191
222
|
# project_name.
|
192
223
|
#
|
193
224
|
# @return [Array<Gitlab::Dangerfiles::Teammate>]
|
194
225
|
def project_team(project_name)
|
195
|
-
company_members.select
|
226
|
+
company_members.select do |member|
|
227
|
+
member.in_project?(project_name) ||
|
228
|
+
member.in_project?("gitlab") # Used for backup reviewer
|
229
|
+
end
|
196
230
|
rescue => err
|
197
231
|
warn("Reviewer roulette failed to load team data: #{err.message}")
|
198
232
|
[]
|
@@ -60,37 +60,44 @@ def note_for_spin_role(spin, role)
|
|
60
60
|
spin.public_send(role)&.markdown_name(author: roulette.team_mr_author)
|
61
61
|
end
|
62
62
|
|
63
|
-
def markdown_row_for_spins(category = nil, spins_array)
|
63
|
+
def markdown_row_for_spins(category = nil, spins_array, has_categories:)
|
64
64
|
reviewer_note = note_for_spins_role(spins_array, :reviewer)
|
65
65
|
maintainer_note = note_for_spins_role(spins_array, :maintainer)
|
66
66
|
|
67
67
|
row = +"| #{reviewer_note} | #{maintainer_note} |"
|
68
|
-
row.prepend("| #{helper.label_for_category(category)} ") if
|
69
|
-
|
68
|
+
row.prepend("| #{helper.label_for_category(category)} ") if has_categories
|
70
69
|
row
|
71
70
|
end
|
72
71
|
|
73
|
-
|
74
|
-
helper.config.files_to_category.any?
|
75
|
-
end
|
72
|
+
changes = helper.changes_by_category
|
76
73
|
|
77
|
-
|
78
|
-
|
79
|
-
|
74
|
+
# Replicating label based categories from:
|
75
|
+
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/danger/roulette/Dangerfile
|
76
|
+
categories = Set.new(changes.keys - [:unknown])
|
80
77
|
|
81
|
-
|
82
|
-
|
83
|
-
end
|
78
|
+
# Ensure to spin for database reviewer/maintainer when ~database is applied (e.g. to review SQL queries)
|
79
|
+
categories << :database if helper.mr_labels.include?('database')
|
84
80
|
|
85
|
-
changes
|
81
|
+
# 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
|
82
|
+
categories << :ux if helper.mr_labels.include?('UX') && !helper.mr_labels.include?('Community contribution')
|
83
|
+
|
84
|
+
# Ensure to spin for Product Intelligence reviewer when ~"product intelligence::review pending" is applied
|
85
|
+
categories << :product_intelligence if helper.mr_labels.include?("product intelligence::review pending")
|
86
|
+
|
87
|
+
# Skip Product intelligence reviews for growth experiment MRs
|
88
|
+
categories.delete(:product_intelligence) if helper.mr_labels.include?("growth experiment")
|
86
89
|
|
87
90
|
if changes.any?
|
88
|
-
|
91
|
+
has_categories = categories.any?
|
92
|
+
categories = [nil] unless has_categories
|
93
|
+
random_roulette_spins = roulette.spin(nil, categories)
|
89
94
|
|
90
95
|
rows = random_roulette_spins.map do |spin|
|
91
|
-
markdown_row_for_spins(spin.category, [spin])
|
96
|
+
markdown_row_for_spins(spin.category, [spin], has_categories: has_categories)
|
92
97
|
end
|
93
98
|
|
99
|
+
table_header = has_categories ? TABLE_HEADER_WITH_CATEGORIES : TABLE_HEADER_WITHOUT_CATEGORIES
|
100
|
+
|
94
101
|
markdown(MESSAGE)
|
95
102
|
markdown(TABLE_MARKDOWN + table_header + rows.join("\n")) unless rows.empty?
|
96
103
|
end
|
@@ -23,6 +23,10 @@ module Gitlab
|
|
23
23
|
options
|
24
24
|
end
|
25
25
|
|
26
|
+
def inspect
|
27
|
+
"#<#{self.class} @username=#{username.inspect}>"
|
28
|
+
end
|
29
|
+
|
26
30
|
def ==(other)
|
27
31
|
return false unless other.respond_to?(:username)
|
28
32
|
|
@@ -130,11 +134,22 @@ module Gitlab
|
|
130
134
|
capabilities(project).include?("#{kind}")
|
131
135
|
else
|
132
136
|
capabilities(project).include?("#{kind} #{category}")
|
137
|
+
end || has_backup_capability?(category, kind, labels)
|
138
|
+
end
|
139
|
+
|
140
|
+
def has_backup_capability?(category, kind, labels)
|
141
|
+
case category
|
142
|
+
when :ux
|
143
|
+
capacity = "#{kind} #{category}"
|
144
|
+
|
145
|
+
projects.each_value.find do |capabilities|
|
146
|
+
capabilities.include?(capacity)
|
147
|
+
end
|
133
148
|
end
|
134
149
|
end
|
135
150
|
|
136
151
|
def capabilities(project)
|
137
|
-
|
152
|
+
projects.fetch(project, [])
|
138
153
|
end
|
139
154
|
|
140
155
|
def pluralize(count, singular, plural)
|
@@ -13,15 +13,15 @@ module Gitlab
|
|
13
13
|
# | reduced capacity reviewer | 1 |
|
14
14
|
# | reviewer | 2 |
|
15
15
|
# | hungry reviewer | 4 |
|
16
|
-
# | reduced capacity traintainer |
|
17
|
-
# | traintainer |
|
18
|
-
# | hungry traintainer |
|
16
|
+
# | reduced capacity traintainer | 1 |
|
17
|
+
# | traintainer | 2 |
|
18
|
+
# | hungry traintainer | 6 |
|
19
19
|
# +------------------------------+--------------------------------+
|
20
20
|
#
|
21
21
|
# @api private
|
22
22
|
class Reviewers
|
23
23
|
DEFAULT_REVIEWER_WEIGHT = Gitlab::Dangerfiles::Weightage::CAPACITY_MULTIPLIER * Gitlab::Dangerfiles::Weightage::BASE_REVIEWER_WEIGHT
|
24
|
-
TRAINTAINER_WEIGHT =
|
24
|
+
TRAINTAINER_WEIGHT = 2
|
25
25
|
|
26
26
|
def initialize(reviewers, traintainers)
|
27
27
|
@reviewers = reviewers
|
@@ -32,6 +32,8 @@ module Gitlab
|
|
32
32
|
# TODO: take CODEOWNERS into account?
|
33
33
|
# https://gitlab.com/gitlab-org/gitlab/issues/26723
|
34
34
|
|
35
|
+
remove_traintainers_from_reviewers!
|
36
|
+
|
35
37
|
weighted_reviewers + weighted_traintainers
|
36
38
|
end
|
37
39
|
|
@@ -39,25 +41,33 @@ module Gitlab
|
|
39
41
|
|
40
42
|
attr_reader :reviewers, :traintainers
|
41
43
|
|
44
|
+
def remove_traintainers_from_reviewers!
|
45
|
+
# Sometimes folks will add themselves as traintainers and not remove themselves as reviewers.
|
46
|
+
# There seems no way currently to ensure only one of these entries exists for a person.
|
47
|
+
# We need to protect ourselves from that scenario here as the code assumes a reviewer will only
|
48
|
+
# appear in reviewers or traintainers, not both.
|
49
|
+
reviewers.reject! { |reviewer| traintainers.include?(reviewer) }
|
50
|
+
end
|
51
|
+
|
42
52
|
def weighted_reviewers
|
43
53
|
reviewers.each_with_object([]) do |reviewer, total_reviewers|
|
44
|
-
add_weighted_reviewer(total_reviewers, reviewer,
|
54
|
+
add_weighted_reviewer(total_reviewers, reviewer, DEFAULT_REVIEWER_WEIGHT)
|
45
55
|
end
|
46
56
|
end
|
47
57
|
|
48
58
|
def weighted_traintainers
|
49
59
|
traintainers.each_with_object([]) do |reviewer, total_traintainers|
|
50
|
-
add_weighted_reviewer(total_traintainers, reviewer, TRAINTAINER_WEIGHT)
|
60
|
+
add_weighted_reviewer(total_traintainers, reviewer, DEFAULT_REVIEWER_WEIGHT + TRAINTAINER_WEIGHT)
|
51
61
|
end
|
52
62
|
end
|
53
63
|
|
54
|
-
def add_weighted_reviewer(reviewers, reviewer,
|
64
|
+
def add_weighted_reviewer(reviewers, reviewer, added_weight_for_hungry)
|
55
65
|
if reviewer.reduced_capacity
|
56
|
-
reviewers.fill(reviewer, reviewers.size,
|
66
|
+
reviewers.fill(reviewer, reviewers.size, Gitlab::Dangerfiles::Weightage::BASE_REVIEWER_WEIGHT)
|
57
67
|
elsif reviewer.hungry
|
58
|
-
reviewers.fill(reviewer, reviewers.size,
|
68
|
+
reviewers.fill(reviewer, reviewers.size, DEFAULT_REVIEWER_WEIGHT + added_weight_for_hungry)
|
59
69
|
else
|
60
|
-
reviewers.fill(reviewer, reviewers.size,
|
70
|
+
reviewers.fill(reviewer, reviewers.size, DEFAULT_REVIEWER_WEIGHT)
|
61
71
|
end
|
62
72
|
end
|
63
73
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-dangerfiles
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -169,6 +169,7 @@ files:
|
|
169
169
|
- lib/danger/rules/changelog/Dangerfile
|
170
170
|
- lib/danger/rules/changes_size/Dangerfile
|
171
171
|
- lib/danger/rules/commit_messages/Dangerfile
|
172
|
+
- lib/danger/rules/commits_counter/Dangerfile
|
172
173
|
- lib/danger/rules/simple_roulette/Dangerfile
|
173
174
|
- lib/danger/rules/type_label/Dangerfile
|
174
175
|
- lib/danger/rules/z_add_labels/Dangerfile
|