gitlab_quality-test_tooling 0.6.2 → 0.7.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/Gemfile.lock +1 -1
- data/lib/gitlab_quality/test_tooling/gitlab_issue_client.rb +8 -2
- data/lib/gitlab_quality/test_tooling/labels_inference.rb +53 -0
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +113 -85
- data/lib/gitlab_quality/test_tooling/report/report_as_issue.rb +26 -6
- data/lib/gitlab_quality/test_tooling/test_results/test_result.rb +5 -1
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c393338cd20306bc419cf1bbb0c4dd3e09f51312f0c6bbc2a58e60b062a8170d
|
4
|
+
data.tar.gz: 3e43e01fc39e8c1544f9fdf552f061b98c7056710377ffeaa5a229f2ca08d587
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b78a87a21945eaacdf4e7ff609b248cb427c7e4c3ccf19c8f4784aa19f0219856144ff2c35079f9556907308f11a72fdf10c60ab3e1f79799f6eede53556bf2b
|
7
|
+
data.tar.gz: 7362e44b09a26d0b4893a05cafc819f9aaff457a7f9dc0c39f645851b2b3362872e2acf14ed3b0e1e35a00f17e02753ff2b42badf4de27672433d6288f39828e
|
data/Gemfile.lock
CHANGED
@@ -61,8 +61,14 @@ module GitlabQuality
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
def create_issue(title:, description:, labels:, issue_type: 'issue')
|
65
|
-
attrs = {
|
64
|
+
def create_issue(title:, description:, labels:, issue_type: 'issue', assignee_id: nil, due_date: nil)
|
65
|
+
attrs = {
|
66
|
+
issue_type: issue_type,
|
67
|
+
description: description,
|
68
|
+
labels: labels,
|
69
|
+
assignee_id: assignee_id,
|
70
|
+
due_date: due_date
|
71
|
+
}.compact
|
66
72
|
|
67
73
|
handle_gitlab_client_exceptions do
|
68
74
|
client.create_issue(project, title, attrs)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'httparty'
|
6
|
+
|
7
|
+
module GitlabQuality
|
8
|
+
module TestTooling
|
9
|
+
class LabelsInference
|
10
|
+
WWW_GITLAB_COM_SITE = 'https://about.gitlab.com'
|
11
|
+
WWW_GITLAB_COM_GROUPS_JSON = "#{WWW_GITLAB_COM_SITE}/groups.json".freeze
|
12
|
+
WWW_GITLAB_COM_CATEGORIES_JSON = "#{WWW_GITLAB_COM_SITE}/categories.json".freeze
|
13
|
+
FEATURE_CATEGORY_METADATA_REGEX = /(?<=feature_category: :)\w+/
|
14
|
+
|
15
|
+
def infer_labels_from_product_group(product_group)
|
16
|
+
[groups_mapping.dig(product_group, 'label')].compact.to_set
|
17
|
+
end
|
18
|
+
|
19
|
+
def infer_labels_from_feature_category(feature_category)
|
20
|
+
[
|
21
|
+
categories_mapping.dig(feature_category, 'label'),
|
22
|
+
*infer_labels_from_product_group(categories_mapping.dig(feature_category, 'group'))
|
23
|
+
].compact.to_set
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def categories_mapping
|
29
|
+
@categories_mapping ||= self.class.fetch_json(WWW_GITLAB_COM_CATEGORIES_JSON)
|
30
|
+
end
|
31
|
+
|
32
|
+
def groups_mapping
|
33
|
+
@groups_mapping ||= self.class.fetch_json(WWW_GITLAB_COM_GROUPS_JSON)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.fetch_json(json_url)
|
37
|
+
json = with_retries { HTTParty.get(json_url, format: :plain) }
|
38
|
+
JSON.parse(json)
|
39
|
+
rescue JSON::ParserError
|
40
|
+
Runtime::Logger.debug("#{self.class.name}##{__method__} attempted to parse invalid JSON:\n\n#{json}")
|
41
|
+
{}
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.with_retries(attempts: 3)
|
45
|
+
yield
|
46
|
+
rescue Errno::ECONNRESET, OpenSSL::SSL::SSLError, Net::OpenTimeout
|
47
|
+
retry if (attempts -= 1).positive?
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
private_class_method :with_retries
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -13,8 +13,7 @@ module GitlabQuality
|
|
13
13
|
# - Takes the JSON test run reports, e.g. `$CI_PROJECT_DIR/gitlab-qa-run-*/**/rspec-*.json`
|
14
14
|
# - Takes a project where failure issues should be created
|
15
15
|
# - Find issue by title (with test description or test file), then further filter by stack trace, then pick the better-matching one
|
16
|
-
# -
|
17
|
-
# - Update labels
|
16
|
+
# - Add the failed job to the issue description, and update labels
|
18
17
|
class RelateFailureIssue < ReportAsIssue
|
19
18
|
include Concerns::FindSetDri
|
20
19
|
|
@@ -22,19 +21,20 @@ module GitlabQuality
|
|
22
21
|
SPAM_THRESHOLD_FOR_FAILURE_ISSUES = 3
|
23
22
|
FAILURE_STACKTRACE_REGEX = %r{(?:(?:.*Failure/Error:(?<stacktrace>.+))|(?<stacktrace>.+))}m
|
24
23
|
ISSUE_STACKTRACE_REGEX = /### Stack trace\s*(```)#{FAILURE_STACKTRACE_REGEX}(```)/m
|
25
|
-
|
26
|
-
|
24
|
+
JOB_URL_REGEX = %r{(?<job_url>https://(?<host>[\w.]+)/(?<project_path>[\w\-./]+)/-/jobs/\d+)}
|
25
|
+
FAILED_JOB_DESCRIPTION_REGEX = /First happened in #{JOB_URL_REGEX}\./m
|
26
|
+
REPORT_ITEM_REGEX = /^1\. \d{4}-\d{2}-\d{2}: #{JOB_URL_REGEX} \((?<pipeline_url>.+)\)$/
|
27
27
|
NEW_ISSUE_LABELS = Set.new(%w[test failure::new priority::2]).freeze
|
28
28
|
IGNORE_EXCEPTIONS = ['Net::ReadTimeout', '403 Forbidden - Your account has been blocked'].freeze
|
29
29
|
SCREENSHOT_IGNORED_ERRORS = ['500 Internal Server Error', 'fabricate_via_api!', 'Error Code 500'].freeze
|
30
30
|
|
31
31
|
MultipleIssuesFound = Class.new(StandardError)
|
32
32
|
|
33
|
-
def initialize(max_diff_ratio: DEFAULT_MAX_DIFF_RATIO_FOR_DETECTION, system_logs: [], base_issue_labels:
|
33
|
+
def initialize(max_diff_ratio: DEFAULT_MAX_DIFF_RATIO_FOR_DETECTION, system_logs: [], base_issue_labels: Set.new, **kwargs)
|
34
34
|
super
|
35
35
|
@max_diff_ratio = max_diff_ratio.to_f
|
36
36
|
@system_logs = Dir.glob(system_logs)
|
37
|
-
@base_issue_labels = base_issue_labels
|
37
|
+
@base_issue_labels = Set.new(base_issue_labels)
|
38
38
|
@issue_type = 'issue'
|
39
39
|
@commented_issue_list = Set.new
|
40
40
|
end
|
@@ -58,35 +58,36 @@ module GitlabQuality
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def relate_failure_to_issue(test)
|
61
|
-
puts "
|
61
|
+
puts " => Relating issues for test '#{test.name}'..."
|
62
62
|
|
63
63
|
begin
|
64
|
-
issue
|
65
|
-
return create_issue(test) unless issue || test.quarantine?
|
64
|
+
issue = find_issue_and_update_reports(test)
|
66
65
|
|
67
|
-
|
66
|
+
create_issue(test) unless issue || test.quarantine?
|
68
67
|
rescue MultipleIssuesFound => e
|
69
68
|
warn(e.message)
|
70
69
|
end
|
71
70
|
end
|
72
71
|
|
73
|
-
def
|
72
|
+
def find_issue_and_update_reports(test)
|
74
73
|
issue, diff_ratio = find_failure_issue(test)
|
75
|
-
return
|
74
|
+
return unless issue
|
76
75
|
|
77
|
-
|
78
|
-
if
|
79
|
-
puts "
|
76
|
+
failure_already_reported = failure_already_reported?(issue, test)
|
77
|
+
if failure_already_reported
|
78
|
+
puts " => Failure already reported on issue."
|
80
79
|
else
|
81
80
|
puts " => Found issue #{issue.web_url} for test '#{test.name}' with a diff ratio of #{(diff_ratio * 100).round(2)}%."
|
82
|
-
|
81
|
+
update_reports(issue, test)
|
83
82
|
@commented_issue_list.add(issue.web_url)
|
84
83
|
end
|
85
84
|
|
86
|
-
|
85
|
+
issue
|
87
86
|
end
|
88
87
|
|
89
|
-
def
|
88
|
+
def failure_already_reported?(issue, test)
|
89
|
+
@commented_issue_list.add(issue.web_url) if failed_issue_job_urls(issue).include?(test.ci_job_url)
|
90
|
+
|
90
91
|
@commented_issue_list.include?(issue.web_url)
|
91
92
|
end
|
92
93
|
|
@@ -94,33 +95,23 @@ module GitlabQuality
|
|
94
95
|
similar_issues = pipeline_issues_with_similar_stacktrace(test)
|
95
96
|
|
96
97
|
if similar_issues.size >= SPAM_THRESHOLD_FOR_FAILURE_ISSUES
|
97
|
-
puts "
|
98
|
-
puts " => Will not create new issue for this failing spec"
|
98
|
+
puts " => Similar failure issues have already been opened for the same pipeline environment, we won't create new issue"
|
99
99
|
similar_issues.each do |similar_issue|
|
100
|
-
puts "Please check issue: #{similar_issue.web_url}"
|
101
|
-
|
102
|
-
unless existing_failure_note(similar_issue, test.ci_job_url)
|
103
|
-
gitlab.create_issue_note(iid: similar_issue.iid,
|
104
|
-
note: "This failed job is most likely related: #{test.ci_job_url}")
|
105
|
-
end
|
100
|
+
puts " => Please check issue: #{similar_issue.web_url}"
|
101
|
+
update_reports(similar_issue, test)
|
106
102
|
end
|
107
103
|
return
|
108
104
|
end
|
109
105
|
|
110
|
-
|
111
|
-
puts "for test '#{test.name}'."
|
112
|
-
|
113
|
-
post_or_update_failed_job_note(issue, test)
|
114
|
-
|
115
|
-
assign_dri(issue, test)
|
116
|
-
|
117
|
-
issue
|
106
|
+
super
|
118
107
|
end
|
119
108
|
|
120
109
|
def pipeline_issues_with_similar_stacktrace(test)
|
121
|
-
|
110
|
+
search_labels = (base_issue_labels + Set.new(%w[test failure::new])).to_a
|
111
|
+
gitlab.find_issues(options: { state: 'opened', labels: search_labels,
|
122
112
|
created_after: past_timestamp(2) }).select do |issue|
|
123
113
|
job_url_from_issue = failed_issue_job_url(issue)
|
114
|
+
|
124
115
|
next if pipeline != pipeline_env_from_job_url(job_url_from_issue)
|
125
116
|
|
126
117
|
stack_trace_from_issue = cleaned_stack_trace_from_issue(issue)
|
@@ -131,20 +122,22 @@ module GitlabQuality
|
|
131
122
|
end
|
132
123
|
|
133
124
|
def failed_issue_job_url(issue)
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
else
|
139
|
-
job_url_string = issue.description
|
140
|
-
matched = job_url_string.match(FAILED_JOB_DESCRIPTION_REGEX)
|
141
|
-
end
|
125
|
+
job_urls_from_description(issue.description, REPORT_ITEM_REGEX).last ||
|
126
|
+
# Legacy format
|
127
|
+
job_urls_from_description(issue.description, FAILED_JOB_DESCRIPTION_REGEX).last
|
128
|
+
end
|
142
129
|
|
143
|
-
|
130
|
+
def failed_issue_job_urls(issue)
|
131
|
+
job_urls_from_description(issue.description, REPORT_ITEM_REGEX) +
|
132
|
+
# Legacy format
|
133
|
+
job_urls_from_description(issue.description, FAILED_JOB_DESCRIPTION_REGEX)
|
134
|
+
end
|
144
135
|
|
145
|
-
|
146
|
-
|
147
|
-
|
136
|
+
def job_urls_from_description(issue_description, regex)
|
137
|
+
issue_description.lines.filter_map do |line|
|
138
|
+
match = line.match(regex)
|
139
|
+
match[:job_url] if match
|
140
|
+
end
|
148
141
|
end
|
149
142
|
|
150
143
|
def pipeline_env_from_job_url(job_url)
|
@@ -163,7 +156,8 @@ module GitlabQuality
|
|
163
156
|
end
|
164
157
|
|
165
158
|
def failure_issues(test)
|
166
|
-
|
159
|
+
search_labels = (base_issue_labels + Set.new(%w[test])).to_a
|
160
|
+
gitlab.find_issues(options: { state: 'opened', labels: search_labels }).select do |issue|
|
167
161
|
issue_title = issue.title.strip
|
168
162
|
issue_title.include?(test.name) || issue_title.include?(partial_file_path(test.file))
|
169
163
|
end
|
@@ -235,15 +229,14 @@ module GitlabQuality
|
|
235
229
|
stacktrace_match = stacktrace.match(regex)
|
236
230
|
|
237
231
|
if stacktrace_match
|
238
|
-
stacktrace_match[:stacktrace].
|
239
|
-
'').strip
|
232
|
+
stacktrace_match[:stacktrace].gsub(/^\s*#.*$/, '').gsub(/^[[:space:]]+/, '').strip
|
240
233
|
else
|
241
|
-
puts " => [DEBUG] Stacktrace doesn't match the
|
234
|
+
puts " => [DEBUG] Stacktrace doesn't match the regex (#{regex}):\n----------------\n#{stacktrace}\n----------------\n"
|
242
235
|
end
|
243
236
|
end
|
244
237
|
|
245
238
|
def remove_unique_resource_names(stacktrace)
|
246
|
-
stacktrace.gsub(/qa-(test|user)-[a-z0-9-]+/, '<unique-test-resource>').gsub(
|
239
|
+
stacktrace.gsub(/(QA User |qa-(test|user)-)[a-z0-9-]+/, '<unique-test-resource>').gsub(
|
247
240
|
/(?:-|_)(?:\d+[a-z]|[a-z]+\d)[a-z\d]{4,}/, '<unique-hash>')
|
248
241
|
end
|
249
242
|
|
@@ -268,10 +261,9 @@ module GitlabQuality
|
|
268
261
|
super + [
|
269
262
|
"\n### Stack trace",
|
270
263
|
"```\n#{full_stacktrace(test)}\n```",
|
271
|
-
"First happened in #{test.ci_job_url}.",
|
272
|
-
("Related test case: #{test.testcase}." if test.testcase),
|
273
264
|
screenshot_section(test),
|
274
|
-
system_log_errors_section(test)
|
265
|
+
system_log_errors_section(test),
|
266
|
+
reports_section(test)
|
275
267
|
].compact.join("\n\n")
|
276
268
|
end
|
277
269
|
|
@@ -286,40 +278,87 @@ module GitlabQuality
|
|
286
278
|
).system_logs_summary_markdown
|
287
279
|
end
|
288
280
|
|
289
|
-
|
281
|
+
if section.empty?
|
282
|
+
puts " => No system logs or correlation id provided, skipping this section in issue description"
|
283
|
+
return
|
284
|
+
end
|
290
285
|
|
291
286
|
section
|
292
287
|
end
|
293
288
|
|
289
|
+
def reports_section(test)
|
290
|
+
<<~REPORTS
|
291
|
+
### Reports (1)
|
292
|
+
|
293
|
+
#{report_list_item(test)}
|
294
|
+
REPORTS
|
295
|
+
end
|
296
|
+
|
297
|
+
def report_list_item(test)
|
298
|
+
"1. #{Time.new.utc.strftime('%F')}: #{test.ci_job_url} (#{ENV.fetch('CI_PIPELINE_URL', 'pipeline url is missing')})"
|
299
|
+
end
|
300
|
+
|
301
|
+
def labels_inference
|
302
|
+
@labels_inference ||= GitlabQuality::TestTooling::LabelsInference.new
|
303
|
+
end
|
304
|
+
|
294
305
|
def new_issue_labels(test)
|
295
|
-
|
306
|
+
puts " => [DEBUG] product_group: #{test.product_group}; feature_category: #{test.feature_category}"
|
307
|
+
new_labels = NEW_ISSUE_LABELS +
|
308
|
+
labels_inference.infer_labels_from_product_group(test.product_group) +
|
309
|
+
labels_inference.infer_labels_from_feature_category(test.feature_category)
|
310
|
+
up_to_date_labels(test: test, new_labels: new_labels)
|
296
311
|
end
|
297
312
|
|
298
313
|
def up_to_date_labels(test:, issue: nil, new_labels: Set.new)
|
299
|
-
base_issue_labels + (super << pipeline_name_label).to_a
|
314
|
+
(Set.new(base_issue_labels) + (super << pipeline_name_label)).to_a
|
300
315
|
end
|
301
316
|
|
302
|
-
def
|
303
|
-
|
304
|
-
existing_note = existing_failure_note(issue)
|
317
|
+
def new_issue_assignee_id(test)
|
318
|
+
return unless test.product_group?
|
305
319
|
|
306
|
-
|
307
|
-
|
308
|
-
else
|
309
|
-
gitlab.create_issue_note(iid: issue.iid, note: current_note)
|
310
|
-
end
|
320
|
+
dri = set_dri_via_group(test.product_group, test)
|
321
|
+
puts " => Assigning #{dri} as DRI for the issue."
|
311
322
|
|
312
|
-
|
323
|
+
gitlab.find_user_id(username: dri)
|
313
324
|
end
|
314
325
|
|
315
|
-
def
|
316
|
-
|
326
|
+
def new_issue_due_date(test)
|
327
|
+
return unless test.product_group?
|
328
|
+
|
329
|
+
Date.today + 1.month
|
317
330
|
end
|
318
331
|
|
319
|
-
def
|
320
|
-
gitlab.
|
321
|
-
|
322
|
-
|
332
|
+
def update_reports(issue, test)
|
333
|
+
gitlab.edit_issue(iid: issue.iid, options: {
|
334
|
+
description: up_to_date_issue_description(issue.description, test),
|
335
|
+
labels: up_to_date_labels(test: test, issue: issue)
|
336
|
+
})
|
337
|
+
puts " => Added a report in '#{issue.title}': #{issue.web_url}!"
|
338
|
+
end
|
339
|
+
|
340
|
+
def up_to_date_issue_description(issue_description, test)
|
341
|
+
# We include the number of reports in the header, for visibility.
|
342
|
+
new_issue_description =
|
343
|
+
if issue_description.include?('### Reports')
|
344
|
+
# We count the number of existing reports.
|
345
|
+
reports_count = issue_description
|
346
|
+
.scan(REPORT_ITEM_REGEX)
|
347
|
+
.size.to_i + 1
|
348
|
+
issue_description.sub(/^### Reports.*$/, "### Reports (#{reports_count})")
|
349
|
+
else # For issue with the legacy format, we add the Reports section
|
350
|
+
reports_count = issue_description
|
351
|
+
.scan(JOB_URL_REGEX)
|
352
|
+
.size.to_i + 1
|
353
|
+
|
354
|
+
"#{issue_description}\n\n### Reports (#{reports_count})"
|
355
|
+
end
|
356
|
+
|
357
|
+
"#{new_issue_description}\n#{report_list_item(test)}"
|
358
|
+
end
|
359
|
+
|
360
|
+
def new_issue_title(test)
|
361
|
+
"Failure in #{super}"
|
323
362
|
end
|
324
363
|
|
325
364
|
def screenshot_section(test)
|
@@ -331,18 +370,7 @@ module GitlabQuality
|
|
331
370
|
relative_url = gitlab.upload_file(file_fullpath: test.failure_screenshot)
|
332
371
|
return unless relative_url
|
333
372
|
|
334
|
-
"### Screenshot
|
335
|
-
end
|
336
|
-
|
337
|
-
def assign_dri(issue, test)
|
338
|
-
if test.product_group?
|
339
|
-
dri = set_dri_via_group(test.product_group, test)
|
340
|
-
dri_id = gitlab.find_user_id(username: dri)
|
341
|
-
gitlab.edit_issue(iid: issue.iid, options: { assignee_id: dri_id, due_date: Date.today + 1.month })
|
342
|
-
puts " => Assigning #{dri} as DRI for the issue."
|
343
|
-
else
|
344
|
-
puts " => No product group metadata found for test '#{test.name}'"
|
345
|
-
end
|
373
|
+
"### Screenshot\n\n#{relative_url.markdown}"
|
346
374
|
end
|
347
375
|
|
348
376
|
# Checks if a test failure should be reported.
|
@@ -8,6 +8,8 @@ module GitlabQuality
|
|
8
8
|
class ReportAsIssue
|
9
9
|
include Concerns::Utils
|
10
10
|
|
11
|
+
FILE_BASE_URL = "https://gitlab.com/gitlab-org/gitlab/-/blob/master/"
|
12
|
+
|
11
13
|
def initialize(token:, input_files:, project: nil, dry_run: false, **_kwargs)
|
12
14
|
@project = project
|
13
15
|
@gitlab = (dry_run ? GitlabIssueDryClient : GitlabIssueClient).new(token: token, project: project)
|
@@ -38,16 +40,31 @@ module GitlabQuality
|
|
38
40
|
|
39
41
|
| Field | Value |
|
40
42
|
| ------ | ------ |
|
41
|
-
| File |
|
43
|
+
| File | #{test_file_link(test)} |
|
42
44
|
| Description | `#{test.name}` |
|
43
45
|
| Hash | `#{failed_test_hash(test)}` |
|
46
|
+
#{"| Test case | #{test.testcase} |" if test.testcase}
|
44
47
|
DESCRIPTION
|
45
48
|
end
|
46
49
|
|
50
|
+
def test_file_link(test)
|
51
|
+
path_prefix = test.file.start_with?('qa/') ? 'qa/' : ''
|
52
|
+
|
53
|
+
"[`#{path_prefix}#{test.file}`](#{FILE_BASE_URL}#{path_prefix}#{test.file})"
|
54
|
+
end
|
55
|
+
|
47
56
|
def new_issue_labels(_test)
|
48
57
|
[]
|
49
58
|
end
|
50
59
|
|
60
|
+
def new_issue_assignee_id(_test)
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def new_issue_due_date(_test)
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
51
68
|
def validate_input!
|
52
69
|
assert_project!
|
53
70
|
assert_input_files!(files)
|
@@ -67,14 +84,17 @@ module GitlabQuality
|
|
67
84
|
end
|
68
85
|
|
69
86
|
def create_issue(test)
|
70
|
-
|
87
|
+
attrs = {
|
71
88
|
title: title_from_test(test),
|
72
89
|
description: new_issue_description(test),
|
73
90
|
labels: new_issue_labels(test).to_a,
|
74
|
-
issue_type: issue_type
|
75
|
-
|
91
|
+
issue_type: issue_type,
|
92
|
+
assignee_id: new_issue_assignee_id(test),
|
93
|
+
due_date: new_issue_due_date(test)
|
94
|
+
}.compact
|
95
|
+
issue = gitlab.create_issue(**attrs)
|
76
96
|
|
77
|
-
new_link = issue_type == 'test_case' ? issue
|
97
|
+
new_link = issue_type == 'test_case' ? issue&.web_url&.sub('/issues/', '/quality/test_cases/') : issue&.web_url
|
78
98
|
|
79
99
|
puts "Created new #{issue_type}: #{new_link}"
|
80
100
|
|
@@ -95,7 +115,7 @@ module GitlabQuality
|
|
95
115
|
|
96
116
|
def up_to_date_labels(test:, issue: nil, new_labels: Set.new)
|
97
117
|
labels = issue_labels(issue)
|
98
|
-
labels |= new_labels
|
118
|
+
labels |= new_labels.to_set
|
99
119
|
ee_test?(test) ? labels << 'Enterprise Edition' : labels.delete('Enterprise Edition')
|
100
120
|
|
101
121
|
if test.quarantine?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab_quality-test_tooling
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab Quality
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: climate_control
|
@@ -344,6 +344,7 @@ files:
|
|
344
344
|
- lib/gitlab_quality/test_tooling.rb
|
345
345
|
- lib/gitlab_quality/test_tooling/gitlab_issue_client.rb
|
346
346
|
- lib/gitlab_quality/test_tooling/gitlab_issue_dry_client.rb
|
347
|
+
- lib/gitlab_quality/test_tooling/labels_inference.rb
|
347
348
|
- lib/gitlab_quality/test_tooling/report/concerns/find_set_dri.rb
|
348
349
|
- lib/gitlab_quality/test_tooling/report/concerns/results_reporter.rb
|
349
350
|
- lib/gitlab_quality/test_tooling/report/concerns/utils.rb
|