gitlab-dangerfiles 3.10.0 → 3.12.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: 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