gitlab-dangerfiles 3.1.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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