gitlab-dangerfiles 4.6.0 → 4.8.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 +4 -4
- data/.gitignore +0 -1
- data/.gitlab-ci.yml +11 -21
- data/.rubocop.yml +3 -16
- data/Gemfile.lock +231 -0
- data/lefthook.yml +0 -5
- data/lib/danger/plugins/changelog.rb +8 -4
- data/lib/danger/plugins/internal/helper.rb +17 -6
- data/lib/danger/plugins/roulette.rb +34 -267
- data/lib/danger/rules/commit_messages/Dangerfile +1 -6
- data/lib/danger/rules/simple_roulette/Dangerfile +4 -2
- data/lib/gitlab/dangerfiles/approval.rb +22 -0
- data/lib/gitlab/dangerfiles/base_linter.rb +1 -1
- data/lib/gitlab/dangerfiles/capability.rb +84 -0
- data/lib/gitlab/dangerfiles/commit_linter.rb +2 -2
- data/lib/gitlab/dangerfiles/emoji_checker.rb +1 -1
- data/lib/gitlab/dangerfiles/spec_helper.rb +231 -1
- data/lib/gitlab/dangerfiles/spin.rb +15 -0
- data/lib/gitlab/dangerfiles/spinner.rb +190 -0
- data/lib/gitlab/dangerfiles/teammate.rb +89 -3
- data/lib/gitlab/dangerfiles/type_label_guesser.rb +1 -1
- data/lib/gitlab/dangerfiles/version.rb +1 -1
- metadata +8 -4
- data/lib/gitlab/dangerfiles/category.rb +0 -111
@@ -17,7 +17,7 @@ module DangerSpecHelper
|
|
17
17
|
"GITLAB_CI" => "true",
|
18
18
|
"DANGER_GITLAB_HOST" => "gitlab.example.com",
|
19
19
|
"CI_MERGE_REQUEST_IID" => 28_493,
|
20
|
-
"DANGER_GITLAB_API_TOKEN" => "123sbdq54erfsd3422gdfio"
|
20
|
+
"DANGER_GITLAB_API_TOKEN" => "123sbdq54erfsd3422gdfio"
|
21
21
|
}
|
22
22
|
end
|
23
23
|
|
@@ -45,6 +45,19 @@ module DangerSpecHelper
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
RSpec::Matchers.define :match_teammates do |expected|
|
49
|
+
match do |actual|
|
50
|
+
expected.each do |expected_person|
|
51
|
+
matched_person = actual.find { |actual_person| actual_person.name == expected_person.username }
|
52
|
+
|
53
|
+
matched_person &&
|
54
|
+
matched_person.name == expected_person.name &&
|
55
|
+
matched_person.role == expected_person.role &&
|
56
|
+
matched_person.projects == expected_person.projects
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
48
61
|
RSpec.shared_context "with dangerfile" do
|
49
62
|
let(:dangerfile) { DangerSpecHelper.testing_dangerfile }
|
50
63
|
let(:added_files) { %w[added-from-git] }
|
@@ -68,3 +81,220 @@ RSpec.shared_context "with dangerfile" do
|
|
68
81
|
allow(dangerfile.helper).to receive(:changes).and_return(changes) if dangerfile.respond_to?(:helper)
|
69
82
|
end
|
70
83
|
end
|
84
|
+
|
85
|
+
RSpec.shared_context "with teammates" do
|
86
|
+
let(:backend_available) { true }
|
87
|
+
let(:backend_tz_offset_hours) { 2.0 }
|
88
|
+
let(:backend_maintainer_project) { { "gitlab" => "maintainer backend" } }
|
89
|
+
let(:backend_maintainer) do
|
90
|
+
Gitlab::Dangerfiles::Teammate.new(
|
91
|
+
"username" => "backend-maintainer",
|
92
|
+
"name" => "Backend maintainer",
|
93
|
+
"role" => "Backend engineer",
|
94
|
+
"projects" => backend_maintainer_project,
|
95
|
+
"available" => backend_available,
|
96
|
+
"tz_offset_hours" => backend_tz_offset_hours
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
let(:another_backend_maintainer) do
|
101
|
+
Gitlab::Dangerfiles::Teammate.new(
|
102
|
+
"username" => "another-backend-maintainer",
|
103
|
+
"name" => "Another Backend Maintainer",
|
104
|
+
"role" => "Backend engineer",
|
105
|
+
"projects" => backend_maintainer_project,
|
106
|
+
"available" => backend_available,
|
107
|
+
"tz_offset_hours" => backend_tz_offset_hours
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
let(:backend_reviewer_available) { true }
|
112
|
+
let(:backend_reviewer) do
|
113
|
+
Gitlab::Dangerfiles::Teammate.new(
|
114
|
+
"username" => "backend-reviewer",
|
115
|
+
"name" => "Backend reviewer",
|
116
|
+
"role" => "Backend engineer",
|
117
|
+
"projects" => { "gitlab" => "reviewer backend" },
|
118
|
+
"available" => backend_reviewer_available,
|
119
|
+
"tz_offset_hours" => 1.0
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
let(:frontend_reviewer) do
|
124
|
+
Gitlab::Dangerfiles::Teammate.new(
|
125
|
+
"username" => "frontend-reviewer",
|
126
|
+
"name" => "Frontend reviewer",
|
127
|
+
"role" => "Frontend engineer",
|
128
|
+
"projects" => { "gitlab" => "reviewer frontend" },
|
129
|
+
"available" => true,
|
130
|
+
"tz_offset_hours" => 2.0
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
let(:frontend_maintainer) do
|
135
|
+
Gitlab::Dangerfiles::Teammate.new(
|
136
|
+
"username" => "frontend-maintainer",
|
137
|
+
"name" => "Frontend maintainer",
|
138
|
+
"role" => "Frontend engineer",
|
139
|
+
"projects" => { "gitlab" => "maintainer frontend" },
|
140
|
+
"available" => true,
|
141
|
+
"tz_offset_hours" => 2.0
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
let(:ux_reviewer) do
|
146
|
+
Gitlab::Dangerfiles::Teammate.new(
|
147
|
+
"username" => "ux-reviewer",
|
148
|
+
"name" => "UX reviewer",
|
149
|
+
"role" => "Product Designer",
|
150
|
+
"projects" => { "gitlab" => "reviewer ux" },
|
151
|
+
"specialty" => "Create: Source Code",
|
152
|
+
"available" => true,
|
153
|
+
"tz_offset_hours" => 2.0
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
let(:software_engineer_in_test) do
|
158
|
+
Gitlab::Dangerfiles::Teammate.new(
|
159
|
+
"username" => "software-engineer-in-test",
|
160
|
+
"name" => "Software Engineer in Test",
|
161
|
+
"role" => "Software Engineer in Test, Create:Source Code",
|
162
|
+
"projects" => { "gitlab" => "maintainer qa", "gitlab-qa" => "maintainer" },
|
163
|
+
"available" => true,
|
164
|
+
"tz_offset_hours" => 2.0
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
let(:software_engineer_in_import_integrate_fe) do
|
169
|
+
Gitlab::Dangerfiles::Teammate.new(
|
170
|
+
"username" => "software-engineer-in-import-and-integrate-fe",
|
171
|
+
"name" => "Software Engineer in Import and Integrate FE",
|
172
|
+
"role" => "Frontend Engineer, Manage:Import and Integrate",
|
173
|
+
"projects" => { "gitlab" => "reviewer frontend" },
|
174
|
+
"available" => true,
|
175
|
+
"tz_offset_hours" => 2.0
|
176
|
+
)
|
177
|
+
end
|
178
|
+
|
179
|
+
let(:software_engineer_in_import_integrate_be) do
|
180
|
+
Gitlab::Dangerfiles::Teammate.new(
|
181
|
+
"username" => "software-engineer-in-import-and-integrate-be",
|
182
|
+
"name" => "Software Engineer in Import and Integrate BE",
|
183
|
+
"role" => "Backend Engineer, Manage:Import and Integrate",
|
184
|
+
"projects" => { "gitlab" => "reviewer backend" },
|
185
|
+
"available" => true,
|
186
|
+
"tz_offset_hours" => 2.0
|
187
|
+
)
|
188
|
+
end
|
189
|
+
|
190
|
+
let(:tooling_reviewer) do
|
191
|
+
Gitlab::Dangerfiles::Teammate.new(
|
192
|
+
"username" => "eng-prod-reviewer",
|
193
|
+
"name" => "EP engineer",
|
194
|
+
"role" => "Engineering Productivity",
|
195
|
+
"projects" => { "gitlab" => "reviewer tooling" },
|
196
|
+
"available" => true,
|
197
|
+
"tz_offset_hours" => 2.0
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
let(:ci_template_reviewer) do
|
202
|
+
Gitlab::Dangerfiles::Teammate.new(
|
203
|
+
"username" => "ci-template-maintainer",
|
204
|
+
"name" => "CI Template engineer",
|
205
|
+
"role" => '~"ci::templates"',
|
206
|
+
"projects" => { "gitlab" => "reviewer ci_template" },
|
207
|
+
"available" => true,
|
208
|
+
"tz_offset_hours" => 2.0
|
209
|
+
)
|
210
|
+
end
|
211
|
+
|
212
|
+
let(:analytics_instrumentation_reviewer) do
|
213
|
+
Gitlab::Dangerfiles::Teammate.new(
|
214
|
+
"username" => "analytics-instrumentation-reviewer",
|
215
|
+
"name" => "PI engineer",
|
216
|
+
"role" => "Backend Engineer, Analytics: Analytics Instrumentation",
|
217
|
+
"projects" => { "gitlab" => "reviewer analytics_instrumentation" },
|
218
|
+
"available" => true,
|
219
|
+
"tz_offset_hours" => 2.0
|
220
|
+
)
|
221
|
+
end
|
222
|
+
|
223
|
+
let(:import_and_integrate_backend_reviewer) do
|
224
|
+
Gitlab::Dangerfiles::Teammate.new(
|
225
|
+
"username" => "import-and-integrate-backend-reviewer",
|
226
|
+
"name" => "Import and Integrate BE engineer",
|
227
|
+
"role" => "Backend Engineer, Manage:Import and Integrate",
|
228
|
+
"projects" => { "gitlab" => "reviewer backend" },
|
229
|
+
"available" => backend_reviewer_available,
|
230
|
+
"tz_offset_hours" => 2.0
|
231
|
+
)
|
232
|
+
end
|
233
|
+
|
234
|
+
let(:import_and_integrate_frontend_reviewer) do
|
235
|
+
Gitlab::Dangerfiles::Teammate.new(
|
236
|
+
"username" => "import-and-integrate-frontend-reviewer",
|
237
|
+
"name" => "Import and Integrate FE engineer",
|
238
|
+
"role" => "Frontend Engineer, Manage:Import and Integrate",
|
239
|
+
"projects" => { "gitlab" => "reviewer frontend" },
|
240
|
+
"available" => true,
|
241
|
+
"tz_offset_hours" => 2.0
|
242
|
+
)
|
243
|
+
end
|
244
|
+
|
245
|
+
let(:workhorse_reviewer) do
|
246
|
+
Gitlab::Dangerfiles::Teammate.new(
|
247
|
+
"username" => "workhorse-reviewer",
|
248
|
+
"name" => "Workhorse reviewer",
|
249
|
+
"role" => "Backend engineer",
|
250
|
+
"projects" => { "gitlab-workhorse" => "reviewer" },
|
251
|
+
"available" => true,
|
252
|
+
"tz_offset_hours" => 2.0
|
253
|
+
)
|
254
|
+
end
|
255
|
+
|
256
|
+
let(:workhorse_maintainer) do
|
257
|
+
Gitlab::Dangerfiles::Teammate.new(
|
258
|
+
"username" => "workhorse-maintainer",
|
259
|
+
"name" => "Workhorse maintainer",
|
260
|
+
"role" => "Backend engineer",
|
261
|
+
"projects" => { "gitlab-workhorse" => "maintainer" },
|
262
|
+
"available" => true,
|
263
|
+
"tz_offset_hours" => 2.0
|
264
|
+
)
|
265
|
+
end
|
266
|
+
|
267
|
+
let(:teammates) do
|
268
|
+
[
|
269
|
+
backend_maintainer.to_h,
|
270
|
+
backend_reviewer.to_h,
|
271
|
+
frontend_maintainer.to_h,
|
272
|
+
frontend_reviewer.to_h,
|
273
|
+
ux_reviewer.to_h,
|
274
|
+
software_engineer_in_test.to_h,
|
275
|
+
tooling_reviewer.to_h,
|
276
|
+
ci_template_reviewer.to_h,
|
277
|
+
workhorse_reviewer.to_h,
|
278
|
+
workhorse_maintainer.to_h,
|
279
|
+
analytics_instrumentation_reviewer.to_h,
|
280
|
+
import_and_integrate_backend_reviewer.to_h,
|
281
|
+
import_and_integrate_frontend_reviewer.to_h,
|
282
|
+
software_engineer_in_import_integrate_fe.to_h,
|
283
|
+
software_engineer_in_import_integrate_be.to_h
|
284
|
+
]
|
285
|
+
end
|
286
|
+
|
287
|
+
let(:teammate_json) { teammates.to_json }
|
288
|
+
let(:teammate_pedroms) { instance_double(Gitlab::Dangerfiles::Teammate, name: "Pedro") }
|
289
|
+
let(:company_members) { Gitlab::Dangerfiles::Teammate.fetch_company_members }
|
290
|
+
|
291
|
+
before do
|
292
|
+
WebMock
|
293
|
+
.stub_request(:get, Gitlab::Dangerfiles::Teammate::ROULETTE_DATA_URL)
|
294
|
+
.to_return(body: teammate_json)
|
295
|
+
|
296
|
+
# This avoid changing the internal state of the class
|
297
|
+
allow(Gitlab::Dangerfiles::Teammate).to receive(:warnings).and_return([])
|
298
|
+
allow(Gitlab::Dangerfiles::Teammate).to receive(:company_members).and_return(company_members)
|
299
|
+
end
|
300
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Dangerfiles
|
5
|
+
Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role) do
|
6
|
+
def no_reviewer?
|
7
|
+
reviewer.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
def no_maintainer?
|
11
|
+
maintainer.nil?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "spin"
|
4
|
+
require_relative "teammate"
|
5
|
+
require_relative "weightage/maintainers"
|
6
|
+
require_relative "weightage/reviewers"
|
7
|
+
|
8
|
+
module Gitlab
|
9
|
+
module Dangerfiles
|
10
|
+
class Spinner
|
11
|
+
attr_reader :project, :author, :team_author, :labels, :categories
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
project:, author:, team_author: nil, labels: [], categories: [],
|
15
|
+
random: Random.new, ux_fallback_wider_community_reviewer: nil)
|
16
|
+
@project = project
|
17
|
+
@author = author
|
18
|
+
@team_author = team_author
|
19
|
+
@labels = labels
|
20
|
+
@categories = categories.reject do |category|
|
21
|
+
import_and_integrate_reject_category?(category)
|
22
|
+
end
|
23
|
+
@random = random
|
24
|
+
@ux_fallback_wider_community_reviewer = ux_fallback_wider_community_reviewer
|
25
|
+
end
|
26
|
+
|
27
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
28
|
+
def spin
|
29
|
+
spins = categories.sort_by(&:to_s).map do |category|
|
30
|
+
spin_for_category(category)
|
31
|
+
end
|
32
|
+
|
33
|
+
backend_spin = spins.find { |spin| spin.category == :backend }
|
34
|
+
frontend_spin = spins.find { |spin| spin.category == :frontend }
|
35
|
+
|
36
|
+
spins.each do |spin|
|
37
|
+
case spin.category
|
38
|
+
when :qa
|
39
|
+
spin.optional_role = :maintainer if
|
40
|
+
categories.size > 1 && author_no_qa_capability?
|
41
|
+
when :test
|
42
|
+
spin.optional_role = :maintainer
|
43
|
+
|
44
|
+
if spin.no_reviewer?
|
45
|
+
# Fetch an already picked backend reviewer, or pick one otherwise
|
46
|
+
spin.reviewer = backend_spin&.reviewer || spin_for_category(:backend).reviewer
|
47
|
+
end
|
48
|
+
when :tooling
|
49
|
+
if spin.no_maintainer?
|
50
|
+
# Fetch an already picked backend maintainer, or pick one otherwise
|
51
|
+
spin.maintainer = backend_spin&.maintainer || spin_for_category(:backend).maintainer
|
52
|
+
end
|
53
|
+
when :ci_template # rubocop:disable Lint/DuplicateBranch -- bug?
|
54
|
+
if spin.no_maintainer?
|
55
|
+
# Fetch an already picked backend maintainer, or pick one otherwise
|
56
|
+
spin.maintainer = backend_spin&.maintainer || spin_for_category(:backend).maintainer
|
57
|
+
end
|
58
|
+
when :analytics_instrumentation
|
59
|
+
spin.optional_role = :maintainer
|
60
|
+
|
61
|
+
if spin.no_maintainer?
|
62
|
+
# Fetch an already picked maintainer, or pick one otherwise
|
63
|
+
spin.maintainer = backend_spin&.maintainer || frontend_spin&.maintainer || spin_for_category(:backend).maintainer
|
64
|
+
end
|
65
|
+
when :import_integrate_be, :import_integrate_fe
|
66
|
+
spin.optional_role = :maintainer
|
67
|
+
when :ux
|
68
|
+
spin.optional_role = :maintainer
|
69
|
+
|
70
|
+
# We want at least a UX reviewer who can review any wider community
|
71
|
+
# contribution even without a team designer. We assign this to Pedro.
|
72
|
+
spin.reviewer = ux_fallback_wider_community_reviewer if
|
73
|
+
labels.include?("Community contribution") &&
|
74
|
+
spin.no_reviewer? &&
|
75
|
+
spin.no_maintainer?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
spins
|
80
|
+
end
|
81
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
82
|
+
|
83
|
+
# Spin a reviewer for a particular approval rule
|
84
|
+
#
|
85
|
+
# @param [Hash] rule of approval
|
86
|
+
#
|
87
|
+
# @return [Gitlab::Dangerfiles::Teammate]
|
88
|
+
def spin_for_approver(rule)
|
89
|
+
approvers = rule["eligible_approvers"].filter_map do |approver|
|
90
|
+
Gitlab::Dangerfiles::Teammate.find_member(
|
91
|
+
approver["username"], project: project)
|
92
|
+
end
|
93
|
+
|
94
|
+
spin_for_person(approvers) || spin_for_approver_fallback(rule)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
attr_reader :random, :ux_fallback_wider_community_reviewer
|
100
|
+
|
101
|
+
# @param [String] category name
|
102
|
+
# @return [Boolean]
|
103
|
+
def import_and_integrate_reject_category?(category)
|
104
|
+
# Reject Import and Integrate categories if the MR author has reviewing abilities for the category.
|
105
|
+
team_author&.import_integrate_be?(project, category, labels) ||
|
106
|
+
team_author&.import_integrate_fe?(project, category, labels)
|
107
|
+
end
|
108
|
+
|
109
|
+
# MR includes QA changes, but also other changes, and author isn't an SET
|
110
|
+
def author_no_qa_capability?
|
111
|
+
!(team_author && team_author.capabilities(project).any? { |capability| capability.end_with?("qa") })
|
112
|
+
end
|
113
|
+
|
114
|
+
def spin_for_category(category)
|
115
|
+
reviewers, traintainers, maintainers =
|
116
|
+
%i[reviewer traintainer maintainer].map do |role|
|
117
|
+
spin_role_for_category(role, category)
|
118
|
+
end
|
119
|
+
|
120
|
+
weighted_reviewers = Weightage::Reviewers.new(reviewers, traintainers).execute
|
121
|
+
weighted_maintainers = Weightage::Maintainers.new(maintainers).execute
|
122
|
+
|
123
|
+
reviewer = spin_for_person(weighted_reviewers)
|
124
|
+
maintainer = spin_for_person(weighted_maintainers)
|
125
|
+
|
126
|
+
# allow projects with small number of reviewers to take from maintainers if possible
|
127
|
+
if reviewer.nil? && weighted_maintainers.uniq.size > 1
|
128
|
+
weighted_maintainers.delete(maintainer)
|
129
|
+
reviewer = spin_for_person(weighted_maintainers)
|
130
|
+
end
|
131
|
+
|
132
|
+
Spin.new(category, reviewer, maintainer, false)
|
133
|
+
end
|
134
|
+
|
135
|
+
def spin_role_for_category(role, category)
|
136
|
+
team.select do |member|
|
137
|
+
member.public_send(:"#{role}?", project, category, labels)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
|
142
|
+
# selection will change on next spin.
|
143
|
+
#
|
144
|
+
# @param [Array<Gitlab::Dangerfiles::Teammate>] people
|
145
|
+
#
|
146
|
+
# @return [Gitlab::Dangerfiles::Teammate]
|
147
|
+
def spin_for_person(people)
|
148
|
+
shuffled_people = people.shuffle(random: random)
|
149
|
+
|
150
|
+
shuffled_people.find { |person| valid_person?(person) }
|
151
|
+
end
|
152
|
+
|
153
|
+
# @param [Gitlab::Dangerfiles::Teammate] person
|
154
|
+
# @return [Boolean]
|
155
|
+
def valid_person?(person)
|
156
|
+
person.username != author && person.available
|
157
|
+
end
|
158
|
+
|
159
|
+
# It can be possible that we don't have a valid reviewer for approval.
|
160
|
+
# In this case, we sample again without considering:
|
161
|
+
#
|
162
|
+
# * If they're available
|
163
|
+
# * If they're an actual reviewer from roulette data
|
164
|
+
#
|
165
|
+
# We do this because we strictly require an approval from the approvers.
|
166
|
+
#
|
167
|
+
# @param [Hash] rule of approval
|
168
|
+
#
|
169
|
+
# @return [Gitlab::Dangerfiles::Teammate]
|
170
|
+
def spin_for_approver_fallback(rule)
|
171
|
+
fallback_approvers = rule["eligible_approvers"].map do |approver|
|
172
|
+
Teammate.find_member(approver["username"]) || Teammate.new(approver)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Intentionally not using `spin_for_person` to skip `valid_person?`.
|
176
|
+
# This should strictly return someone so we don't filter anything,
|
177
|
+
# and it's a fallback mechanism which should not happen often that
|
178
|
+
# deserves a complex algorithm.
|
179
|
+
fallback_approvers.sample(random: random)
|
180
|
+
end
|
181
|
+
|
182
|
+
# @return [Array<Gitlab::Dangerfiles::Teammate>]
|
183
|
+
def team
|
184
|
+
@team ||= Teammate.company_members.select do |member|
|
185
|
+
member.in_project?(project)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -1,10 +1,82 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "net/http"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
require_relative "capability"
|
4
7
|
|
5
8
|
module Gitlab
|
6
9
|
module Dangerfiles
|
7
10
|
class Teammate
|
11
|
+
ROULETTE_DATA_URL = "https://gitlab-org.gitlab.io/gitlab-roulette/roulette.json"
|
12
|
+
|
13
|
+
def self.find_member(username, project: nil)
|
14
|
+
company_members.find do |member|
|
15
|
+
member.username == username &&
|
16
|
+
(project.nil? || member.in_project?(project))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.has_member_for_the_group?(category, labels:, **arguments)
|
21
|
+
capabilities = %i[reviewer maintainer].map do |kind|
|
22
|
+
# Use new to use the base class for original has_capability? method
|
23
|
+
Capability.new(category: category, kind: kind, labels: labels, **arguments)
|
24
|
+
end
|
25
|
+
|
26
|
+
company_members.any? do |teammate|
|
27
|
+
capabilities.any? do |capability|
|
28
|
+
capability.has_capability?(teammate) &&
|
29
|
+
teammate.member_of_the_group?(labels)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Looks up the current list of GitLab team members and parses it into a
|
35
|
+
# useful form.
|
36
|
+
#
|
37
|
+
# @return [Array<Gitlab::Dangerfiles::Teammate>]
|
38
|
+
def self.company_members
|
39
|
+
@company_members ||= fetch_company_members
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.fetch_company_members
|
43
|
+
data = http_get_json(ROULETTE_DATA_URL) || []
|
44
|
+
data.map { |hash| Teammate.new(hash) }
|
45
|
+
rescue JSON::ParserError
|
46
|
+
warnings << "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Fetches the given +url+ and parse its response as JSON.
|
51
|
+
#
|
52
|
+
# @param [String] url
|
53
|
+
#
|
54
|
+
# @return [Hash, Array, NilClass]
|
55
|
+
def self.http_get_json(url)
|
56
|
+
rsp = Net::HTTP.get_response(URI.parse(url))
|
57
|
+
|
58
|
+
if rsp.is_a?(Net::HTTPRedirection)
|
59
|
+
uri = URI.parse(rsp.header["location"])
|
60
|
+
|
61
|
+
uri.query = nil if uri
|
62
|
+
|
63
|
+
warnings << "Redirection detected: #{uri}."
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
|
67
|
+
unless rsp.is_a?(Net::HTTPOK)
|
68
|
+
message = rsp.message[0, 30]
|
69
|
+
warnings << "HTTPError: Failed to read #{url}: #{rsp.code} #{message}."
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
|
73
|
+
JSON.parse(rsp.body)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.warnings
|
77
|
+
@warnings ||= []
|
78
|
+
end
|
79
|
+
|
8
80
|
attr_reader :options, :username, :name, :role, :specialty, :projects, :available, :hungry, :reduced_capacity, :tz_offset_hours,
|
9
81
|
:only_maintainer_reviews
|
10
82
|
|
@@ -14,7 +86,7 @@ module Gitlab
|
|
14
86
|
@username = options["username"]
|
15
87
|
@name = options["name"]
|
16
88
|
@markdown_name = options["markdown_name"] ||
|
17
|
-
|
89
|
+
default_markdown_name(options["username"])
|
18
90
|
@role = options["role"]
|
19
91
|
@specialty = options["specialty"]
|
20
92
|
@projects = process_projects(options["projects"])
|
@@ -25,6 +97,20 @@ module Gitlab
|
|
25
97
|
@only_maintainer_reviews = options["only_maintainer_reviews"]
|
26
98
|
end
|
27
99
|
|
100
|
+
def member_of_the_group?(labels)
|
101
|
+
# Specialty can be:
|
102
|
+
# Source Code
|
103
|
+
# [Growth: Activation, Growth: Expansion]
|
104
|
+
# Runner
|
105
|
+
group_labels = Array(specialty).map do |field|
|
106
|
+
group = field.strip.sub(/^.+: ?/, "").downcase
|
107
|
+
|
108
|
+
"group::#{group}"
|
109
|
+
end
|
110
|
+
|
111
|
+
(group_labels & labels).any?
|
112
|
+
end
|
113
|
+
|
28
114
|
def to_h
|
29
115
|
options
|
30
116
|
end
|
@@ -128,7 +214,7 @@ module Gitlab
|
|
128
214
|
end
|
129
215
|
|
130
216
|
def has_capability?(project, category, kind, labels)
|
131
|
-
|
217
|
+
Capability.for(category, project: project, kind: kind, labels: labels).has_capability?(self)
|
132
218
|
end
|
133
219
|
|
134
220
|
def pluralize(count, singular, plural)
|
@@ -9,7 +9,7 @@ module Gitlab
|
|
9
9
|
performance: %w[type::bug bug::performance],
|
10
10
|
added: %w[type::feature feature::addition],
|
11
11
|
deprecated: %w[type::maintenance maintenance::removal],
|
12
|
-
removed: %w[type::maintenance maintenance::removal]
|
12
|
+
removed: %w[type::maintenance maintenance::removal]
|
13
13
|
}.freeze
|
14
14
|
|
15
15
|
def labels_from_changelog_categories(categories)
|
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: 4.
|
4
|
+
version: 4.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -213,6 +213,7 @@ files:
|
|
213
213
|
- CONTRIBUTING.md
|
214
214
|
- Dangerfile
|
215
215
|
- Gemfile
|
216
|
+
- Gemfile.lock
|
216
217
|
- Guardfile
|
217
218
|
- LICENSE.txt
|
218
219
|
- README.md
|
@@ -238,14 +239,17 @@ files:
|
|
238
239
|
- lib/gitlab-dangerfiles.rb
|
239
240
|
- lib/gitlab/Dangerfile
|
240
241
|
- lib/gitlab/dangerfiles.rb
|
242
|
+
- lib/gitlab/dangerfiles/approval.rb
|
241
243
|
- lib/gitlab/dangerfiles/base_linter.rb
|
242
|
-
- lib/gitlab/dangerfiles/
|
244
|
+
- lib/gitlab/dangerfiles/capability.rb
|
243
245
|
- lib/gitlab/dangerfiles/changes.rb
|
244
246
|
- lib/gitlab/dangerfiles/commit_linter.rb
|
245
247
|
- lib/gitlab/dangerfiles/config.rb
|
246
248
|
- lib/gitlab/dangerfiles/emoji_checker.rb
|
247
249
|
- lib/gitlab/dangerfiles/merge_request_linter.rb
|
248
250
|
- lib/gitlab/dangerfiles/spec_helper.rb
|
251
|
+
- lib/gitlab/dangerfiles/spin.rb
|
252
|
+
- lib/gitlab/dangerfiles/spinner.rb
|
249
253
|
- lib/gitlab/dangerfiles/task_loader.rb
|
250
254
|
- lib/gitlab/dangerfiles/tasks/main.rake
|
251
255
|
- lib/gitlab/dangerfiles/teammate.rb
|
@@ -278,7 +282,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
278
282
|
- !ruby/object:Gem::Version
|
279
283
|
version: '0'
|
280
284
|
requirements: []
|
281
|
-
rubygems_version: 3.3.
|
285
|
+
rubygems_version: 3.3.27
|
282
286
|
signing_key:
|
283
287
|
specification_version: 4
|
284
288
|
summary: This gem provides common Dangerfile and plugins for GitLab projects.
|