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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80c8edb6daa8bf924d294ef291d82fd60c8545898aaa0eac7e71b98dad4dc1c2
4
- data.tar.gz: 9e414922f797ce645e951f3fc26adb394a7a7f48f2c01bf7ddd7d218a1229486
3
+ metadata.gz: c393338cd20306bc419cf1bbb0c4dd3e09f51312f0c6bbc2a58e60b062a8170d
4
+ data.tar.gz: 3e43e01fc39e8c1544f9fdf552f061b98c7056710377ffeaa5a229f2ca08d587
5
5
  SHA512:
6
- metadata.gz: 573774ab0d7be7d23d2c262f0246377b824604240930ba451b088af914e7b166b8844e63425f3583a16f45bdc73be3779e5e5eb081218ff934fcb2e87698a8bf
7
- data.tar.gz: f0ad9eb31e28e2c9f21c317b6d84e78645c36f5fff30baa8ada905d17f202710e087125a373f8fc38dd06612c08764839d2a35aaa835d133211219ee2778c37a
6
+ metadata.gz: b78a87a21945eaacdf4e7ff609b248cb427c7e4c3ccf19c8f4784aa19f0219856144ff2c35079f9556907308f11a72fdf10c60ab3e1f79799f6eede53556bf2b
7
+ data.tar.gz: 7362e44b09a26d0b4893a05cafc819f9aaff457a7f9dc0c39f645851b2b3362872e2acf14ed3b0e1e35a00f17e02753ff2b42badf4de27672433d6288f39828e
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (0.6.2)
4
+ gitlab_quality-test_tooling (0.7.0)
5
5
  activesupport (~> 6.1)
6
6
  gitlab (~> 4.19)
7
7
  http (~> 5.0)
@@ -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 = { issue_type: issue_type, description: description, labels: labels }
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
- # - Post a note to the latest failed job, e.g. https://gitlab.com/gitlab-org/gitlab/-/issues/408333#note_1361882769
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
- FAILED_JOB_DESCRIPTION_REGEX = %r{First happened in https?://\S+\.}m
26
- FAILED_JOB_NOTE_REGEX = %r{Failed most recently in \D+ pipeline: https?://\S+}
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: [], **kwargs)
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 " => Searching issues for test '#{test.name}'..."
61
+ puts " => Relating issues for test '#{test.name}'..."
62
62
 
63
63
  begin
64
- issue, issue_already_commented = find_and_link_issue(test)
65
- return create_issue(test) unless issue || test.quarantine?
64
+ issue = find_issue_and_update_reports(test)
66
65
 
67
- update_labels(issue, test) unless issue_already_commented
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 find_and_link_issue(test)
72
+ def find_issue_and_update_reports(test)
74
73
  issue, diff_ratio = find_failure_issue(test)
75
- return [false, true] unless issue
74
+ return unless issue
76
75
 
77
- issue_already_commented = issue_already_commented?(issue)
78
- if issue_already_commented
79
- puts " => Failure already commented on issue."
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
- post_or_update_failed_job_note(issue, test)
81
+ update_reports(issue, test)
83
82
  @commented_issue_list.add(issue.web_url)
84
83
  end
85
84
 
86
- [issue, issue_already_commented]
85
+ issue
87
86
  end
88
87
 
89
- def issue_already_commented?(issue)
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 " => Similar failure issues have already been opened for same pipeline environment"
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
- issue = super
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
- gitlab.find_issues(options: { state: 'opened', labels: (base_issue_labels + %w[test failure::new]).join(','),
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
- existing_note = existing_failure_note(issue)
135
- if existing_note
136
- job_url_string = existing_note.body
137
- matched = job_url_string.match(FAILED_JOB_NOTE_REGEX)
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
- return unless matched
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
- job_url = matched[0].chop.split.last
146
- puts "=> Found failed job url in the issue: #{job_url}"
147
- job_url
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
- gitlab.find_issues(options: { state: 'opened', labels: base_issue_labels + %w[test] }).select do |issue|
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].split('First happened in')[0].gsub(/^\s*#.*$/, '').gsub(/^[[:space:]]+/,
239
- '').strip
232
+ stacktrace_match[:stacktrace].gsub(/^\s*#.*$/, '').gsub(/^[[:space:]]+/, '').strip
240
233
  else
241
- puts " => [DEBUG] Stacktrace doesn't match the expected regex (#{regex}):\n----------------\n#{stacktrace}\n----------------\n"
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
- puts " => No system logs or correlation id provided, skipping this section in issue description" if section.empty?
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
- up_to_date_labels(test: test, new_labels: NEW_ISSUE_LABELS)
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 post_or_update_failed_job_note(issue, test)
303
- current_note = "Failed most recently in #{pipeline} pipeline: #{test.ci_job_url}"
304
- existing_note = existing_failure_note(issue)
317
+ def new_issue_assignee_id(test)
318
+ return unless test.product_group?
305
319
 
306
- if existing_note
307
- gitlab.edit_issue_note(issue_iid: issue.iid, note_id: existing_note.id, note: current_note)
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
- puts " => Linked #{test.ci_job_url} to #{issue.web_url}."
323
+ gitlab.find_user_id(username: dri)
313
324
  end
314
325
 
315
- def new_issue_title(test)
316
- "Failure in #{super}"
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 existing_failure_note(issue, content = "Failed most recently in")
320
- gitlab.find_issue_notes(iid: issue.iid)&.find do |note|
321
- note.body.include?(content)
322
- end
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: #{relative_url.markdown}"
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 | `#{test.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
- issue = gitlab.create_issue(
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.web_url.sub('/issues/', '/quality/test_cases/') : issue.web_url
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?
@@ -108,7 +108,11 @@ module GitlabQuality
108
108
  end
109
109
 
110
110
  def product_group
111
- report['product_group'] if product_group?
111
+ report['product_group']
112
+ end
113
+
114
+ def feature_category
115
+ report['feature_category']
112
116
  end
113
117
 
114
118
  private
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "0.6.2"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
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.6.2
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-06 00:00:00.000000000 Z
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