gitlab-dangerfiles 3.3.0 → 3.4.2

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: bf965c37aaf01dff0630442feecb4fbe6e8d96bb2c9f7f714c631487c9eb894e
4
- data.tar.gz: 720fe1ddfd202e0b199dfbd63fdb360a8a7e58d355182b7055ff0fdebb74c886
3
+ metadata.gz: bcb56a4513a5f26239f800a2692bd8feedc8fed15bd2d4d2d93d9d2203e0df70
4
+ data.tar.gz: 0fd4fd6f9cacee27bf394c2b5ef17a7fee6b34b45b215e08524d5641fc0b092d
5
5
  SHA512:
6
- metadata.gz: 31a832aa516290296662a7949b01a13c7dd47e7c05ad6c6e78cb1f189f2190b99094e97a07875d65f4cb6b5504a8816d1a26aa39a618480899f2026c58c2c311
7
- data.tar.gz: 4f18f92c83494aff49ceec1a05f2b380bd9be7aee17a50a4126ec25c011cfc468a672da200d4a93420d4c47bbec19d6678dc389e083a56945d94d52a58862107
6
+ metadata.gz: 8bcfa9f04ce155b7e77393c9d8e2eef9ab6534111f2ce5eaa33d484ed5e811827172ba90f40589b81f1987fd35277eab91afcd5365282e4deb94de92752d5054
7
+ data.tar.gz: 526c301f0e148a4ed6faf4dcffb3afa4d142ab828eb36eb8e745783ba80964a769a4e3456683ad1898d0d4328bc3f25f5847557042fad7ae9f53b20b7d2e22e1
data/Gemfile CHANGED
@@ -7,6 +7,10 @@ gem "rake", "~> 12.0"
7
7
  gem "guard-rspec"
8
8
  gem "yard"
9
9
 
10
+ group :development do
11
+ gem "lefthook", require: false
12
+ end
13
+
10
14
  group :test do
11
15
  gem "pry-byebug", "~> 3.8", require: false
12
16
  end
data/README.md CHANGED
@@ -215,12 +215,19 @@ Latest documentation can be found at <https://www.rubydoc.info/gems/gitlab-dange
215
215
 
216
216
  ## Development
217
217
 
218
+ ### Initial setup
219
+
218
220
  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.
219
221
 
220
222
  To install this gem onto your local machine, run `bundle exec rake install`.
221
223
 
222
224
  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.
223
225
 
226
+ ### Activate lefthook locally
227
+
228
+ ```shell
229
+ lefthook install
230
+ ```
224
231
  ## Contributing
225
232
 
226
233
  Bug reports and merge requests are welcome at https://gitlab.com/gitlab-org/gitlab-dangerfiles. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://gitlab.com/gitlab-org/gitlab-dangerfiles/blob/master/CODE_OF_CONDUCT.md).
data/lefthook.yml ADDED
@@ -0,0 +1,11 @@
1
+ # EXAMPLE USAGE
2
+ # Refer for explanation to following link:
3
+ # https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md
4
+ #
5
+
6
+ pre-push:
7
+ commands:
8
+ # Runs RUby FOrmatter (rufo)
9
+ rufo:
10
+ run: bundle exec rufo --check .
11
+ glob: '*.rb'
@@ -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,46 @@ module Danger
141
162
  end
142
163
  end
143
164
 
165
+ # Spin a reviewer for a particular approval rule
166
+ #
167
+ # @param [Hash] rule of approval
168
+ #
169
+ # @return [Gitlab::Dangerfiles::Teammate]
170
+ def spin_for_approver(rule)
171
+ # This will filter out approvers who are not even reviewers who
172
+ # don't show up in roulette data we're relying on.
173
+ # That's why `filter_map` is used.
174
+ approvers = rule["eligible_approvers"].filter_map do |approver|
175
+ find_member(approver["username"])
176
+ end
177
+
178
+ spin_for_person(approvers) || spin_for_approver_fallback(rule)
179
+ end
180
+
181
+ # It can be possible that we don't have a valid reviewer for approval.
182
+ # In this case, we sample again without considering:
183
+ #
184
+ # * If they're available
185
+ # * If they're an actual reviewer from roulette data
186
+ #
187
+ # We do this because we strictly require an approval from the approvers.
188
+ #
189
+ # @param [Hash] rule of approval
190
+ #
191
+ # @return [Gitlab::Dangerfiles::Teammate]
192
+ def spin_for_approver_fallback(rule)
193
+ fallback_approvers = rule["eligible_approvers"].map do |approver|
194
+ find_member(approver["username"]) ||
195
+ Gitlab::Dangerfiles::Teammate.new(approver)
196
+ end
197
+
198
+ # Intentionally not using `spin_for_person` to skip `valid_person?`.
199
+ # This should strictly return someone so we don't filter anything,
200
+ # and it's a fallback mechanism which should not happen often that
201
+ # deserves a complex algorithm.
202
+ fallback_approvers.sample(random: random)
203
+ end
204
+
144
205
  def spin_for_category(project, category, timezone_experiment: false)
145
206
  team = project_team(project)
146
207
  reviewers, traintainers, maintainers =
@@ -148,13 +209,11 @@ module Danger
148
209
  spin_role_for_category(team, role, project, category)
149
210
  end
150
211
 
151
- random = new_random(helper.mr_source_branch)
152
-
153
212
  weighted_reviewers = Gitlab::Dangerfiles::Weightage::Reviewers.new(reviewers, traintainers).execute
154
213
  weighted_maintainers = Gitlab::Dangerfiles::Weightage::Maintainers.new(maintainers).execute
155
214
 
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)
215
+ reviewer = spin_for_person(weighted_reviewers, timezone_experiment: timezone_experiment)
216
+ maintainer = spin_for_person(weighted_maintainers, timezone_experiment: timezone_experiment)
158
217
 
159
218
  Spin.new(category, reviewer, maintainer, false, timezone_experiment)
160
219
  end
@@ -167,6 +226,10 @@ module Danger
167
226
  def http_get_json(url)
168
227
  rsp = Net::HTTP.get_response(URI.parse(url))
169
228
 
229
+ if rsp.is_a?(Net::HTTPRedirection)
230
+ raise "Redirection detected: #{rsp.header["location"]}"
231
+ end
232
+
170
233
  unless rsp.is_a?(Net::HTTPOK)
171
234
  raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
172
235
  end
@@ -187,6 +250,10 @@ module Danger
187
250
  end
188
251
  end
189
252
 
253
+ def find_member(username)
254
+ company_members.find { |person| person.username == username }
255
+ end
256
+
190
257
  # Like +team+, but only returns teammates in the current project, based on
191
258
  # project_name.
192
259
  #
@@ -10,7 +10,8 @@ module Gitlab
10
10
  @options = options
11
11
  @username = options["username"]
12
12
  @name = options["name"]
13
- @markdown_name = options["markdown_name"]
13
+ @markdown_name = options["markdown_name"] ||
14
+ default_markdown_name(*options.values_at("username", "name"))
14
15
  @role = options["role"]
15
16
  @projects = process_projects(options["projects"])
16
17
  @available = options["available"]
@@ -23,6 +24,10 @@ module Gitlab
23
24
  options
24
25
  end
25
26
 
27
+ def inspect
28
+ "#<#{self.class} @username=#{username.inspect}>"
29
+ end
30
+
26
31
  def ==(other)
27
32
  return false unless other.respond_to?(:username)
28
33
 
@@ -62,7 +67,7 @@ module Gitlab
62
67
  end
63
68
 
64
69
  def markdown_name(author: nil)
65
- "#{@markdown_name} (#{utc_offset_text(author)})"
70
+ "#{@markdown_name}#{utc_offset_text(author)}"
66
71
  end
67
72
 
68
73
  def local_hour
@@ -79,6 +84,10 @@ module Gitlab
79
84
 
80
85
  private
81
86
 
87
+ def default_markdown_name(username, name)
88
+ "[#{name}](https://gitlab.com/#{username}) (`@#{username}`)"
89
+ end
90
+
82
91
  def process_projects(projects)
83
92
  return nil unless projects
84
93
 
@@ -88,15 +97,19 @@ module Gitlab
88
97
  end
89
98
 
90
99
  def utc_offset_text(author = nil)
100
+ return unless tz_offset_hours
101
+
91
102
  offset_text = if floored_offset_hours >= 0
92
103
  "UTC+#{floored_offset_hours}"
93
104
  else
94
105
  "UTC#{floored_offset_hours}"
95
106
  end
96
107
 
97
- return offset_text unless author
98
-
99
- "#{offset_text}, #{offset_diff_compared_to_author(author)}"
108
+ if author
109
+ " (#{offset_text}, #{offset_diff_compared_to_author(author)})"
110
+ else
111
+ " (#{offset_text})"
112
+ end
100
113
  end
101
114
 
102
115
  def offset_diff_compared_to_author(author)
@@ -1,5 +1,5 @@
1
1
  module Gitlab
2
2
  module Dangerfiles
3
- VERSION = "3.3.0"
3
+ VERSION = "3.4.2"
4
4
  end
5
5
  end
@@ -58,7 +58,7 @@ module Gitlab
58
58
  danger_plugin.import_plugin(File.expand_path("../danger/plugins/*.rb", __dir__))
59
59
 
60
60
  Dir.glob(File.expand_path("danger/plugins/*.rb", config.project_root)).sort.each do |path|
61
- puts "Importing plugin at #{path}" if dangerfile.verbose
61
+ puts "Importing plugin at #{path}" if helper_plugin.ci?
62
62
  danger_plugin.import_plugin(path)
63
63
  end
64
64
  end
@@ -87,10 +87,9 @@ module Gitlab
87
87
  return if helper_plugin.release_automation?
88
88
 
89
89
  rules = filtered_rules(only, except)
90
- puts "Running rules: #{rules}\n" if dangerfile.verbose
91
90
 
92
91
  rules.each do |rule, path|
93
- puts "Importing rule #{rule} at #{path}" if dangerfile.verbose
92
+ puts "Importing rule #{rule} at #{path}" if helper_plugin.ci?
94
93
  danger_plugin.import_dangerfile(path: path)
95
94
  end
96
95
  end
@@ -116,45 +115,45 @@ module Gitlab
116
115
 
117
116
  attr_reader :dangerfile
118
117
 
118
+ def filtered_rules(only_rules, except_rules)
119
+ only_rules = Array(only_rules).compact.map(&:to_s)
120
+
121
+ rules = allowed_rules_based_on_context.reject { |rule, _v| except_rules.include?(rule) }
122
+
123
+ if only_rules.any?
124
+ rules.select! { |rule, _v| only_rules.include?(rule) }
125
+ end
126
+
127
+ rules.sort.to_h
128
+ end
129
+
130
+ def allowed_rules_based_on_context
131
+ helper_plugin.ci? ? all_rules : local_rules
132
+ end
133
+
134
+ def all_rules
135
+ all_gem_rules.merge(custom_rules)
136
+ end
137
+
119
138
  def all_gem_rules
120
- @all_gem_rules ||= Dir.glob(File.join(RULES_DIR, "*")).sort.each_with_object({}) do |path, memo|
139
+ @all_gem_rules ||= Dir.glob(File.join(RULES_DIR, "*")).each_with_object({}) do |path, memo|
121
140
  rule_name = File.basename(path)
122
141
  memo[rule_name] = path if File.directory?(path) && File.exist?(File.join(path, "Dangerfile"))
123
142
  end
124
143
  end
125
144
 
126
145
  def custom_rules
127
- @custom_rules ||= Dir.glob(File.expand_path("danger/*", config.project_root)).sort.each_with_object({}) do |path, memo|
146
+ @custom_rules ||= Dir.glob(File.expand_path("danger/*", config.project_root)).each_with_object({}) do |path, memo|
128
147
  rule_name = File.basename(path)
129
148
  memo[rule_name] = path if File.directory?(path) && File.exist?(File.join(path, "Dangerfile"))
130
149
  end
131
150
  end
132
151
 
133
- def all_rules
134
- all_gem_rules.merge(custom_rules)
135
- end
136
-
137
152
  def local_rules
138
153
  ci_only_rules = CI_ONLY_RULES | config.ci_only_rules
139
154
  all_rules.reject { |rule, _v| ci_only_rules.include?(rule) }
140
155
  end
141
156
 
142
- def allowed_rules_based_on_context
143
- helper_plugin.ci? ? all_rules : local_rules
144
- end
145
-
146
- def filtered_rules(only_rules, except_rules)
147
- only_rules = Array(only_rules).compact.map(&:to_s)
148
-
149
- rules = allowed_rules_based_on_context.reject { |rule, _v| except_rules.include?(rule) }
150
-
151
- if only_rules.any?
152
- rules.select! { |rule, _v| only_rules.include?(rule) }
153
- end
154
-
155
- rules
156
- end
157
-
158
157
  def danger_plugin
159
158
  @danger_plugin ||= dangerfile.plugins[Danger::DangerfileDangerPlugin]
160
159
  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.3.0
4
+ version: 3.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-15 00:00:00.000000000 Z
11
+ date: 2022-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -163,6 +163,7 @@ files:
163
163
  - fixtures/emojis/aliases.json
164
164
  - fixtures/emojis/digests.json
165
165
  - gitlab-dangerfiles.gemspec
166
+ - lefthook.yml
166
167
  - lib/danger/plugins/changelog.rb
167
168
  - lib/danger/plugins/internal/helper.rb
168
169
  - lib/danger/plugins/roulette.rb