# frozen_string_literal: true
module GitlabQuality
module TestTooling
module TestMeta
module Processor
class AddToBlockingProcessor < MetaProcessor
BLOCKING_METADATA = ", :blocking%{suffix}"
class << self
# Execute the processor
#
# @param [Hash] spec the spec to update
# @param [TestMetaUpdater] context instance of TestMetaUpdater
def execute(spec, context) # rubocop:disable Metrics/AbcSize
@context = context
@existing_mrs = nil
@file_path = spec["file_path"]
testcase = spec["testcase"]
devops_stage = spec["stage"]
product_group = spec["product_group"]
@example_name = spec["name"]
@mr_title = format("%{prefix} %{example_name}", prefix: '[PROMOTE TO BLOCKING]', example_name: example_name).truncate(72, omission: '')
@file_contents = context.get_file_contents(file_path)
new_content, @changed_line_no = add_blocking_metadata
return unless proceed_with_merge_request?
branch = context.create_branch("blocking-promotion-#{SecureRandom.hex(4)}", example_name, context.ref)
context.commit_changes(branch, <<~COMMIT_MESSAGE, file_path, new_content)
Promote end-to-end test to blocking
#{"Promote to blocking: #{example_name}".truncate(72)}
COMMIT_MESSAGE
reviewer_id, assignee_handle = context.fetch_dri_id(product_group, devops_stage)
gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id, [reviewer_id]) do
<<~MARKDOWN
## What does this MR do?
Promotes the test [`#{example_name}`](https://gitlab.com/#{context.project}/-/blob/#{context.ref}/#{file_path}#L#{changed_line_no + 1})
to the blocking bucket
This test was identified in the reliable e2e test report: #{context.report_issue}
[Testcase link](#{testcase})
[Spec metrics link](#{context.single_spec_metrics_link(example_name)})
/label ~"Quality" ~"QA" ~"type::maintenance"
/label ~"devops::#{devops_stage}"
#{context.label_from_product_group(product_group)}
(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})
MARKDOWN
end
context.post_note_on_merge_request(<<~MARKDOWN, merge_request.iid)
@#{assignee_handle} Please review this MR, approve and assign it to a maintainer.
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}
MARKDOWN
if merge_request
context.add_processed_record({ file_path => changed_line_no })
Runtime::Logger.info(" Created MR for promotion to blocking: #{merge_request.web_url}")
end
merge_request
end
# Performs post processing. Takes a list of MRs and posts them in a note on report_issue
#
# @param [Gitlab::ObjectifiedHash] merge_requests
def post_process(merge_requests)
web_urls = merge_requests.compact.map { |mr| "- #{mr.web_url}\n" }.join
return if web_urls.empty?
context.post_note_on_report_issue(<<~ISSUE_NOTE)
The following merge requests have been created to promote stable specs to blocking:
#{web_urls}
ISSUE_NOTE
end
private
attr_reader :context, :file_path, :file_contents, :example_name, :mr_title, :changed_line_no
# Checks if there is already an MR open
#
# @return [Boolean]
def proceed_with_merge_request? # rubocop:disable Metrics/AbcSize
if changed_line_no.negative?
Runtime::Logger.info(" No lines were changed in #{file_path}. Will not proceed with creating MR.")
return false
elsif context.record_processed?(file_path, changed_line_no)
Runtime::Logger.info(" Record already processed for #{file_path}:#{changed_line_no}. Will not proceed with creating MR.")
return false
elsif existing_mrs&.any?
Runtime::Logger.info(" An open MR already exists for '#{example_name}': #{existing_mrs.first['web_url']}. Will not proceed with creating MR.")
return false
end
true
end
# Add blocking metadata to the file content and replace it
#
# @return [Array] first value holds the new content, the second value holds the line number of the test
def add_blocking_metadata # rubocop:disable Metrics/AbcSize
matched_lines = context.find_example_match_lines(file_contents, example_name)
if matched_lines.any? { |line| line[0].include?(':blocking') }
puts "Example '#{example_name}' is already blocking"
return [file_contents, -1]
end
context.update_matched_line(matched_lines.last, file_contents.dup) do |line|
if line.sub(DESCRIPTION_REGEX, '').include?(',')
line[line.index(',', end_of_description_index(line))] = format(BLOCKING_METADATA, suffix: ',')
else
line[line.rindex(' ')] = format(BLOCKING_METADATA, suffix: ' ')
end
line
end
end
end
end
end
end
end
end