gitlab_quality-test_tooling 0.1.0 → 0.2.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 +4 -4
- data/.rubocop.yml +9 -4
- data/Gemfile.lock +1 -1
- data/Guardfile +0 -22
- data/exe/generate-test-session +49 -0
- data/exe/post-to-slack +57 -0
- data/exe/prepare-stage-reports +38 -0
- data/exe/relate-failure-issue +59 -0
- data/exe/report-results +56 -0
- data/exe/update-screenshot-paths +38 -0
- data/lib/gitlab_quality/test_tooling/gitlab_issue_client.rb +194 -0
- data/lib/gitlab_quality/test_tooling/gitlab_issue_dry_client.rb +26 -0
- data/lib/gitlab_quality/test_tooling/report/concerns/find_set_dri.rb +51 -0
- data/lib/gitlab_quality/test_tooling/report/concerns/results_reporter.rb +75 -0
- data/lib/gitlab_quality/test_tooling/report/concerns/utils.rb +49 -0
- data/lib/gitlab_quality/test_tooling/report/generate_test_session.rb +275 -0
- data/lib/gitlab_quality/test_tooling/report/prepare_stage_reports.rb +79 -0
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +377 -0
- data/lib/gitlab_quality/test_tooling/report/report_as_issue.rb +134 -0
- data/lib/gitlab_quality/test_tooling/report/report_results.rb +83 -0
- data/lib/gitlab_quality/test_tooling/report/results_in_issues.rb +130 -0
- data/lib/gitlab_quality/test_tooling/report/results_in_testcases.rb +113 -0
- data/lib/gitlab_quality/test_tooling/report/update_screenshot_path.rb +81 -0
- data/lib/gitlab_quality/test_tooling/runtime/env.rb +113 -0
- data/lib/gitlab_quality/test_tooling/runtime/logger.rb +92 -0
- data/lib/gitlab_quality/test_tooling/runtime/token_finder.rb +44 -0
- data/lib/gitlab_quality/test_tooling/slack/post_to_slack.rb +36 -0
- data/lib/gitlab_quality/test_tooling/summary_table.rb +41 -0
- data/lib/gitlab_quality/test_tooling/support/http_request.rb +34 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/json_log_finder.rb +65 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/api_log_finder.rb +21 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/application_log_finder.rb +21 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/exception_log_finder.rb +21 -0
- data/lib/gitlab_quality/test_tooling/system_logs/finders/rails/graphql_log_finder.rb +21 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/log.rb +38 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/rails/api_log.rb +34 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/rails/application_log.rb +27 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/rails/exception_log.rb +23 -0
- data/lib/gitlab_quality/test_tooling/system_logs/log_types/rails/graphql_log.rb +30 -0
- data/lib/gitlab_quality/test_tooling/system_logs/shared_fields.rb +29 -0
- data/lib/gitlab_quality/test_tooling/system_logs/system_logs_formatter.rb +65 -0
- data/lib/gitlab_quality/test_tooling/test_results/base_test_results.rb +39 -0
- data/lib/gitlab_quality/test_tooling/test_results/builder.rb +35 -0
- data/lib/gitlab_quality/test_tooling/test_results/j_unit_test_results.rb +27 -0
- data/lib/gitlab_quality/test_tooling/test_results/json_test_results.rb +29 -0
- data/lib/gitlab_quality/test_tooling/test_results/test_result.rb +184 -0
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- data/lib/gitlab_quality/test_tooling.rb +11 -2
- metadata +51 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1826436013184c9312e5ba0bf45ed49eec2380743d81fa1afb31342559a799a2
|
4
|
+
data.tar.gz: d7fa98724fcf619d807ff4c61756ff119af3ad04c6b9f21306aaab59b872bcbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c3bb098432bd3992ad6654c6540f75521e74648dd68c48be4b14962624ad835c199872cc1edf2d7a5173ec358ea3a629d612533853c2ad57a106c830b4c9ff6
|
7
|
+
data.tar.gz: 1d5adf1e7cc98306bfc3d1477aee4310ac6e267da9ed73a26203d9cdba45dd0a8b927c4ecd49f37543a8771c661eaeaa035105393957e288a62cd82165955be6
|
data/.rubocop.yml
CHANGED
@@ -19,6 +19,12 @@ AllCops:
|
|
19
19
|
MaxFilesInCache: 1_000_000
|
20
20
|
NewCops: enable
|
21
21
|
|
22
|
+
CodeReuse/ActiveRecord:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Gemspec/RequiredRubyVersion:
|
26
|
+
Enabled: false
|
27
|
+
|
22
28
|
Gemspec/RequireMFA:
|
23
29
|
Enabled: false
|
24
30
|
|
@@ -49,12 +55,11 @@ Style/ModuleFunction:
|
|
49
55
|
Style/SignalException:
|
50
56
|
Enabled: false
|
51
57
|
|
58
|
+
Layout/LineLength:
|
59
|
+
Max: 180
|
60
|
+
|
52
61
|
Layout/SpaceBeforeFirstArg:
|
53
62
|
Enabled: false
|
54
63
|
|
55
64
|
RSpec/MultipleMemoizedHelpers:
|
56
65
|
Enabled: false
|
57
|
-
|
58
|
-
CodeReuse/ActiveRecord:
|
59
|
-
Exclude:
|
60
|
-
- 'spec/**/*_spec.rb'
|
data/Gemfile.lock
CHANGED
data/Guardfile
CHANGED
@@ -42,28 +42,6 @@ guard :rspec, cmd: "bundle exec rspec" do
|
|
42
42
|
ruby = dsl.ruby
|
43
43
|
dsl.watch_spec_files_for(ruby.lib_files)
|
44
44
|
|
45
|
-
# Rails files
|
46
|
-
rails = dsl.rails(view_extensions: %w[erb haml slim])
|
47
|
-
dsl.watch_spec_files_for(rails.app_files)
|
48
|
-
dsl.watch_spec_files_for(rails.views)
|
49
|
-
|
50
|
-
watch(rails.controllers) do |m|
|
51
|
-
[
|
52
|
-
rspec.spec.call("routing/#{m[1]}_routing"),
|
53
|
-
rspec.spec.call("controllers/#{m[1]}_controller"),
|
54
|
-
rspec.spec.call("acceptance/#{m[1]}")
|
55
|
-
]
|
56
|
-
end
|
57
|
-
|
58
|
-
# Rails config changes
|
59
|
-
watch(rails.spec_helper) { rspec.spec_dir }
|
60
|
-
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
61
|
-
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
62
|
-
|
63
|
-
# Capybara features specs
|
64
|
-
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
|
65
|
-
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
|
66
|
-
|
67
45
|
# Turnip features and steps
|
68
46
|
watch(%r{^spec/acceptance/(.+)\.feature$})
|
69
47
|
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
require_relative "../lib/gitlab_quality/test_tooling"
|
8
|
+
|
9
|
+
params = {}
|
10
|
+
|
11
|
+
options = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
13
|
+
|
14
|
+
opts.on('-i', '--input-files FILES', String, 'Generate test session report') do |input_files|
|
15
|
+
params[:input_files] = input_files
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on('-p', '--project PROJECT', String, 'Can be an integer or a group/project string') do |project|
|
19
|
+
params[:project] = project
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-t', '--token ACCESS_TOKEN', String, 'A valid access token with Reporter permission in PROJECT') do |token|
|
23
|
+
params[:token] = GitlabQuality::TestTooling::Runtime::TokenFinder.find_token!(token)
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on('--dry-run', "Perform a dry-run (don't create or update issues or test cases)") do
|
27
|
+
params[:dry_run] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on_tail('-v', '--version', 'Show the version') do
|
31
|
+
require_relative "../lib/gitlab_quality/test_tooling/version"
|
32
|
+
puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on_tail('-h', '--help', 'Show the usage') do
|
37
|
+
puts opts
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.parse(ARGV)
|
42
|
+
end
|
43
|
+
|
44
|
+
if params.any?
|
45
|
+
GitlabQuality::TestTooling::Report::GenerateTestSession.new(**params).invoke!
|
46
|
+
else
|
47
|
+
puts options
|
48
|
+
exit 1
|
49
|
+
end
|
data/exe/post-to-slack
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
require_relative "../lib/gitlab_quality/test_tooling"
|
8
|
+
|
9
|
+
params = {}
|
10
|
+
|
11
|
+
options = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
13
|
+
|
14
|
+
opts.on('-w', '--slack-webhook-url SLACK_WEBHOOK_URL', String, 'Slack webhook URL') do |slack_webhook_url|
|
15
|
+
params[:slack_webhook_url] = slack_webhook_url
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on('-c', '--channel CHANNEL', String, 'Slack channel to post the message to') do |channel|
|
19
|
+
params[:channel] = channel
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-m', '--message MESSAGE', String, 'Post message to Slack') do |message|
|
23
|
+
params[:message] = message
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on('-t', '--include-summary-table FILES', String, 'Create a results summary table to post to Slack') do |files|
|
27
|
+
params[:message] += "\n\n#{GitlabQuality::TestTooling::SummaryTable.create(input_files: files)}"
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on('-u', '--username USERNAME', String, 'Username to use for the Slack message') do |username|
|
31
|
+
params[:username] = username
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on('-i', '--icon-emoji ICON_EMOJI', String, 'Icon emoji to use for the Slack message') do |icon_emoji|
|
35
|
+
params[:icon_emoji] = icon_emoji
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on_tail('-v', '--version', 'Show the version') do
|
39
|
+
require_relative "../lib/gitlab_quality/test_tooling/version"
|
40
|
+
puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on_tail('-h', '--help', 'Show the usage') do
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.parse(ARGV)
|
50
|
+
end
|
51
|
+
|
52
|
+
if params.any?
|
53
|
+
GitlabQuality::TestTooling::Slack::PostToSlack.new(**params).invoke!
|
54
|
+
else
|
55
|
+
puts options
|
56
|
+
exit 1
|
57
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
require_relative "../lib/gitlab_quality/test_tooling"
|
8
|
+
|
9
|
+
params = {}
|
10
|
+
|
11
|
+
options = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
13
|
+
|
14
|
+
opts.on('-j', '--junit-files FILES', String,
|
15
|
+
'Prepare separate reports for each Stage from the provided JUnit XML files') do |junit_files|
|
16
|
+
params[:junit_files] = junit_files
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on_tail('-v', '--version', 'Show the version') do
|
20
|
+
require_relative "../lib/gitlab_quality/test_tooling/version"
|
21
|
+
puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on_tail('-h', '--help', 'Show the usage') do
|
26
|
+
puts opts
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.parse(ARGV)
|
31
|
+
end
|
32
|
+
|
33
|
+
if params.any?
|
34
|
+
GitlabQuality::TestTooling::Report::PrepareStageReports.new(**params).invoke!
|
35
|
+
else
|
36
|
+
puts options
|
37
|
+
exit 1
|
38
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
require_relative "../lib/gitlab_quality/test_tooling"
|
8
|
+
|
9
|
+
params = {}
|
10
|
+
|
11
|
+
options = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
13
|
+
|
14
|
+
opts.on('-i', '--input-files FILES', String,
|
15
|
+
'Relate test failures to failure issues from RSpec JSON files') do |input_files|
|
16
|
+
params[:input_files] = input_files
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('--max-diff-ratio DIFF_RATO', Float, 'Max stacktrace diff ratio for QA failure issues detection') do |value|
|
20
|
+
params[:max_diff_ratio] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('-p', '--project PROJECT', String, 'Can be an integer or a group/project string') do |project|
|
24
|
+
params[:project] = project
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-t', '--token ACCESS_TOKEN', String, 'A valid access token with Reporter permission in PROJECT') do |token|
|
28
|
+
params[:token] = GitlabQuality::TestTooling::Runtime::TokenFinder.find_token!(token)
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on('--system-log-files SYSTEM_LOG_FILES', String,
|
32
|
+
'Include errors from system logs in failure issues') do |system_log_files|
|
33
|
+
params[:system_logs] = system_log_files
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on('--dry-run', "Perform a dry-run (don't create or update issues or test cases)") do
|
37
|
+
params[:dry_run] = true
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on_tail('-v', '--version', 'Show the version') do
|
41
|
+
require_relative "../lib/gitlab_quality/test_tooling/version"
|
42
|
+
puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on_tail('-h', '--help', 'Show the usage') do
|
47
|
+
puts opts
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.parse(ARGV)
|
52
|
+
end
|
53
|
+
|
54
|
+
if params.any?
|
55
|
+
GitlabQuality::TestTooling::Report::RelateFailureIssue.new(**params).invoke!
|
56
|
+
else
|
57
|
+
puts options
|
58
|
+
exit 1
|
59
|
+
end
|
data/exe/report-results
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
require_relative "../lib/gitlab_quality/test_tooling"
|
8
|
+
|
9
|
+
params = {}
|
10
|
+
|
11
|
+
options = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
13
|
+
|
14
|
+
opts.on('-i', '--input-files FILES', String,
|
15
|
+
'Report test results from JUnit or JSON files in GitLab test cases and results issues') do |input_files|
|
16
|
+
params[:input_files] = input_files
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('--test-case-project TEST_CASE_PROJECT', String,
|
20
|
+
'Can be an integer or a group/project string') do |test_case_project|
|
21
|
+
params[:test_case_project] = test_case_project
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on('--results-issue-project RESULTS_ISSUE_PROJECT', String,
|
25
|
+
'Can be an integer or a group/project string') do |results_issue_project|
|
26
|
+
params[:results_issue_project] = results_issue_project
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('-t', '--token ACCESS_TOKEN', String, 'A valid access token') do |token|
|
30
|
+
params[:token] = GitlabQuality::TestTooling::Runtime::TokenFinder.find_token!(token)
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('--dry-run', "Perform a dry-run (don't create or update issues or test cases)") do
|
34
|
+
params[:dry_run] = true
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on_tail('-v', '--version', 'Show the version') do
|
38
|
+
require_relative "../lib/gitlab_quality/test_tooling/version"
|
39
|
+
puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on_tail('-h', '--help', 'Show the usage') do
|
44
|
+
puts opts
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.parse(ARGV)
|
49
|
+
end
|
50
|
+
|
51
|
+
if params.any?
|
52
|
+
GitlabQuality::TestTooling::Report::ReportResults.new(**params).invoke!
|
53
|
+
else
|
54
|
+
puts options
|
55
|
+
exit 1
|
56
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
require_relative "../lib/gitlab_quality/test_tooling"
|
8
|
+
|
9
|
+
params = {}
|
10
|
+
|
11
|
+
options = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
13
|
+
|
14
|
+
opts.on('--junit-files FILES', String,
|
15
|
+
"Update the path to screenshots to container's host from the provided JUnit XML files") do |_files|
|
16
|
+
params[:junit_files] = junit_files
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on_tail('-v', '--version', 'Show the version') do
|
20
|
+
require_relative "../lib/gitlab_quality/test_tooling/version"
|
21
|
+
puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on_tail('-h', '--help', 'Show the usage') do
|
26
|
+
puts opts
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.parse(ARGV)
|
31
|
+
end
|
32
|
+
|
33
|
+
if params.any?
|
34
|
+
GitlabQuality::TestTooling::Report::UpdateScreenshotPath.new(**params).invoke!
|
35
|
+
else
|
36
|
+
puts options
|
37
|
+
exit 1
|
38
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'gitlab'
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
# Monkey patch the Gitlab client to use the correct API path and add required methods
|
7
|
+
class Client
|
8
|
+
def team_member(project, id)
|
9
|
+
get("/projects/#{url_encode(project)}/members/all/#{id}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def issue_discussions(project, issue_id, options = {})
|
13
|
+
get("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions", query: options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_note_to_issue_discussion_as_thread(project, issue_id, discussion_id, options = {})
|
17
|
+
post("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions/#{discussion_id}/notes", query: options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module GitlabQuality
|
23
|
+
module TestTooling
|
24
|
+
# The GitLab client is used for API access: https://github.com/NARKOZ/gitlab
|
25
|
+
class GitlabIssueClient
|
26
|
+
MAINTAINER_ACCESS_LEVEL = 40
|
27
|
+
RETRY_BACK_OFF_DELAY = 60
|
28
|
+
MAX_RETRY_ATTEMPTS = 3
|
29
|
+
|
30
|
+
def initialize(token:, project:)
|
31
|
+
@token = token
|
32
|
+
@project = project
|
33
|
+
@retry_backoff = 0
|
34
|
+
|
35
|
+
configure_gitlab_client
|
36
|
+
end
|
37
|
+
|
38
|
+
def assert_user_permission!
|
39
|
+
handle_gitlab_client_exceptions do
|
40
|
+
user = Gitlab.user
|
41
|
+
member = Gitlab.team_member(project, user.id)
|
42
|
+
|
43
|
+
abort_not_permitted if member.access_level < MAINTAINER_ACCESS_LEVEL
|
44
|
+
end
|
45
|
+
rescue Gitlab::Error::NotFound
|
46
|
+
abort_not_permitted
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_issues(iid: nil, options: {}, &select)
|
50
|
+
select ||= :itself
|
51
|
+
|
52
|
+
handle_gitlab_client_exceptions do
|
53
|
+
break [Gitlab.issue(project, iid)].select(&select) if iid
|
54
|
+
|
55
|
+
Gitlab.issues(project, options)
|
56
|
+
.auto_paginate
|
57
|
+
.select(&select)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_issue_discussions(iid:)
|
62
|
+
handle_gitlab_client_exceptions do
|
63
|
+
Gitlab.issue_discussions(project, iid, order_by: 'created_at', sort: 'asc').auto_paginate
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_issue(title:, description:, labels:, issue_type: 'issue')
|
68
|
+
attrs = { issue_type: issue_type, description: description, labels: labels }
|
69
|
+
|
70
|
+
handle_gitlab_client_exceptions do
|
71
|
+
Gitlab.create_issue(project, title, attrs)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def edit_issue(iid:, options: {})
|
76
|
+
handle_gitlab_client_exceptions do
|
77
|
+
Gitlab.edit_issue(project, iid, options)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def find_issue_notes(iid:)
|
82
|
+
handle_gitlab_client_exceptions do
|
83
|
+
Gitlab.issue_notes(project, iid, order_by: 'created_at', sort: 'asc')&.auto_paginate
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_issue_note(iid:, note:)
|
88
|
+
handle_gitlab_client_exceptions do
|
89
|
+
Gitlab.create_issue_note(project, iid, note)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def edit_issue_note(issue_iid:, note_id:, note:)
|
94
|
+
handle_gitlab_client_exceptions do
|
95
|
+
Gitlab.edit_issue_note(project, issue_iid, note_id, note)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_note_to_issue_discussion_as_thread(iid:, discussion_id:, body:)
|
100
|
+
handle_gitlab_client_exceptions do
|
101
|
+
Gitlab.add_note_to_issue_discussion_as_thread(project, iid, discussion_id, body: body)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def find_user_id(username:)
|
106
|
+
handle_gitlab_client_exceptions do
|
107
|
+
user = Gitlab.users(username: username)&.first
|
108
|
+
user['id'] unless user.nil?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def upload_file(file_fullpath:)
|
113
|
+
ignore_gitlab_client_exceptions do
|
114
|
+
Gitlab.upload_file(project, file_fullpath)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def ignore_gitlab_client_exceptions
|
119
|
+
yield
|
120
|
+
rescue StandardError, SystemCallError, OpenSSL::SSL::SSLError, Net::OpenTimeout, Net::ReadTimeout,
|
121
|
+
Gitlab::Error::Error => e
|
122
|
+
puts "Ignoring the following error: #{e}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def handle_gitlab_client_exceptions # rubocop:disable Metrics/AbcSize
|
126
|
+
yield
|
127
|
+
rescue Gitlab::Error::NotFound
|
128
|
+
# This error could be raised in assert_user_permission!
|
129
|
+
# If so, we want it to terminate at that point
|
130
|
+
raise
|
131
|
+
rescue SystemCallError, OpenSSL::SSL::SSLError, Net::OpenTimeout, Net::ReadTimeout,
|
132
|
+
Gitlab::Error::InternalServerError, Gitlab::Error::Parsing => e
|
133
|
+
@retry_backoff += RETRY_BACK_OFF_DELAY
|
134
|
+
|
135
|
+
raise if @retry_backoff > RETRY_BACK_OFF_DELAY * MAX_RETRY_ATTEMPTS
|
136
|
+
|
137
|
+
warn_exception(e)
|
138
|
+
warn("Sleeping for #{@retry_backoff} seconds before retrying...")
|
139
|
+
sleep @retry_backoff
|
140
|
+
|
141
|
+
retry
|
142
|
+
rescue StandardError => e
|
143
|
+
pipeline = Runtime::Env.pipeline_from_project_name
|
144
|
+
channel = case pipeline
|
145
|
+
when "canary"
|
146
|
+
"qa-production"
|
147
|
+
when "staging-canary"
|
148
|
+
"qa-staging"
|
149
|
+
else
|
150
|
+
"qa-#{pipeline}"
|
151
|
+
end
|
152
|
+
error_msg = warn_exception(e)
|
153
|
+
|
154
|
+
return unless Runtime::Env.ci_commit_ref_name == Runtime::Env.default_branch
|
155
|
+
|
156
|
+
slack_options = {
|
157
|
+
slack_webhook_url: ENV.fetch('CI_SLACK_WEBHOOK_URL', nil),
|
158
|
+
channel: channel,
|
159
|
+
username: "GitLab QA Bot",
|
160
|
+
icon_emoji: ':ci_failing:',
|
161
|
+
message: <<~MSG
|
162
|
+
An unexpected error occurred while reporting test results in issues.
|
163
|
+
The error occurred in job: #{Runtime::Env.ci_job_url}
|
164
|
+
`#{error_msg}`
|
165
|
+
MSG
|
166
|
+
}
|
167
|
+
puts "Posting Slack message to channel: #{channel}"
|
168
|
+
|
169
|
+
GitlabQuality::TestTooling::Slack::PostToSlack.new(**slack_options).invoke!
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
attr_reader :token, :project
|
175
|
+
|
176
|
+
def configure_gitlab_client
|
177
|
+
Gitlab.configure do |config|
|
178
|
+
config.endpoint = Runtime::Env.gitlab_api_base
|
179
|
+
config.private_token = token
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def abort_not_permitted
|
184
|
+
abort "You must have at least Reporter access to the project to use this feature."
|
185
|
+
end
|
186
|
+
|
187
|
+
def warn_exception(error)
|
188
|
+
error_msg = "#{error.class.name} #{error.message}"
|
189
|
+
warn(error_msg)
|
190
|
+
error_msg
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
class GitlabIssueDryClient < GitlabIssueClient
|
6
|
+
def create_issue(title:, description:, labels:, issue_type: 'issue')
|
7
|
+
attrs = { description: description, labels: labels }
|
8
|
+
|
9
|
+
puts "The following #{issue_type} would have been created:"
|
10
|
+
puts "project: #{project}, title: #{title}, attrs: #{attrs}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def edit_issue(iid:, options: {})
|
14
|
+
puts "The #{project}##{iid} issue would have been updated with: #{options}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_issue_note(iid:, note:)
|
18
|
+
puts "The following note would have been posted on #{project}##{iid} issue: #{note}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_note_to_issue_discussion_as_thread(iid:, discussion_id:, body:)
|
22
|
+
puts "The following discussion note would have been posted on #{project}##{iid} (discussion #{discussion_id}) issue: #{body}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module GitlabQuality
|
6
|
+
module TestTooling
|
7
|
+
module Report
|
8
|
+
module Concerns
|
9
|
+
module FindSetDri
|
10
|
+
def set_dri_via_group(product_group, test)
|
11
|
+
parse_json_with_sets
|
12
|
+
fetch_stage_sets(test)
|
13
|
+
|
14
|
+
return @sets.sample['username'] if @stage_sets.empty?
|
15
|
+
|
16
|
+
fetch_group_sets(product_group)
|
17
|
+
|
18
|
+
if @group_sets.empty?
|
19
|
+
@stage_sets.sample['username']
|
20
|
+
else
|
21
|
+
@group_sets.sample['username']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse_json_with_sets
|
28
|
+
response = Support::HttpRequest.make_http_request(
|
29
|
+
url: 'https://gitlab-org.gitlab.io/gitlab-roulette/roulette.json'
|
30
|
+
)
|
31
|
+
@sets = JSON.parse(response.body).select { |user| user['role'].include?('software-engineer-in-test') }
|
32
|
+
end
|
33
|
+
|
34
|
+
def fetch_stage_sets(test)
|
35
|
+
@stage_sets = @sets.select do |user|
|
36
|
+
user['role'].include?(test.stage.split("_").map(&:capitalize).join(" "))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch_group_sets(product_group)
|
41
|
+
@group_sets = @stage_sets.select do |user|
|
42
|
+
user['role'].include?(product_group.split("_").map do |word|
|
43
|
+
word == 'and' ? word : word.capitalize
|
44
|
+
end.join(" "))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|