gitlab_quality-test_tooling 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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