gitlab_quality-test_tooling 1.17.0 → 1.21.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: b8709476aafe6dc96d2b49d1d74b18b2e3dbf6c37cb8ebb0acd631d05dbfe2ac
4
- data.tar.gz: 26d4d54613a522014b5f3b93f38c0cb62027c2e209573727265a5c2b630a6d9d
3
+ metadata.gz: 9549372e9bad8839b0f8ff6a0ca1646579d244cc596d74fbbdc1234bd1e5a176
4
+ data.tar.gz: 065ee0af0ea8e14fcf4719ede74ab1a5950c05f17caef3e070832bd4695089ae
5
5
  SHA512:
6
- metadata.gz: 194294ffed245e88a96e35480485f98c1748a8417766cdca1cf737ece2b090a4e29efc444fa449f5f569e292333ba50954f7c07a72fbb2f9225c9195c4b6f411
7
- data.tar.gz: 30cc7f3a5da1c15b5c3508e0850c007bddf9f22a85c9ec1361821be7bc330afd3869cd9fe74d3b424afc2d0ad2d180c4c0649b5f502f4740ca68f1b2c8119933
6
+ metadata.gz: c8e9cbbef843556bd9cbc6f510223627712fbe035ade98e7ce005f13dfe7082668d42a7018857d7015d3236aa970d0b049e5fdfffc9829b0021c0c5cabeb6297
7
+ data.tar.gz: bce2b74fe864a48a6ab3155595431e81f08cf3ae5c0069612165885b96819492a4258bb613fbd01ea35e51962c12f582be334e6554cfaf877bae617dcd690e5e
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (1.17.0)
5
- activesupport (>= 6.1, < 7.1)
4
+ gitlab_quality-test_tooling (1.21.0)
5
+ activesupport (>= 6.1, < 7.2)
6
6
  amatch (~> 0.4.1)
7
7
  gitlab (~> 4.19)
8
8
  http (~> 5.0)
@@ -16,10 +16,15 @@ PATH
16
16
  GEM
17
17
  remote: https://rubygems.org/
18
18
  specs:
19
- activesupport (7.0.8)
19
+ activesupport (7.1.3.2)
20
+ base64
21
+ bigdecimal
20
22
  concurrent-ruby (~> 1.0, >= 1.0.2)
23
+ connection_pool (>= 2.2.5)
24
+ drb
21
25
  i18n (>= 1.6, < 2)
22
26
  minitest (>= 5.1)
27
+ mutex_m
23
28
  tzinfo (~> 2.0)
24
29
  addressable (2.8.6)
25
30
  public_suffix (>= 2.0.2, < 6.0)
@@ -28,7 +33,9 @@ GEM
28
33
  tins (~> 1.0)
29
34
  ast (2.4.2)
30
35
  backport (1.2.0)
36
+ base64 (0.2.0)
31
37
  benchmark (0.3.0)
38
+ bigdecimal (3.1.6)
32
39
  binding_of_caller (1.0.0)
33
40
  debug_inspector (>= 0.0.1)
34
41
  byebug (11.1.3)
@@ -41,6 +48,7 @@ GEM
41
48
  coderay (1.1.3)
42
49
  colored2 (3.1.2)
43
50
  concurrent-ruby (1.2.3)
51
+ connection_pool (2.4.1)
44
52
  cork (0.3.0)
45
53
  colored2 (~> 3.1)
46
54
  crack (0.4.5)
@@ -65,6 +73,7 @@ GEM
65
73
  diff-lcs (1.5.0)
66
74
  docile (1.4.0)
67
75
  domain_name (0.6.20240107)
76
+ drb (2.2.1)
68
77
  e2mmap (0.1.0)
69
78
  faraday (2.9.0)
70
79
  faraday-net_http (>= 2.0, < 3.2)
@@ -119,7 +128,7 @@ GEM
119
128
  httparty (0.21.0)
120
129
  mini_mime (>= 1.0.0)
121
130
  multi_xml (>= 0.5.2)
122
- i18n (1.14.1)
131
+ i18n (1.14.4)
123
132
  concurrent-ruby (~> 1.0)
124
133
  jaro_winkler (1.5.6)
125
134
  json (2.7.1)
@@ -138,10 +147,11 @@ GEM
138
147
  method_source (1.0.0)
139
148
  mini_mime (1.1.5)
140
149
  mini_portile2 (2.8.5)
141
- minitest (5.21.2)
150
+ minitest (5.22.2)
142
151
  mize (0.4.1)
143
152
  protocol (~> 2.0)
144
153
  multi_xml (0.6.0)
154
+ mutex_m (0.2.0)
145
155
  nap (1.1.0)
146
156
  nenv (0.3.0)
147
157
  net-http (0.4.1)
@@ -318,4 +328,4 @@ DEPENDENCIES
318
328
  webmock (= 3.7.0)
319
329
 
320
330
  BUNDLED WITH
321
- 2.5.4
331
+ 2.5.6
@@ -31,6 +31,10 @@ options = OptionParser.new do |opts|
31
31
  params[:issue_url_file] = issue_url_file
32
32
  end
33
33
 
34
+ opts.on('--pipeline-stages STAGES', String, 'Comma-separated list of pipeline stages to include in test session issue') do |pipeline_stages|
35
+ params[:pipeline_stages] = pipeline_stages.split(',')
36
+ end
37
+
34
38
  opts.on('--confidential', "Makes test session issue confidential") do
35
39
  params[:confidential] = true
36
40
  end
data/lefthook.yml CHANGED
@@ -17,7 +17,8 @@ pre-push:
17
17
  # Changelog git trailer for the first commit of the branch
18
18
  changelog-on-first-commit:
19
19
  run: |
20
- first_commit_message=$(git log --format=%B -n 1 $(git log main..HEAD --pretty=format:"%h" | tail -1))
20
+ git fetch origin main
21
+ first_commit_message=$(git log --format=%B -n 1 $(git log origin/main..HEAD --pretty=format:"%h" | tail -1))
21
22
  if ! echo ${first_commit_message} | grep "Changelog:"; then
22
23
  echo Could not find a Changelog: git trailer on the first commit for this branch.
23
24
  echo
@@ -6,19 +6,23 @@ module GitlabQuality
6
6
  module TestTooling
7
7
  module Concerns
8
8
  module FindSetDri
9
- def set_dri_via_group(product_group, stage)
9
+ def test_dri(product_group, stage, section)
10
10
  parse_json_with_sets
11
+ fetch_section_sets(section)
11
12
  fetch_stage_sets(stage)
12
-
13
- return @sets.sample['username'] if @stage_sets.empty?
14
-
15
13
  fetch_group_sets(product_group)
16
14
 
17
- if @group_sets.empty?
18
- @stage_sets.sample['username']
19
- else
20
- @group_sets.sample['username']
21
- end
15
+ set_dris = if @group_sets.any?
16
+ @group_sets
17
+ elsif @stage_sets.any?
18
+ @stage_sets
19
+ elsif @section_sets.any?
20
+ @section_sets
21
+ else
22
+ @sets
23
+ end
24
+
25
+ set_dris.sample['username']
22
26
  end
23
27
 
24
28
  private
@@ -30,6 +34,15 @@ module GitlabQuality
30
34
  @sets = JSON.parse(response.body).select { |user| user['role'].include?('software-engineer-in-test') }
31
35
  end
32
36
 
37
+ def fetch_section_sets(section)
38
+ @section_sets = []
39
+ return if section.nil?
40
+
41
+ @section_sets = @sets.select do |user|
42
+ user['role'].include?(section.split("_").map(&:capitalize).join(" "))
43
+ end
44
+ end
45
+
33
46
  def fetch_stage_sets(stage)
34
47
  @stage_sets = @sets.select do |user|
35
48
  user['role'].include?(stage.split("_").map(&:capitalize).join(" "))
@@ -27,7 +27,7 @@ module GitlabQuality
27
27
 
28
28
  raise if @retry_backoff > RETRY_BACK_OFF_DELAY * MAX_RETRY_ATTEMPTS
29
29
 
30
- warn("#{error.class.name} #{error.message}")
30
+ warn("#{e.class.name} #{e.message}")
31
31
  warn("Sleeping for #{@retry_backoff} seconds before retrying...")
32
32
  sleep @retry_backoff
33
33
 
@@ -4,17 +4,18 @@ module GitlabQuality
4
4
  module TestTooling
5
5
  module GitlabClient
6
6
  class RepositoryFilesClient < GitlabClient
7
- attr_reader :file_path
7
+ attr_reader :file_path, :ref
8
8
 
9
- def initialize(file_path:, **kwargs)
9
+ def initialize(file_path:, ref:, **kwargs)
10
10
  @file_path = file_path
11
+ @ref = ref
11
12
 
12
13
  super
13
14
  end
14
15
 
15
16
  def file_contents
16
17
  handle_gitlab_client_exceptions do
17
- client.file_contents(project, file_path.gsub(%r{^/}, ""))
18
+ client.file_contents(project, file_path.gsub(%r{^/}, ""), ref)
18
19
  end
19
20
  end
20
21
  end
@@ -7,15 +7,16 @@ module GitlabQuality
7
7
  module TestTooling
8
8
  module Report
9
9
  class GenerateTestSession < ReportAsIssue
10
- def initialize(ci_project_token:, **kwargs)
10
+ def initialize(ci_project_token:, pipeline_stages: nil, **kwargs)
11
11
  super
12
12
  @ci_project_token = ci_project_token
13
+ @pipeline_stages = Set.new(pipeline_stages)
13
14
  @issue_type = 'issue'
14
15
  end
15
16
 
16
17
  private
17
18
 
18
- attr_reader :ci_project_token
19
+ attr_reader :ci_project_token, :pipeline_stages
19
20
 
20
21
  # rubocop:disable Metrics/AbcSize
21
22
  def run!
@@ -27,6 +28,8 @@ module GitlabQuality
27
28
  TestResults::JsonTestResults.new(path).to_a
28
29
  end
29
30
 
31
+ tests = tests.select { |test| pipeline_stages.include? test.report["stage"] } unless pipeline_stages.empty?
32
+
30
33
  issue = gitlab.create_issue(
31
34
  title: "#{Time.now.strftime('%Y-%m-%d')} Test session report | #{Runtime::Env.qa_run_type}",
32
35
  description: generate_description(tests),
@@ -80,20 +83,10 @@ module GitlabQuality
80
83
  end
81
84
 
82
85
  def generate_failed_jobs_listing
83
- failed_jobs = []
84
-
85
- ci_project_client = Gitlab.client(
86
- endpoint: Runtime::Env.ci_api_v4_url,
87
- private_token: ci_project_token)
88
-
89
- gitlab.handle_gitlab_client_exceptions do
90
- failed_jobs = ci_project_client.pipeline_jobs(
91
- Runtime::Env.ci_project_id,
92
- Runtime::Env.ci_pipeline_id,
93
- scope: 'failed')
94
- end
86
+ failed_jobs = fetch_pipeline_failed_jobs
87
+ listings = failed_jobs.filter_map do |job|
88
+ next if pipeline_stages.any? && !pipeline_stages.include?(job.stage)
95
89
 
96
- listings = failed_jobs.map do |job|
97
90
  allowed_to_fail = ' (allowed to fail)' if job.allow_failure
98
91
 
99
92
  "* [#{job.name}](#{job.web_url})#{allowed_to_fail}"
@@ -272,6 +265,23 @@ module GitlabQuality
272
265
  * https://dashboards.quality.gitlab.net/d/tR_SmBDVk/main-runs?orgId=1&refresh=1m&var-run_type=#{Runtime::Env.qa_run_type}
273
266
  MARKDOWN
274
267
  end
268
+
269
+ def fetch_pipeline_failed_jobs
270
+ failed_jobs = []
271
+
272
+ ci_project_client = Gitlab.client(
273
+ endpoint: Runtime::Env.ci_api_v4_url,
274
+ private_token: ci_project_token)
275
+
276
+ gitlab.handle_gitlab_client_exceptions do
277
+ failed_jobs = ci_project_client.pipeline_jobs(
278
+ Runtime::Env.ci_project_id,
279
+ Runtime::Env.ci_pipeline_id,
280
+ scope: 'failed')
281
+ end
282
+
283
+ failed_jobs
284
+ end
275
285
  end
276
286
  end
277
287
  end
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'nokogiri'
4
- require 'active_support/core_ext/enumerable'
5
4
  require 'rubygems/text'
6
- require 'active_support/core_ext/integer/time'
7
5
  require 'amatch'
8
6
 
9
7
  module GitlabQuality
@@ -330,7 +328,7 @@ module GitlabQuality
330
328
  def new_issue_assignee_id(test)
331
329
  return unless test.product_group?
332
330
 
333
- dri = set_dri_via_group(test.product_group, test.stage)
331
+ dri = test_dri(test.product_group, test.stage, test.section)
334
332
  puts " => Assigning #{dri} as DRI for the issue."
335
333
 
336
334
  gitlab.find_user_id(username: dri)
@@ -339,7 +337,7 @@ module GitlabQuality
339
337
  def new_issue_due_date(test)
340
338
  return unless test.product_group?
341
339
 
342
- Date.today + 1.month
340
+ Date.today.next_month
343
341
  end
344
342
 
345
343
  def update_reports(issue, test)
@@ -60,7 +60,6 @@ module GitlabQuality
60
60
  | Description | `#{test.name}` |
61
61
  | Test level | #{test.level} |
62
62
  | Hash | `#{test_hash(test)}` |
63
- | Reference duration | #{test.run_time} seconds |
64
63
  | Expected duration | < #{test.max_duration_for_test} seconds |
65
64
  #{"| Test case | #{test.testcase} |" if test.testcase}
66
65
  DESCRIPTION
@@ -93,7 +93,7 @@ module GitlabQuality
93
93
  current_reports_content: reports_note&.body.to_s,
94
94
  test: test,
95
95
  reports_section_header: REPORT_SECTION_HEADER,
96
- item_extra_content: found_label,
96
+ item_extra_content: "(#{test.run_time} seconds) #{found_label}",
97
97
  reports_extra_content: REPORTS_DOCUMENTATION
98
98
  )
99
99
  end
@@ -38,7 +38,7 @@ module GitlabQuality
38
38
  console_log = console_logger(source: source, level: Env.log_level)
39
39
  file_log = file_logger(source: source, path: log_path)
40
40
 
41
- console_log.extend(ActiveSupport::Logger.broadcast(file_log))
41
+ ActiveSupport::BroadcastLogger.new(console_log, file_log, file_log)
42
42
  end
43
43
  end
44
44
 
@@ -6,86 +6,42 @@ module GitlabQuality
6
6
  module Processor
7
7
  class AddToBlockingProcessor < MetaProcessor
8
8
  BLOCKING_METADATA = ", :blocking%{suffix}"
9
+ BRANCH_PREFIX = 'blocking-promotion'
9
10
 
10
11
  class << self
11
- # Execute the processor
12
+ # Creates the merge requests for promoting E2E tests to :blocking
12
13
  #
13
- # @param [Hash] spec the spec to update
14
14
  # @param [TestMetaUpdater] context instance of TestMetaUpdater
15
- def execute(spec, context) # rubocop:disable Metrics/AbcSize
15
+ def create_merge_requests(context)
16
16
  @context = context
17
- @existing_mrs = nil
18
- @file_path = spec["file_path"]
19
- testcase = spec["testcase"]
20
- devops_stage = spec["stage"]
21
- product_group = spec["product_group"]
22
- @example_name = spec["name"]
23
- @mr_title = format("%{prefix} %{example_name}", prefix: '[PROMOTE TO BLOCKING]', example_name: example_name).truncate(72, omission: '')
24
17
 
25
- @file_contents = context.get_file_contents(file_path)
18
+ context.processed_commits.each_value do |record|
19
+ branch, devops_stage, product_group, file, reviewer_id, assignee_handle = extract_data_from_record(record)
26
20
 
27
- new_content, @changed_line_no = add_blocking_metadata
21
+ mr_title = format("%{prefix} %{file}", prefix: '[E2E] PROMOTE TO BLOCKING:', file: file).truncate(72, omission: '')
28
22
 
29
- return unless proceed_with_merge_request?
30
-
31
- branch = context.create_branch("blocking-promotion-#{SecureRandom.hex(4)}", example_name, context.ref)
32
-
33
- context.commit_changes(branch, <<~COMMIT_MESSAGE, file_path, new_content)
34
- Promote end-to-end test to blocking
35
-
36
- #{"Promote to blocking: #{example_name}".truncate(72)}
37
- COMMIT_MESSAGE
38
-
39
- reviewer_id, assignee_handle = context.fetch_dri_id(product_group, devops_stage)
40
-
41
- gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
42
-
43
- merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id, [reviewer_id]) do
44
- <<~MARKDOWN
45
- ## What does this MR do?
46
-
47
- Promotes the test [`#{example_name}`](https://gitlab.com/#{context.project}/-/blob/#{context.ref}/#{file_path}#L#{changed_line_no + 1})
48
- to the blocking bucket
49
-
50
- This test was identified in the reliable e2e test report: #{context.report_issue}
51
-
52
- [Testcase link](#{testcase})
53
-
54
- [Spec metrics link](#{context.single_spec_metrics_link(example_name)})
55
-
56
- /label ~"Quality" ~"QA" ~"type::maintenance"
57
- /label ~"devops::#{devops_stage}"
58
- #{context.label_from_product_group(product_group)}
59
-
60
- <div align="center">
61
- (This MR was automatically generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling) at #{Time.now.utc})
62
- </div>
63
- MARKDOWN
64
- end
65
-
66
- context.post_note_on_merge_request(<<~MARKDOWN, merge_request.iid)
67
- @#{assignee_handle} Please review this MR, approve and assign it to a maintainer.
23
+ merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id, [reviewer_id]) do
24
+ merge_request_description(record, devops_stage, product_group)
25
+ end
68
26
 
69
- If you think this MR should not be merged, please close it and add a note of the reason to the blocking report: #{context.report_issue}
70
- MARKDOWN
27
+ context.post_note_on_merge_request(maintainer_note_on_merge_request(assignee_handle), merge_request.iid)
71
28
 
72
- if merge_request
73
- context.add_processed_record({ file_path => changed_line_no })
74
- Runtime::Logger.info(" Created MR for promotion to blocking: #{merge_request.web_url}")
29
+ if merge_request
30
+ Runtime::Logger.info(" Created MR for promotion to blocking: #{merge_request.web_url}")
31
+ record[:merge_request] = merge_request
32
+ end
75
33
  end
76
-
77
- merge_request
78
34
  end
79
35
 
80
- # Performs post processing. Takes a list of MRs and posts them in a note on report_issue
36
+ # Performs post processing. Posts a list of MRs in a note on report_issue
81
37
  #
82
- # @param [Gitlab::ObjectifiedHash] merge_requests
83
- def post_process(merge_requests)
84
- web_urls = merge_requests.compact.map { |mr| "- #{mr.web_url}\n" }.join
38
+ # @param [TestMetaUpdater] context instance of TestMetaUpdater
39
+ def post_process(context)
40
+ web_urls = context.processed_commits.values.map { |value| "- #{value[:merge_request].web_url}\n" }.join
85
41
 
86
42
  return if web_urls.empty?
87
43
 
88
- context.post_note_on_report_issue(<<~ISSUE_NOTE)
44
+ context.post_note_on_issue(<<~ISSUE_NOTE, context.report_issue)
89
45
  The following merge requests have been created to promote stable specs to blocking:
90
46
 
91
47
  #{web_urls}
@@ -94,34 +50,80 @@ module GitlabQuality
94
50
 
95
51
  private
96
52
 
97
- attr_reader :context, :file_path, :file_contents, :example_name, :mr_title, :changed_line_no
53
+ attr_reader :context, :file_path, :file, :file_contents, :example_name, :mr_title, :changed_line_no
98
54
 
99
55
  # Checks if there is already an MR open
100
56
  #
101
57
  # @return [Boolean]
102
- def proceed_with_merge_request? # rubocop:disable Metrics/AbcSize
58
+ def proceed_with_commit?
103
59
  if changed_line_no.negative?
104
60
  Runtime::Logger.info(" No lines were changed in #{file_path}. Will not proceed with creating MR.")
105
61
  return false
106
- elsif context.record_processed?(file_path, changed_line_no)
62
+ elsif context.commit_processed?(file_path, changed_line_no)
107
63
  Runtime::Logger.info(" Record already processed for #{file_path}:#{changed_line_no}. Will not proceed with creating MR.")
108
64
  return false
109
- elsif existing_mrs&.any?
110
- Runtime::Logger.info(" An open MR already exists for '#{example_name}': #{existing_mrs.first['web_url']}. Will not proceed with creating MR.")
111
- return false
112
65
  end
113
66
 
114
67
  true
115
68
  end
116
69
 
70
+ def commit_message
71
+ <<~COMMIT_MESSAGE
72
+ Promote end-to-end test to blocking
73
+
74
+ #{"Promote to blocking: #{example_name}".truncate(72)}
75
+ COMMIT_MESSAGE
76
+ end
77
+
78
+ def maintainer_note_on_merge_request(assignee_handle)
79
+ <<~MARKDOWN
80
+ @#{assignee_handle} Please review this MR, approve and assign it to a maintainer.
81
+
82
+ If you think this MR should not be merged, please close it and add a note of the reason to the blocking report: #{context.report_issue}
83
+ MARKDOWN
84
+ end
85
+
86
+ def merge_request_description(record, devops_stage, product_group)
87
+ <<~MARKDOWN
88
+ ## What does this MR do?
89
+
90
+ Promotes the following e2e tests to the blocking bucket:
91
+
92
+ #{spec_details_from_commits(record[:commits])}
93
+
94
+ This MR was created based on data from reliable e2e test report: #{context.report_issue}
95
+
96
+ /label ~"Quality" ~"QA" ~"type::maintenance"
97
+ /label ~"devops::#{devops_stage}"
98
+ #{context.label_from_product_group(product_group)}
99
+
100
+ <div align="center">
101
+ (This MR was automatically generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling) at #{Time.now.utc})
102
+ </div>
103
+ MARKDOWN
104
+ end
105
+
106
+ def extract_data_from_record(record)
107
+ first_spec = record[:commits].values.first
108
+ product_group = first_spec["product_group"]
109
+ devops_stage = first_spec["stage"]
110
+ reviewer_id, assignee_handle = context.fetch_dri_id(product_group, devops_stage, first_spec["section"])
111
+
112
+ [record[:branch], devops_stage, product_group, first_spec["file"], reviewer_id, assignee_handle]
113
+ end
114
+
115
+ def gitlab_bot_user_id
116
+ context.user_id_for_username(Runtime::Env.gitlab_bot_username)
117
+ end
118
+
117
119
  # Add blocking metadata to the file content and replace it
118
120
  #
119
121
  # @return [Array<String, Integer>] first value holds the new content, the second value holds the line number of the test
120
- def add_blocking_metadata # rubocop:disable Metrics/AbcSize
122
+ def add_metadata # rubocop:disable Metrics/AbcSize
121
123
  matched_lines = context.find_example_match_lines(file_contents, example_name)
122
124
 
123
125
  if matched_lines.any? { |line| line[0].include?(':blocking') }
124
- puts "Example '#{example_name}' is already blocking"
126
+ Runtime::Logger.info("Example '#{example_name}' is already blocking")
125
127
  return [file_contents, -1]
126
128
  end
127
129
 
@@ -12,149 +12,193 @@ module GitlabQuality
12
12
  %{indentation} type: %{quarantine_type}
13
13
  %{indentation}}%{suffix}
14
14
  META
15
+ BRANCH_PREFIX = 'quarantine'
15
16
 
16
17
  class << self
17
- # Execute the processor
18
+ # Creates the merge requests to quarantine E2E tests
18
19
  #
19
- # @param [Hash<String,String>] spec the spec to update
20
- # @option spec [String] :file_path the path to the spec file
21
- # @option spec [String] :stage the stage of the test
22
- # @option spec [String] :failure_issue the issue url of the failure
23
- # @option spec [String] :name the name of the example
24
20
  # @param [TestMetaUpdater] context instance of TestMetaUpdater
25
- def execute(spec, context) # rubocop:disable Metrics/AbcSize
21
+ def create_merge_requests(context)
26
22
  @context = context
27
- @existing_mrs = nil
28
- @file_path = spec["file_path"]
29
- testcase = spec["testcase"]
30
- devops_stage = spec["stage"]
31
- product_group = spec["product_group"]
32
- @failure_issue_url = spec["failure_issue"]
33
- @example_name = spec["name"]
34
- @issue_id = failure_issue_url.split('/').last # split url segment, last segment of path is the issue id
35
- @mr_title = format("%{prefix} %{example_name}", prefix: '[QUARANTINE]', example_name: example_name).truncate(72, omission: '')
36
- @failure_issue = context.fetch_issue(iid: issue_id)
37
23
 
38
- @file_contents = context.get_file_contents(file_path)
24
+ context.processed_commits.each_value do |record|
25
+ branch, devops_stage, product_group, file = extract_data_from_record(record)
39
26
 
40
- new_content, @changed_line_no = add_quarantine_metadata
27
+ mr_title = format("%{prefix} %{file}", prefix: '[E2E] QUARANTINE:', file: file).truncate(72, omission: '')
41
28
 
42
- return unless proceed_with_merge_request?
29
+ gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
43
30
 
44
- branch = context.create_branch("#{issue_id}-quarantine-#{SecureRandom.hex(4)}", example_name, context.ref)
31
+ merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id) do
32
+ merge_request_description(record, devops_stage, product_group)
33
+ end
45
34
 
46
- context.commit_changes(branch, <<~COMMIT_MESSAGE, file_path, new_content)
47
- Quarantine end-to-end test
35
+ if merge_request
36
+ Runtime::Logger.info(" Created MR for quarantine: #{merge_request.web_url}")
37
+ record[:merge_request] = merge_request
38
+ end
39
+ end
40
+ end
48
41
 
49
- #{"Quarantine #{example_name}".truncate(72)}
50
- COMMIT_MESSAGE
42
+ # Performs post processing. Posts a list of MRs in a note on report_issue and Slack.
43
+ # Also posts note on failure issues for un-quarantining of the quarantined
44
+ #
45
+ # @param [TestMetaUpdater] context instance of TestMetaUpdater
46
+ def post_process(context)
47
+ @context = context
51
48
 
52
- gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
49
+ web_urls = context.processed_commits.values.map { |value| "- #{value[:merge_request].web_url}\n" }.join
53
50
 
54
- merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id) do
55
- <<~MARKDOWN
56
- ## What does this MR do?
51
+ return if web_urls.empty?
57
52
 
58
- Quarantines the test [`#{example_name}`](https://gitlab.com/#{context.project}/-/blob/#{context.ref}/#{file_path}#L#{changed_line_no + 1})
53
+ context.post_note_on_issue(mrs_created_note_for_report_issue(web_urls), context.report_issue)
59
54
 
60
- This test was identified in the reliable e2e test report: #{context.report_issue}
55
+ context.post_message_on_slack(mrs_created_message_for_slack(web_urls))
61
56
 
62
- [Testcase link](#{testcase})
57
+ post_unquarantine_note_on_failure_issue
58
+ end
63
59
 
64
- [Spec metrics link](#{context.single_spec_metrics_link(example_name)})
60
+ private
65
61
 
66
- ### E2E Test Failure issue(s)
62
+ attr_reader :context, :file_path, :file_contents, :failure_issue_url, :example_name,
63
+ :mr_title, :failure_issue, :changed_line_no
67
64
 
68
- #{failure_issue_url}
65
+ # Checks if the failure issue is closed or if there is already an MR open
66
+ #
67
+ # @return [Boolean]
68
+ def proceed_with_commit?
69
+ if context.issue_is_closed?(failure_issue)
70
+ Runtime::Logger.info(" Failure issue '#{failure_issue_url}' is closed. Will not proceed with creating MR.")
71
+ return false
72
+ elsif context.commit_processed?(file_path, changed_line_no)
73
+ Runtime::Logger.info(" Record already processed for #{file_path}:#{changed_line_no}. Will not proceed with creating MR.")
74
+ return false
75
+ elsif failure_is_related_to_test_environment?
76
+ Runtime::Logger.info(" Failure issue '#{failure_issue_url}' is environment related. Will not proceed with creating MR.")
77
+ return false
78
+ end
69
79
 
70
- ### Check-list
80
+ true
81
+ end
71
82
 
72
- - [ ] General code guidelines check-list
73
- - [ ] [Code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
74
- - [ ] [Style guides](https://docs.gitlab.com/ee/development/contributing/style_guides.html)
75
- - [ ] Quarantine test check-list
76
- - [ ] Follow the [Quarantining Tests guide](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantining-tests).
77
- - [ ] Confirm the test has a [`quarantine:` tag with the specified quarantine type](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantined-test-types).
78
- - [ ] Note if the test should be [quarantined for a specific environment](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/execution_context_selection.html#quarantine-a-test-for-a-specific-environment).
79
- - [ ] (Optionally) In case of an emergency (e.g. blocked deployments), consider adding labels to pick into auto-deploy (~"Pick into auto-deploy" ~"priority::1" ~"severity::1").
80
- - [ ] To ensure a faster turnaround, ask in the `#quality_maintainers` Slack channel for someone to review and merge the merge request, rather than assigning it directly.
83
+ def failure_is_related_to_test_environment?
84
+ context.issue_scoped_label(failure_issue, 'failure')&.split('::')&.last == 'test-environment'
85
+ end
81
86
 
82
- <!-- Base labels. -->
83
- /label ~"Quality" ~"QA" ~"type::maintenance" ~"maintenance::pipelines"
87
+ def extract_data_from_record(record)
88
+ first_spec = record[:commits].values.first
89
+ [record[:branch], first_spec["stage"], first_spec["product_group"], first_spec["file"]]
90
+ end
91
+
92
+ # Posts note on failure issue to un-quarantine the test
93
+ #
94
+ def post_unquarantine_note_on_failure_issue
95
+ context.processed_commits.each_value do |record|
96
+ merge_request = record[:merge_request]
97
+ next unless merge_request
84
98
 
85
- <!--
86
- Choose the stage that appears in the test path, e.g. ~"devops::create" for
87
- `qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb`.
88
- -->
89
- /label ~"devops::#{devops_stage}"
90
- #{context.label_from_product_group(product_group)}
99
+ record[:commits].each_value do |spec|
100
+ devops_stage = spec["stage"]
101
+ product_group = spec["product_group"]
102
+ failure_issue = spec["failure_issue"]
91
103
 
92
- <div align="center">
93
- (This MR was automatically generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling) at #{Time.now.utc})
94
- </div>
95
- MARKDOWN
96
- end
104
+ next unless failure_issue
97
105
 
98
- if merge_request
99
- context.add_processed_record({ file_path => changed_line_no })
100
- Runtime::Logger.info(" Created MR for quarantine: #{merge_request.web_url}")
106
+ note = context.post_note_on_issue(unquarantine_note_for_failure(spec, product_group, devops_stage, merge_request),
107
+ failure_issue)
108
+
109
+ Runtime::Logger.info(" Posted note on failure issue for un-quarantine: #{failure_issue}") if note
110
+ end
101
111
  end
112
+ end
113
+
114
+ def merge_request_description(record, devops_stage, product_group)
115
+ <<~MARKDOWN
116
+ ## What does this MR do?
102
117
 
103
- merge_request
118
+ Quarantines the following e2e tests:
119
+
120
+ #{spec_details_from_commits(record[:commits])}
121
+
122
+ This MR was created based on data from reliable e2e test report: #{context.report_issue}
123
+
124
+ ### Check-list
125
+
126
+ - [ ] General code guidelines check-list
127
+ - [ ] [Code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
128
+ - [ ] [Style guides](https://docs.gitlab.com/ee/development/contributing/style_guides.html)
129
+ - [ ] Quarantine test check-list
130
+ - [ ] Follow the [Quarantining Tests guide](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantining-tests).
131
+ - [ ] Confirm the test has a [`quarantine:` tag with the specified quarantine type](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantined-test-types).
132
+ - [ ] Note if the test should be [quarantined for a specific environment](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/execution_context_selection.html#quarantine-a-test-for-a-specific-environment).
133
+ - [ ] (Optionally) In case of an emergency (e.g. blocked deployments), consider adding labels to pick into auto-deploy (~"Pick into auto-deploy" ~"priority::1" ~"severity::1").
134
+ - [ ] To ensure a faster turnaround, ask in the `#quality_maintainers` Slack channel for someone to review and merge the merge request, rather than assigning it directly.
135
+
136
+ <!-- Base labels. -->
137
+ /label ~"Quality" ~"QA" ~"type::maintenance" ~"maintenance::pipelines"
138
+
139
+ <!--
140
+ Choose the stage that appears in the test path, e.g. ~"devops::create" for
141
+ `qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb`.
142
+ -->
143
+ /label ~"devops::#{devops_stage}"
144
+ #{context.label_from_product_group(product_group)}
145
+
146
+ <div align="center">
147
+ (This MR was automatically generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling) at #{Time.now.utc})
148
+ </div>
149
+ MARKDOWN
104
150
  end
105
151
 
106
- # Performs post processing. Takes a list of MRs and posts them in a note on report_issue and Slack
107
- #
108
- # @param [Gitlab::ObjectifiedHash] merge_requests
109
- def post_process(merge_requests)
110
- web_urls = merge_requests.compact.map { |mr| "- #{mr.web_url}\n" }.join
152
+ def commit_message
153
+ <<~COMMIT_MESSAGE
154
+ Quarantine end-to-end test
111
155
 
112
- return if web_urls.empty?
156
+ #{"Quarantine #{example_name}".truncate(72)}
157
+ COMMIT_MESSAGE
158
+ end
113
159
 
114
- context.post_note_on_report_issue(<<~ISSUE_NOTE)
160
+ def mrs_created_note_for_report_issue(web_urls)
161
+ <<~ISSUE_NOTE
115
162
 
116
163
  The following merge requests have been created to quarantine the unstable tests:
117
164
 
118
165
  #{web_urls}
119
166
  ISSUE_NOTE
167
+ end
168
+
169
+ def unquarantine_note_for_failure(spec, product_group, devops_stage, merge_request)
170
+ failure_issue_assignee_handle = get_failure_issue_assignee_handle(spec, product_group, devops_stage)
171
+ <<~ISSUE_NOTE
172
+ @#{failure_issue_assignee_handle} This test will be quarantined in #{merge_request.web_url} based on data from reliable e2e test report: #{context.report_issue}
173
+
174
+ Please take this issue forward to un-quarantine the test by either addressing the issue yourself or delegating it to the relevant product group.
175
+
176
+ If this issue is delegated, please make sure to update the assignee. Thanks.
177
+ ISSUE_NOTE
178
+ end
120
179
 
121
- context.post_message_on_slack(<<~MSG)
180
+ def mrs_created_message_for_slack(web_urls)
181
+ <<~MSG
122
182
  *Action Required!* The following merge requests have been created to quarantine the unstable tests identified
123
183
  in the reliable test report: #{context.report_issue}
124
184
 
125
185
  #{web_urls}
126
186
 
127
- Maintainers are requested to review and merge. Thank you.
187
+ Maintainers are requested to review and merge the above MRs. Thank you.
128
188
  MSG
129
189
  end
130
190
 
131
- private
132
-
133
- attr_reader :context, :file_path, :file_contents, :failure_issue_url, :example_name,
134
- :issue_id, :mr_title, :failure_issue, :changed_line_no
191
+ def get_failure_issue_assignee_handle(spec, product_group, devops_stage)
192
+ return spec["failure_issue_assignee_handle"] if spec["failure_issue_assignee_handle"]
135
193
 
136
- # Checks if the failure issue is closed or if there is already an MR open
137
- #
138
- # @return [Boolean]
139
- def proceed_with_merge_request? # rubocop:disable Metrics/AbcSize
140
- if context.issue_is_closed?(failure_issue)
141
- Runtime::Logger.info(" Failure issue '#{failure_issue_url}' is closed. Will not proceed with creating MR.")
142
- return false
143
- elsif context.record_processed?(file_path, changed_line_no)
144
- Runtime::Logger.info(" Record already processed for #{file_path}:#{changed_line_no}. Will not proceed with creating MR.")
145
- return false
146
- elsif existing_mrs&.any?
147
- Runtime::Logger.info(" An open MR already exists for '#{example_name}': #{existing_mrs.first['web_url']}. Will not proceed with creating MR.")
148
- return false
149
- end
150
-
151
- true
194
+ _, assignee_handle = context.fetch_dri_id(product_group, devops_stage, nil)
195
+ assignee_handle
152
196
  end
153
197
 
154
198
  # Add quarantine metadata to the file content and replace it
155
199
  #
156
200
  # @return [Array<String, Integer>] first value holds the new content, the second value holds the line number of the test
157
- def add_quarantine_metadata # rubocop:disable Metrics/AbcSize
201
+ def add_metadata # rubocop:disable Metrics/AbcSize
158
202
  matched_lines = context.find_example_match_lines(file_contents, example_name)
159
203
 
160
204
  context.update_matched_line(matched_lines.last, file_contents.dup) do |line|
@@ -10,12 +10,12 @@ module GitlabQuality
10
10
  class << self
11
11
  DESCRIPTION_REGEX = /('.*?')|(".*?")/
12
12
 
13
- def execute
14
- raise 'method not implemented'
13
+ def post_process
14
+ raise NotImplementedError, 'Subclass must implement this method'
15
15
  end
16
16
 
17
- def post_process
18
- raise 'method not implemented'
17
+ def create_merge_requests
18
+ raise NotImplementedError, 'Subclass must implement this method'
19
19
  end
20
20
 
21
21
  private_class_method :new
@@ -36,6 +36,65 @@ module GitlabQuality
36
36
  description_start_index = line.index(DESCRIPTION_REGEX)
37
37
  description_start_index + description_length
38
38
  end
39
+
40
+ # List specs in markdown with details such as link to code, testcase, metrics and failure issue
41
+ #
42
+ # @param [Hash<String,Hash>] commits The commits hash to use for spec details
43
+ # @return String
44
+ def spec_details_from_commits(commits)
45
+ commits.each_with_index.map do |(changed_line_number, spec), index|
46
+ <<~MARKDOWN
47
+ #{index + 1}. [`#{spec['name']}`](https://gitlab.com/#{context.project}/-/blob/#{context.ref}/#{spec['file_path']}#L#{changed_line_number.to_i + 1})
48
+ | [Testcase](#{spec['testcase']}) | [Spec metrics](#{context.single_spec_metrics_link(spec['name'])})
49
+ #{failure_issue_text(spec)}
50
+ MARKDOWN
51
+ end.join("\n")
52
+ end
53
+
54
+ # Returns a string in markdown of failure issue and its link
55
+ #
56
+ # @param [Hash] spec the spec for failure issue
57
+ # @return [String]
58
+ def failure_issue_text(spec)
59
+ spec['failure_issue'].empty? ? '' : "| [Failure issue](#{spec['failure_issue']})"
60
+ end
61
+
62
+ # Creates a commit depending on the context provided and adds it to a Hash of created commits
63
+ #
64
+ # @param [Hash] spec the spec to update
65
+ # @param [TestMetaUpdater] context instance of TestMetaUpdater
66
+ def create_commit(spec, context) # rubocop:disable Metrics/AbcSize
67
+ @context = context
68
+ @file_path = spec["file_path"]
69
+ @file = spec["file"]
70
+ @example_name = spec["name"]
71
+ @failure_issue_url = spec["failure_issue"]
72
+
73
+ issue_id = failure_issue_url&.split('/')&.last # split url segment, last segment of path is the issue id
74
+ existing_branch = context.branch_for_file_path(file_path)
75
+
76
+ @file_contents = context.get_file_contents(file_path: file_path,
77
+ branch: existing_branch && existing_branch['name'])
78
+
79
+ @failure_issue = context.fetch_issue(iid: issue_id) if issue_id
80
+
81
+ spec['failure_issue_assignee_handle'] = @failure_issue['assignee']['username'] if @failure_issue && @failure_issue['assignee']
82
+
83
+ new_content, @changed_line_no = add_metadata
84
+
85
+ return unless proceed_with_commit?
86
+
87
+ branch = existing_branch ||
88
+ context.create_branch("#{self::BRANCH_PREFIX}-#{SecureRandom.hex(4)}", @file, context.ref)
89
+
90
+ context.commit_changes(branch, commit_message, file_path, new_content)
91
+
92
+ context.add_processed_commit(file_path, changed_line_no, branch, spec)
93
+ end
94
+
95
+ private
96
+
97
+ attr_reader :failure_issue_url, :failure_issue
39
98
  end
40
99
  end
41
100
  end
@@ -8,9 +8,10 @@ module GitlabQuality
8
8
  class TestMetaUpdater
9
9
  include TestTooling::Concerns::FindSetDri
10
10
 
11
- attr_reader :project, :ref, :report_issue, :processed_records
11
+ attr_reader :project, :ref, :report_issue, :processed_commits
12
12
 
13
13
  TEST_PLATFORM_MAINTAINERS_SLACK_CHANNEL_ID = 'C0437FV9KBN' # test-platform-maintainers
14
+ BATCH_LIMIT = 10
14
15
 
15
16
  def initialize(token:, project:, specs_file:, processor:, ref: 'master', dry_run: false)
16
17
  @specs_file = specs_file
@@ -19,37 +20,85 @@ module GitlabQuality
19
20
  @ref = ref
20
21
  @dry_run = dry_run
21
22
  @processor = processor
22
- @processed_records = {}
23
+ @processed_commits = {}
23
24
  end
24
25
 
25
26
  def invoke!
26
27
  JSON.parse(File.read(specs_file)).tap do |contents|
27
28
  @report_issue = contents['report_issue']
28
29
 
29
- results = []
30
30
  contents['specs'].each do |spec|
31
- results << processor.execute(spec, self)
31
+ processor.create_commit(spec, self)
32
+ break if processed_commits.keys.count >= BATCH_LIMIT
32
33
  end
33
- processor.post_process(results)
34
+
35
+ processor.create_merge_requests(self)
36
+
37
+ processor.post_process(self)
38
+ end
39
+ end
40
+
41
+ # Add processed commits.
42
+ #
43
+ # processed_commits has the following form. Note that each key in the :commits hash
44
+ # is the changed line number and the value is the spec changed.
45
+ #
46
+ # {
47
+ # "/file/path/for/spec_1" =>
48
+ # { :commits =>
49
+ # {
50
+ # "34" => {"stage"=> "create", "product_group" => "source_code".. },
51
+ # "38" => {"stage"=> "create", "product_group" => "source_code".. }
52
+ # },
53
+ # :branch => #<Gitlab::ObjectifiedHash>
54
+ # },
55
+ # "/file/path/for/spec_2" =>
56
+ # { :commits =>
57
+ # {
58
+ # "34" => {"stage"=> "create", "product_group" => "source_code".. },
59
+ # "38" => {"stage"=> "create", "product_group" => "source_code".. }
60
+ # },
61
+ # :branch => #<Gitlab::ObjectifiedHash>
62
+ # },
63
+ # }
64
+ #
65
+ # @param [<String>] file_path the file path to the spec
66
+ # @param [<Integer>] changed_line_no the changed line number for the commit
67
+ # @param [<Gitlab::ObjectifiedHash>] branch the branch for the commit
68
+ # @param [<Hash>] spec spec details hash
69
+ # @return [Hash<String,Hash>] processed_commits
70
+ def add_processed_commit(file_path, changed_line_no, branch, spec)
71
+ if processed_commits[file_path].nil?
72
+ processed_commits[file_path] = { commits: { changed_line_no.to_s => spec }, branch: branch }
73
+ elsif processed_commits[file_path][:commits][changed_line_no.to_s].nil?
74
+ processed_commits[file_path][:commits].merge!({ changed_line_no.to_s => spec })
34
75
  end
35
76
  end
36
77
 
37
- # Add processed records
78
+ # Checks if changes have already been made to given file_path and line number
79
+ #
80
+ # @param [String] file_path path to the file
81
+ # @param [Integer] changed_line_no updated line number
82
+ # @return [Boolean]
83
+ def commit_processed?(file_path, changed_line_no)
84
+ processed_commits[file_path] && processed_commits[file_path][:commits][changed_line_no.to_s]
85
+ end
86
+
87
+ # Returns the branch for the given file_path
38
88
  #
39
- # @param [Hash<String,Integer>] record the processed record
40
- # @option record [String] :file_path the path to the spec file
41
- # @option spec [Intenger] :changed_line_no the line number change in file_path
42
- # @return [Hash<String,Integer>] processed_records
43
- def add_processed_record(record)
44
- @processed_records.merge!(record)
89
+ # @param [String] file_path path to the file
90
+ # @return [<Gitlab::ObjectifiedHash>]
91
+ def branch_for_file_path(file_path)
92
+ processed_commits[file_path] && processed_commits[file_path][:branch]
45
93
  end
46
94
 
47
95
  # Fetch contents of file from the repository
48
96
  #
49
97
  # @param [String] file_path path to the file
98
+ # @param [String] branch branch ref
50
99
  # @return [String] contents of the file
51
- def get_file_contents(file_path)
52
- repository_files = GitlabClient::RepositoryFilesClient.new(token: token, project: project, file_path: file_path)
100
+ def get_file_contents(file_path:, branch:)
101
+ repository_files = GitlabClient::RepositoryFilesClient.new(token: token, project: project, file_path: file_path, ref: branch || ref)
53
102
  repository_files.file_contents
54
103
  end
55
104
 
@@ -62,11 +111,17 @@ module GitlabQuality
62
111
  lines = content.split("\n")
63
112
 
64
113
  matched_lines = []
114
+ example_name_for_parsing = example_name.dup
65
115
 
66
116
  lines.each_with_index do |line, line_index|
67
117
  string_within_quotes = spec_desc_string_within_quotes(line)
68
118
 
69
- matched_lines << [line, line_index] if string_within_quotes && example_name.include?(string_within_quotes)
119
+ regex = /^\s?#{Regexp.escape(string_within_quotes)}/ if string_within_quotes
120
+
121
+ if !example_name_for_parsing.empty? && regex && example_name_for_parsing.match(regex)
122
+ example_name_for_parsing.sub!(regex, '')
123
+ matched_lines << [line, line_index]
124
+ end
70
125
  rescue StandardError => e
71
126
  puts "Error: #{e}"
72
127
  end
@@ -95,10 +150,10 @@ module GitlabQuality
95
150
  # Create a branch from the ref
96
151
  #
97
152
  # @param [String] name_prefix the prefix to attach to the branch name
98
- # @param [String] example_name the example
153
+ # @param [String] name the branch name
99
154
  # @return [Gitlab::ObjectifiedHash] the new branch
100
- def create_branch(name_prefix, example_name, ref)
101
- branch_name = [name_prefix, example_name.gsub(/\W/, '-')]
155
+ def create_branch(name_prefix, name, ref)
156
+ branch_name = [name_prefix, name.gsub(/\W/, '-')]
102
157
  @branches_client ||= (dry_run ? GitlabClient::BranchesDryClient : GitlabClient::BranchesClient).new(token: token, project: project)
103
158
  @branches_client.create(branch_name.join('-'), ref)
104
159
  end
@@ -162,16 +217,17 @@ module GitlabQuality
162
217
  issue_client.find_issues(iid: iid).first
163
218
  end
164
219
 
165
- # Post note on report_issue
220
+ # Post note on isse
166
221
  #
167
222
  # @param [String] note the note to post
168
223
  # @return [Gitlab::ObjectifiedHash]
169
- def post_note_on_report_issue(note)
170
- iid = report_issue&.split('/')&.last # split url segment, last segment of path is the issue id
224
+ def post_note_on_issue(note, issue_url)
225
+ iid = issue_url&.split('/')&.last # split url segment, last segment of path is the issue id
171
226
  if iid
172
227
  issue_client.create_issue_note(iid: iid, note: note)
173
228
  else
174
- Runtime::Logger.info("#{self.class.name}##{__method__} Note was NOT posted on report issue: #{report_issue}")
229
+ Runtime::Logger.info("#{self.class.name}##{__method__} Note was NOT posted on issue: #{issue_url}")
230
+ nil
175
231
  end
176
232
  end
177
233
 
@@ -190,8 +246,8 @@ module GitlabQuality
190
246
  # @param [String] product_group
191
247
  # @param [String] devops_stage
192
248
  # @return [Array<Integer, String>]
193
- def fetch_dri_id(product_group, devops_stage)
194
- assignee_handle = ENV.fetch('QA_TEST_DRI_HANDLE', nil) || set_dri_via_group(product_group, devops_stage)
249
+ def fetch_dri_id(product_group, devops_stage, section)
250
+ assignee_handle = ENV.fetch('QA_TEST_DRI_HANDLE', nil) || test_dri(product_group, devops_stage, section)
195
251
 
196
252
  [user_id_for_username(assignee_handle), assignee_handle]
197
253
  end
@@ -243,15 +299,6 @@ module GitlabQuality
243
299
  merge_request_client.find(options: { search: title, in: 'title', state: 'opened' })
244
300
  end
245
301
 
246
- # Checks if changes has already been made to given file and line number
247
- #
248
- # @param [String] file_path path to the file
249
- # @param [Integer] changed_line_no updated line number
250
- # @return [Boolean]
251
- def record_processed?(file_path, changed_line_no)
252
- processed_records[file_path] && processed_records[file_path] == changed_line_no
253
- end
254
-
255
302
  # Infers product group label from the provided product group
256
303
  #
257
304
  # @param [String] product_group product group
@@ -22,6 +22,10 @@ module GitlabQuality
22
22
  raise NotImplementedError
23
23
  end
24
24
 
25
+ def section
26
+ raise NotImplementedError
27
+ end
28
+
25
29
  def category
26
30
  raise NotImplementedError
27
31
  end
@@ -111,6 +111,10 @@ module GitlabQuality
111
111
  report['feature_category']
112
112
  end
113
113
 
114
+ def section
115
+ report['section']
116
+ end
117
+
114
118
  def category
115
119
  report['category']
116
120
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "1.17.0"
5
+ VERSION = "1.21.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: 1.17.0
4
+ version: 1.21.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: 2024-03-01 00:00:00.000000000 Z
11
+ date: 2024-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -201,7 +201,7 @@ dependencies:
201
201
  version: '6.1'
202
202
  - - "<"
203
203
  - !ruby/object:Gem::Version
204
- version: '7.1'
204
+ version: '7.2'
205
205
  type: :runtime
206
206
  prerelease: false
207
207
  version_requirements: !ruby/object:Gem::Requirement
@@ -211,7 +211,7 @@ dependencies:
211
211
  version: '6.1'
212
212
  - - "<"
213
213
  - !ruby/object:Gem::Version
214
- version: '7.1'
214
+ version: '7.2'
215
215
  - !ruby/object:Gem::Dependency
216
216
  name: amatch
217
217
  requirement: !ruby/object:Gem::Requirement