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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4d0b46a9dcbc098c76a6acb7eda61791470b8a8914e47650532f6fe44158d0f
4
- data.tar.gz: f9aa9d41af6ae110acc46de51c9f19457555567ef520aa5b65d91e5fc85eb916
3
+ metadata.gz: 1d8182a9895b7c4ce505c6c31271030222ef4e3eefaee2569edccfe1a3ce892a
4
+ data.tar.gz: 32f4cd7af125f0cf2a96f7bb000eb725f2c58a7a5576349fe3905dd93302c53d
5
5
  SHA512:
6
- metadata.gz: 778edd2df54298a54b37218e6479b6566016248aed13f68a58d1fd43df6ac3bf4e0aee4d47494e0771899be984e5b0cfa667cfb99033f0c9cefde5c4cbe29aa9
7
- data.tar.gz: 740003a2197153bcfb8431329e6eb3653707f34276a3cd34afb9cba1b60e6e8b08fe6884a46a14abb70869b957a2f5510ddc765bf5018885021fba571f1e0890
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`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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 ~Quality for `spec/features/*`",
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 ||= company_members.find { |person| person.username == helper.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 new_random(seed)
119
- Random.new(Digest::MD5.hexdigest(seed).to_i(16))
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, random:, timezone_experiment: false)
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, random: random, timezone_experiment: timezone_experiment)
157
- maintainer = spin_for_person(weighted_maintainers, random: random, timezone_experiment: timezone_experiment)
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 { |member| member.in_project?(project_name) }
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
  []
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ if git.commits.size > 20
4
+ failure 'This MR has more than 20 commits. You need to rebase this branch to have fewer commits.'
5
+ end
@@ -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 categories_defined?
69
-
68
+ row.prepend("| #{helper.label_for_category(category)} ") if has_categories
70
69
  row
71
70
  end
72
71
 
73
- def categories_defined?
74
- helper.config.files_to_category.any?
75
- end
72
+ changes = helper.changes_by_category
76
73
 
77
- def categories(changes)
78
- categories_defined? ? changes.keys : [nil]
79
- end
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
- def table_header
82
- categories_defined? ? TABLE_HEADER_WITH_CATEGORIES : TABLE_HEADER_WITHOUT_CATEGORIES
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 = helper.changes_by_category
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
- random_roulette_spins = roulette.spin(nil, categories(changes))
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
- Array(projects.fetch(project, []))
152
+ projects.fetch(project, [])
138
153
  end
139
154
 
140
155
  def pluralize(count, singular, plural)
@@ -1,5 +1,5 @@
1
1
  module Gitlab
2
2
  module Dangerfiles
3
- VERSION = "3.1.0"
3
+ VERSION = "3.4.0"
4
4
  end
5
5
  end
@@ -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 | 3 |
17
- # | traintainer | 6 |
18
- # | hungry traintainer | 8 |
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 = 3
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, Gitlab::Dangerfiles::Weightage::BASE_REVIEWER_WEIGHT)
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, weight)
64
+ def add_weighted_reviewer(reviewers, reviewer, added_weight_for_hungry)
55
65
  if reviewer.reduced_capacity
56
- reviewers.fill(reviewer, reviewers.size, weight)
66
+ reviewers.fill(reviewer, reviewers.size, Gitlab::Dangerfiles::Weightage::BASE_REVIEWER_WEIGHT)
57
67
  elsif reviewer.hungry
58
- reviewers.fill(reviewer, reviewers.size, weight * Gitlab::Dangerfiles::Weightage::CAPACITY_MULTIPLIER + DEFAULT_REVIEWER_WEIGHT)
68
+ reviewers.fill(reviewer, reviewers.size, DEFAULT_REVIEWER_WEIGHT + added_weight_for_hungry)
59
69
  else
60
- reviewers.fill(reviewer, reviewers.size, weight * Gitlab::Dangerfiles::Weightage::CAPACITY_MULTIPLIER)
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.1.0
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-04-19 00:00:00.000000000 Z
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