class Gitlab::QA::Report::ResultsInIssues

Uses the API to create or update GitLab issues with the results of tests from RSpec report files.

def error_and_stack_trace(text)

def error_and_stack_trace(text)
  text.strip[/Error:(.*)/m, 1].to_s
end

def failure_summary

def failure_summary
  summary = [":x: ~\"#{pipeline}::failed\""]
  summary << "~\"quarantine\"" if quarantine_job?
  summary << "in job `#{Runtime::Env.ci_job_name}` in #{Runtime::Env.ci_job_url}"
  summary.join(' ')
end

def find_issue(test)

def find_issue(test)
  title = title_from_test(test)
  issues =
    gitlab.find_issues(
      iid: iid_from_testcase_url(test.testcase),
      options: { search: search_term(test) }) do |issue|
        issue.state == 'opened' && issue.title.strip == title
      end
  warn(%(Too many issues found with the file path "#{test.file}" and name "#{test.name}")) if issues.many?
  issues.first
end

def iid_from_testcase_url(url)

def iid_from_testcase_url(url)
  url && url.split('/').last.to_i
end

def new_issue_labels(test)

def new_issue_labels(test)
  %w[status::automated]
end

def new_note_matches_discussion?(note, discussion)

def new_note_matches_discussion?(note, discussion)
  note_error = error_and_stack_trace(note)
  discussion_error = error_and_stack_trace(discussion.notes.first['body'])
  return false if note_error.empty? || discussion_error.empty?
  note_error == discussion_error
end

def note_content(test)

def note_content(test)
  errors = test.failures.each_with_object([]) do |failure, text|
    text << <<~TEXT
      Error:
      ```
      #{failure['message']}
      ```
      Stacktrace:
      ```
      #{failure['stacktrace']}
      ```
    TEXT
  end.join("\n\n")
  "#{failure_summary}\n\n#{errors}"
end

def note_status(issue, test)

def note_status(issue, test)
  return false if test.skipped
  return false if test.failures.empty?
  note = note_content(test)
  gitlab.find_issue_discussions(iid: issue.iid).each do |discussion|
    return gitlab.add_note_to_issue_discussion_as_thread(iid: issue.iid, discussion_id: discussion.id, body: failure_summary) if new_note_matches_discussion?(note, discussion)
  end
  gitlab.create_issue_note(iid: issue.iid, note: note)
  true
end

def report_test(test)

def report_test(test)
  puts "Reporting test: #{test.file} | #{test.name}"
  issue = find_issue(test)
  if issue
    puts "Found existing issue: #{issue.web_url}"
  else
    # Don't create new issues for skipped tests
    return if test.skipped
    issue = create_issue(test)
    puts "Created new issue: #{issue.web_url}"
  end
  test.testcase ||= issue.web_url
  labels_updated = update_labels(issue, test)
  note_posted = note_status(issue, test)
  if labels_updated || note_posted
    puts "Issue updated."
  else
    puts "Test passed, no update needed."
  end
end

def run!

def run!
  puts "Reporting test results in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
  test_results_per_file do |test_results|
    puts "Reporting tests in #{test_results.path}"
    test_results.each do |test|
      report_test(test)
    end
    test_results.write
  end
end

def search_term(test)

def search_term(test)
  %("#{partial_file_path(test.file)}" "#{search_safe(test.name)}")
end

def up_to_date_labels(test:, issue: nil)

def up_to_date_labels(test:, issue: nil)
  labels = super
  labels.delete_if { |label| label.start_with?("#{pipeline}::") }
  labels << (test.failures.empty? ? "#{pipeline}::passed" : "#{pipeline}::failed")
end