gitlab-dangerfiles 3.10.0 → 3.12.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: ad2ad158af390c0b6b52fdd86927b4dd0a6118b7b2eca3ad60aaa88ac40b840a
4
- data.tar.gz: 2872e01e85bf7190e2a3fd4cab71868f2e4924234443797e7479a3ab03497120
3
+ metadata.gz: edf969f524278d280762935cb8a04c78a22180296c3b7e88deb342ab8f83c840
4
+ data.tar.gz: 6b3b3c493beb17172786cefaa6e6ac1dd1ef243234b5e90e632b79830a7fae9c
5
5
  SHA512:
6
- metadata.gz: 7dccc5481318999e83be3a9535983ebbf69d731d5394cf66543df0fd77bf7e274d1b012255c9582a503caae5248956aa2441af1b9ad6ffddf6c5bc2bc730013b
7
- data.tar.gz: 5d217f45be9fb3c68032bbffd9a7eee4eb4cdb4056200c8dabdaf8255aa084a28ff3cd3ac74a5fa2588634607c4a5d23ffc6d58cc709e9343296112b6b3457b3
6
+ metadata.gz: 56e849f993eec188646a8f06106855409146070234b158c8cd48abe0378008c9d1c957dafbb1af6dbee4f86744014d1a852bc426993337f8b9978b9e7cdcdd80
7
+ data.tar.gz: 19f31baf98812c924fc62d88cdc601f0f73a0c9e0c239647e25d6e79c6a1a4eaec831397658883b9cc9aff70e0fabee8e3f4df0908f119c0262788296097793e
@@ -2,7 +2,7 @@
2
2
  commit from this merge request, and `<NEW_VERSION>` with the upcoming version number. -->
3
3
  ## Diff
4
4
 
5
- https://gitlab.com/gitlab-org/ruby/gems/gitlab-dangerfiles/compare/v<PREVIOUS_VERSION>...<COMMIT_UPDATING_VERSION>
5
+ https://gitlab.com/gitlab-org/ruby/gems/gitlab-dangerfiles/-/compare/v<PREVIOUS_VERSION>...<COMMIT_UPDATING_VERSION>
6
6
 
7
7
  ## Checklist
8
8
 
@@ -11,11 +11,7 @@ module Danger
11
11
  ROULETTE_DATA_URL = "https://gitlab-org.gitlab.io/gitlab-roulette/roulette.json"
12
12
  HOURS_WHEN_PERSON_CAN_BE_PICKED = (6..14).freeze
13
13
 
14
- INCLUDE_TIMEZONE_FOR_CATEGORY = {
15
- database: false,
16
- }.freeze
17
-
18
- Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role, :timezone_experiment)
14
+ Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role)
19
15
  HTTPError = Class.new(StandardError)
20
16
 
21
17
  Approval = Struct.new(:category, :spin) do
@@ -33,6 +29,34 @@ module Danger
33
29
  end
34
30
  end
35
31
 
32
+ def prepare_categories(changes_keys)
33
+ categories = Set.new(changes_keys)
34
+
35
+ # Ensure to spin for database reviewer/maintainer when ~database is applied (e.g. to review SQL queries)
36
+ categories << :database if labels.include?("database")
37
+
38
+ # Ensure to spin for Analytics Instrumentation reviewer when ~"analytics instrumentation::review pending" is applied
39
+ categories << :analytics_instrumentation if labels.include?("analytics instrumentation::review pending")
40
+
41
+ # Skip Analytics Instrumentation reviews for growth experiment MRs
42
+ categories.delete(:analytics_instrumentation) if labels.include?("growth experiment")
43
+
44
+ prepare_ux_category!(categories) if labels.include?("UX")
45
+
46
+ # Remove disabled categories
47
+ categories.subtract(helper.config.disabled_roulette_categories)
48
+
49
+ categories
50
+ end
51
+
52
+ def assign_pedroms_for_ux_wider_community_contribution(spins)
53
+ # We want at least a UX reviewer who can review any wider community
54
+ # contribution even without a team designer. We assign this to Pedro.
55
+ ux_spin = look_for_fallback_designer_for_ux_wider_community_contribution?(spins)
56
+
57
+ ux_spin && ux_spin.reviewer = teammate_pedroms
58
+ end
59
+
36
60
  # Finds the +Gitlab::Dangerfiles::Teammate+ object whose username matches the MR author username.
37
61
  #
38
62
  # @return [Gitlab::Dangerfiles::Teammate]
@@ -45,26 +69,22 @@ module Danger
45
69
  #
46
70
  # @param project [String] A project path.
47
71
  # @param categories [Array<Symbol>] An array of categories symbols.
48
- # @param timezone_experiment [Boolean] Whether to select reviewers based in timezone or not.
49
72
  #
50
73
  # @return [Array<Spin>]
51
74
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
52
- def spin(project = nil, categories = [:none], timezone_experiment: false)
75
+ def spin(project = nil, categories = [:none])
53
76
  project = (project || config_project_name).downcase
54
77
  categories = categories.map { |category| category&.downcase || :none }
55
78
  categories.reject! { |category| import_and_integrate_reject_category?(category, project) }
56
79
 
57
80
  spins = categories.sort_by(&:to_s).map do |category|
58
- including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(category, timezone_experiment)
59
-
60
- spin_for_category(project, category, timezone_experiment: including_timezone)
81
+ spin_for_category(project, category)
61
82
  end
62
83
 
63
84
  backend_spin = spins.find { |spin| spin.category == :backend }
64
85
  frontend_spin = spins.find { |spin| spin.category == :frontend }
65
86
 
66
87
  spins.each do |spin|
67
- including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(spin.category, timezone_experiment)
68
88
  case spin.category
69
89
  when :qa
70
90
  # MR includes QA changes, but also other changes, and author isn't an SET
@@ -77,24 +97,24 @@ module Danger
77
97
 
78
98
  if spin.reviewer.nil?
79
99
  # Fetch an already picked backend reviewer, or pick one otherwise
80
- spin.reviewer = backend_spin&.reviewer || spin_for_category(project, :backend, timezone_experiment: including_timezone).reviewer
100
+ spin.reviewer = backend_spin&.reviewer || spin_for_category(project, :backend).reviewer
81
101
  end
82
102
  when :tooling, :engineering_productivity # Deprecated as of 2.3.0 in favor of tooling
83
103
  if spin.maintainer.nil?
84
104
  # Fetch an already picked backend maintainer, or pick one otherwise
85
- spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
105
+ spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend).maintainer
86
106
  end
87
107
  when :ci_template
88
108
  if spin.maintainer.nil?
89
109
  # Fetch an already picked backend maintainer, or pick one otherwise
90
- spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
110
+ spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend).maintainer
91
111
  end
92
112
  when :analytics_instrumentation
93
113
  spin.optional_role = :maintainer
94
114
 
95
115
  if spin.maintainer.nil?
96
116
  # Fetch an already picked maintainer, or pick one otherwise
97
- spin.maintainer = backend_spin&.maintainer || frontend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
117
+ spin.maintainer = backend_spin&.maintainer || frontend_spin&.maintainer || spin_for_category(project, :backend).maintainer
98
118
  end
99
119
  when :import_integrate_be, :import_integrate_fe
100
120
  spin.optional_role = :maintainer
@@ -108,7 +128,7 @@ module Danger
108
128
 
109
129
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
110
130
 
111
- def required_approvals
131
+ def codeowners_approvals
112
132
  approval_rules = helper.mr_approval_state["rules"]
113
133
 
114
134
  return [] unless approval_rules
@@ -124,13 +144,29 @@ module Danger
124
144
  @warnings ||= []
125
145
  end
126
146
 
147
+ def teammate_pedroms
148
+ @teammate_pedroms ||= find_member("pedroms")
149
+ end
150
+
151
+ alias_method :required_approvals, :codeowners_approvals
152
+
127
153
  private
128
154
 
129
155
  def spin_for_approval_rule?(rule)
130
156
  rule["rule_type"] == "code_owner" &&
131
- rule["approvals_required"] > 0 &&
157
+ should_include_codeowners_rule?(rule) &&
132
158
  # Exclude generic codeowners rule, which should be covered by others already
133
- !generic_codeowners_rule?(rule)
159
+ !generic_codeowners_rule?(rule) &&
160
+ !excluded_required_codeowners_rule?(rule)
161
+ end
162
+
163
+ def should_include_codeowners_rule?(rule)
164
+ rule["approvals_required"] > 0 ||
165
+ helper.config.included_optional_codeowners_sections_for_roulette.include?(rule["section"])
166
+ end
167
+
168
+ def excluded_required_codeowners_rule?(rule)
169
+ helper.config.excluded_required_codeowners_sections_for_roulette.include?(rule["section"])
134
170
  end
135
171
 
136
172
  def generic_codeowners_rule?(rule)
@@ -159,12 +195,6 @@ module Danger
159
195
  !mr_author?(person) && person.available
160
196
  end
161
197
 
162
- # @param [Gitlab::Dangerfiles::Teammate] person
163
- # @return [Boolean]
164
- def valid_person_with_timezone?(person)
165
- valid_person?(person) && HOURS_WHEN_PERSON_CAN_BE_PICKED.cover?(person.local_hour)
166
- end
167
-
168
198
  # @param [Gitlab::Dangerfiles::Teammate] person
169
199
  # @return [Boolean]
170
200
  def mr_author?(person)
@@ -175,8 +205,8 @@ module Danger
175
205
  # @return [Boolean]
176
206
  def import_and_integrate_reject_category?(category, project)
177
207
  # Reject Import and Integrate categories if the MR author has reviewing abilities for the category.
178
- team_mr_author&.import_integrate_be?(project, category, helper.mr_labels) ||
179
- team_mr_author&.import_integrate_fe?(project, category, helper.mr_labels)
208
+ team_mr_author&.import_integrate_be?(project, category, labels) ||
209
+ team_mr_author&.import_integrate_fe?(project, category, labels)
180
210
  end
181
211
 
182
212
  def random
@@ -185,7 +215,7 @@ module Danger
185
215
 
186
216
  def spin_role_for_category(team, role, project, category)
187
217
  team.select do |member|
188
- member.public_send("#{role}?", project, category, helper.mr_labels)
218
+ member.public_send("#{role}?", project, category, labels)
189
219
  end
190
220
  end
191
221
 
@@ -195,14 +225,10 @@ module Danger
195
225
  # @param [Array<Gitlab::Dangerfiles::Teammate>] people
196
226
  #
197
227
  # @return [Gitlab::Dangerfiles::Teammate]
198
- def spin_for_person(people, timezone_experiment: false)
228
+ def spin_for_person(people)
199
229
  shuffled_people = people.shuffle(random: random)
200
230
 
201
- if timezone_experiment
202
- shuffled_people.find(&method(:valid_person_with_timezone?))
203
- else
204
- shuffled_people.find(&method(:valid_person?))
205
- end
231
+ shuffled_people.find(&method(:valid_person?))
206
232
  end
207
233
 
208
234
  # Spin a reviewer for a particular approval rule
@@ -242,7 +268,7 @@ module Danger
242
268
  fallback_approvers.sample(random: random)
243
269
  end
244
270
 
245
- def spin_for_category(project, category, timezone_experiment: false)
271
+ def spin_for_category(project, category)
246
272
  team = project_team(project)
247
273
  reviewers, traintainers, maintainers =
248
274
  %i[reviewer traintainer maintainer].map do |role|
@@ -252,10 +278,42 @@ module Danger
252
278
  weighted_reviewers = Gitlab::Dangerfiles::Weightage::Reviewers.new(reviewers, traintainers).execute
253
279
  weighted_maintainers = Gitlab::Dangerfiles::Weightage::Maintainers.new(maintainers).execute
254
280
 
255
- reviewer = spin_for_person(weighted_reviewers, timezone_experiment: timezone_experiment)
256
- maintainer = spin_for_person(weighted_maintainers, timezone_experiment: timezone_experiment)
281
+ reviewer = spin_for_person(weighted_reviewers)
282
+ maintainer = spin_for_person(weighted_maintainers)
257
283
 
258
- Spin.new(category, reviewer, maintainer, false, timezone_experiment)
284
+ Spin.new(category, reviewer, maintainer, false)
285
+ end
286
+
287
+ def prepare_ux_category!(categories)
288
+ # Ensure to spin for UX reviewer when ~UX is applied (e.g. to review changes to the UI)
289
+ if labels.include?("Community contribution")
290
+ categories << :ux
291
+ else
292
+ begin
293
+ # We only want to spin a reviewer for merge requests which has a
294
+ # designer for the team. There's no easy way to tell this, so we
295
+ # pretend this is a community contribution, in which case we only
296
+ # pick the team designer. If there's no one got picked, it means
297
+ # there's no designer for this team.
298
+ labels << "Community contribution"
299
+
300
+ ux_spin = spin(nil, [:ux]).first
301
+
302
+ categories << :ux if ux_spin.reviewer || ux_spin.maintainer
303
+ ensure
304
+ # Make sure we delete the label afterward
305
+ labels.delete("Community contribution")
306
+ end
307
+ end
308
+ end
309
+
310
+ def look_for_fallback_designer_for_ux_wider_community_contribution?(spins)
311
+ labels.include?("Community contribution") &&
312
+ spins.find do |spin|
313
+ spin.category == :ux &&
314
+ spin.reviewer.nil? &&
315
+ spin.maintainer.nil?
316
+ end
259
317
  end
260
318
 
261
319
  # Fetches the given +url+ and parse its response as JSON.
@@ -316,14 +374,20 @@ module Danger
316
374
  helper.config.project_name
317
375
  end
318
376
 
377
+ # Return the labels from the merge requests. This is cached.
378
+ #
379
+ # @return [String]
380
+ def labels
381
+ @labels ||= helper.mr_labels
382
+ end
383
+
319
384
  # Like +team+, but only returns teammates in the current project, based on
320
385
  # project_name.
321
386
  #
322
387
  # @return [Array<Gitlab::Dangerfiles::Teammate>]
323
388
  def project_team(project_name)
324
389
  company_members.select do |member|
325
- member.in_project?(project_name) ||
326
- member.in_project?("gitlab") # Used for universal reviewer
390
+ member.in_project?(project_name)
327
391
  end
328
392
  rescue => err
329
393
  warn("Reviewer roulette failed to load team data: #{err.message}")
@@ -81,30 +81,16 @@ def warning_list(roulette)
81
81
  end
82
82
 
83
83
  changes = helper.changes_by_category
84
-
85
- # Replicating label based categories from:
86
- # https://gitlab.com/gitlab-org/gitlab/-/blob/master/danger/roulette/Dangerfile
87
- categories = Set.new(changes.keys)
88
-
89
- # Ensure to spin for database reviewer/maintainer when ~database is applied (e.g. to review SQL queries)
90
- categories << :database if helper.mr_labels.include?('database')
91
-
92
- # 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
93
- categories << :ux if helper.mr_labels.include?('UX') && !helper.mr_labels.include?('Community contribution')
94
-
95
- # Ensure to spin for Analytics Instrumentation reviewer when ~"analytics instrumentation::review pending" is applied
96
- categories << :analytics_instrumentation if helper.mr_labels.include?("analytics instrumentation::review pending")
97
-
98
- # Skip Analytics Instrumentation reviews for growth experiment MRs
99
- categories.delete(:analytics_instrumentation) if helper.mr_labels.include?("growth experiment")
100
-
101
- # Remove disabled categories
102
- categories.subtract(helper.config.disabled_roulette_categories)
84
+ categories = roulette.prepare_categories(changes.keys)
103
85
 
104
86
  if changes.any?
105
87
  show_category_column = categories.size > 1 || categories.first != :none
106
88
  random_roulette_spins = roulette.spin(nil, categories)
107
89
 
90
+ if categories.include?(:ux)
91
+ roulette.assign_pedroms_for_ux_wider_community_contribution(random_roulette_spins)
92
+ end
93
+
108
94
  rows = random_roulette_spins.map do |spin|
109
95
  markdown_row_for_spins(spin.category, [spin], show_category_column: show_category_column)
110
96
  end
@@ -21,19 +21,11 @@ module Gitlab
21
21
  end
22
22
  private_class_method :name_to_class
23
23
 
24
- def has_capability?(...)
25
- has_particular_capability?(...) || has_universal_capability?(...)
26
- end
27
-
28
- private
29
-
30
- def has_particular_capability?(teammate)
24
+ def has_capability?(teammate)
31
25
  teammate.capabilities(project).include?(capability)
32
26
  end
33
27
 
34
- def has_universal_capability?(teammate)
35
- false
36
- end
28
+ private
37
29
 
38
30
  def capability
39
31
  @capability ||= "#{kind} #{name}"
@@ -46,21 +38,17 @@ module Gitlab
46
38
  end
47
39
 
48
40
  class Test < Category
49
- private
50
-
51
- def has_particular_capability?(teammate)
41
+ def has_capability?(teammate)
52
42
  return false if kind != :reviewer
53
43
 
54
44
  area = teammate.role[/Software Engineer in Test(?:.*?, (\w+))/, 1]
55
45
 
56
- area && labels.any?("devops::#{area.downcase}")
46
+ !!area && labels.any?("devops::#{area.downcase}")
57
47
  end
58
48
  end
59
49
 
60
50
  class Tooling < Category
61
- private
62
-
63
- def has_particular_capability?(teammate)
51
+ def has_capability?(teammate)
64
52
  if super
65
53
  true
66
54
  elsif %i[trainee_maintainer maintainer].include?(kind)
@@ -72,59 +60,52 @@ module Gitlab
72
60
  end
73
61
 
74
62
  class ImportIntegrateBE < Category
75
- private
76
-
77
- def has_particular_capability?(teammate)
63
+ def has_capability?(teammate)
78
64
  kind == :reviewer &&
79
65
  teammate.role.match?(/Backend Engineer.+Manage:Import and Integrate/)
80
66
  end
81
67
  end
82
68
 
83
69
  class ImportIntegrateFE < Category
84
- private
85
-
86
- def has_particular_capability?(teammate)
70
+ def has_capability?(teammate)
87
71
  kind == :reviewer &&
88
72
  teammate.role.match?(/Frontend Engineer.+Manage:Import and Integrate/)
89
73
  end
90
74
  end
91
75
 
92
76
  class UX < Category
77
+ def has_capability?(teammate)
78
+ super &&
79
+
80
+ if labels.any?("Community contribution")
81
+ can_review_wider_community_contribution?(teammate)
82
+ else
83
+ can_review_team_memeber_contribution?(teammate)
84
+ end
85
+ end
86
+
93
87
  private
94
88
 
95
- def has_particular_capability?(teammate)
96
- if labels.any?("Community contribution")
97
- can_review_wider_community_contribution?(teammate)
98
- else
99
- super
100
- end
89
+ def can_review_wider_community_contribution?(teammate)
90
+ # We want the designer for the team to review the wider community
91
+ # contribution because they're more familiar with that area.
92
+ the_designer_for_the_team?(teammate)
101
93
  end
102
94
 
103
- def has_universal_capability?(teammate)
104
- # No universal reviewer for community contribution.
105
- # If we do, then picking from corresponding group won't be accurate.
106
- # After solving the following issue, then we can revisit this:
107
- # https://gitlab.com/gitlab-org/ruby/gems/gitlab-dangerfiles/-/issues/58
108
- return false if labels.any?("Community contribution")
109
-
110
- teammate.projects.each_value.any? do |capabilities|
111
- capabilities.include?(capability)
112
- end
95
+ def can_review_team_memeber_contribution?(teammate)
96
+ # We don't want the designer for the team to review merge
97
+ # requests for the same team which is designed by themselves.
98
+ # So they can only review if they're not the designer for the team.
99
+ !the_designer_for_the_team?(teammate)
113
100
  end
114
101
 
115
- def can_review_wider_community_contribution?(teammate)
102
+ def the_designer_for_the_team?(teammate)
116
103
  # Pick corresponding group for community contribution
117
- # Role can be:
118
- # Product Designer, Create:Source Code
119
- # Product Designer, Verify:Pipeline Insights, Verify:Runner
120
- # Product Designer, Release
121
104
  # Specialty can be:
122
105
  # Source Code
123
106
  # [Growth: Activation, Growth: Expansion]
124
107
  # Runner
125
- areas = teammate.role[/Product Designer(?:.*?, (.+))/, 1]&.split(",")
126
-
127
- group_labels = [*teammate.specialty, *areas].map do |field|
108
+ group_labels = Array(teammate.specialty).map do |field|
128
109
  group = field.strip.sub(/^.+: ?/, "").downcase
129
110
 
130
111
  "group::#{group}"
@@ -33,6 +33,14 @@ module Gitlab
33
33
  # @return [Array] indicating which categories would be disabled for the simple roulette. Default to `[]` (all categories are enabled)
34
34
  attr_accessor :disabled_roulette_categories
35
35
 
36
+ # @!attribute included_optional_codeowners_sections_for_roulette
37
+ # @return [Array] indicating which optional codeowners sections should be included in roulette. Default to `[]`.
38
+ attr_accessor :included_optional_codeowners_sections_for_roulette
39
+
40
+ # @!attribute excluded_required_codeowners_sections_for_roulette
41
+ # @return [Array] indicating which required codeowners sections should be excluded from roulette. Default to `[]`.
42
+ attr_accessor :excluded_required_codeowners_sections_for_roulette
43
+
36
44
  DEFAULT_CHANGES_SIZE_THRESHOLDS = { high: 2_000, medium: 500 }.freeze
37
45
  DEFAULT_COMMIT_MESSAGES_MAX_COMMITS_COUNT = 10
38
46
 
@@ -44,6 +52,8 @@ module Gitlab
44
52
  @code_size_thresholds = DEFAULT_CHANGES_SIZE_THRESHOLDS
45
53
  @max_commits_count = DEFAULT_COMMIT_MESSAGES_MAX_COMMITS_COUNT
46
54
  @disabled_roulette_categories = []
55
+ @included_optional_codeowners_sections_for_roulette = []
56
+ @excluded_required_codeowners_sections_for_roulette = []
47
57
  end
48
58
  end
49
59
  end
@@ -1,5 +1,5 @@
1
1
  module Gitlab
2
2
  module Dangerfiles
3
- VERSION = "3.10.0"
3
+ VERSION = "3.12.0"
4
4
  end
5
5
  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.10.0
4
+ version: 3.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-08 00:00:00.000000000 Z
11
+ date: 2023-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake