gitlab-qa 10.3.0 → 12.2.1

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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.gitlab/changelog_config.yml +13 -0
  4. data/.gitlab/merge_request_templates/Release.md +13 -34
  5. data/.gitlab-ci.yml +3 -1
  6. data/.rubocop.yml +13 -2
  7. data/.rubocop_todo.yml +57 -97
  8. data/Dangerfile +1 -5
  9. data/Gemfile.lock +47 -39
  10. data/README.md +1 -2
  11. data/docs/release_process.md +1 -1
  12. data/docs/running_against_remote_grid.md +42 -3
  13. data/docs/what_tests_can_be_run.md +5 -2
  14. data/gitlab-qa.gemspec +5 -3
  15. data/lib/gitlab/qa/component/base.rb +9 -9
  16. data/lib/gitlab/qa/component/gitaly.rb +7 -5
  17. data/lib/gitlab/qa/component/gitaly_cluster.rb +15 -9
  18. data/lib/gitlab/qa/component/gitlab.rb +48 -21
  19. data/lib/gitlab/qa/component/mail_hog.rb +1 -0
  20. data/lib/gitlab/qa/component/praefect.rb +41 -31
  21. data/lib/gitlab/qa/component/selenoid.rb +14 -7
  22. data/lib/gitlab/qa/component/specs.rb +11 -5
  23. data/lib/gitlab/qa/component/staging.rb +4 -4
  24. data/lib/gitlab/qa/component/telegraf.rb +2 -1
  25. data/lib/gitlab/qa/docker/engine.rb +6 -3
  26. data/lib/gitlab/qa/docker/volumes.rb +1 -1
  27. data/lib/gitlab/qa/release.rb +4 -4
  28. data/lib/gitlab/qa/runner.rb +10 -3
  29. data/lib/gitlab/qa/runtime/env.rb +47 -62
  30. data/lib/gitlab/qa/runtime/logger.rb +1 -1
  31. data/lib/gitlab/qa/runtime/omnibus_configuration.rb +1 -0
  32. data/lib/gitlab/qa/runtime/omnibus_configurations/decomposition_single_db.rb +1 -2
  33. data/lib/gitlab/qa/runtime/omnibus_configurations/object_storage_gcs.rb +2 -1
  34. data/lib/gitlab/qa/runtime/scenario.rb +1 -5
  35. data/lib/gitlab/qa/scenario/actable.rb +4 -4
  36. data/lib/gitlab/qa/scenario/test/instance/airgapped.rb +2 -4
  37. data/lib/gitlab/qa/scenario/test/instance/deployment_base.rb +2 -1
  38. data/lib/gitlab/qa/scenario/test/integration/gitaly_cluster.rb +0 -2
  39. data/lib/gitlab/qa/scenario/test/integration/group_saml.rb +1 -1
  40. data/lib/gitlab/qa/scenario/test/integration/ldap.rb +5 -6
  41. data/lib/gitlab/qa/scenario/test/integration/mtls.rb +20 -6
  42. data/lib/gitlab/qa/scenario/test/integration/oauth.rb +13 -4
  43. data/lib/gitlab/qa/scenario/test/integration/praefect.rb +16 -10
  44. data/lib/gitlab/qa/scenario/test/integration/registry_with_cdn.rb +5 -2
  45. data/lib/gitlab/qa/scenario/test/omnibus/update_from_previous.rb +1 -1
  46. data/lib/gitlab/qa/scenario/test/omnibus/upgrade.rb +1 -3
  47. data/lib/gitlab/qa/scenario/test/sanity/version.rb +1 -1
  48. data/lib/gitlab/qa/support/config_scripts.rb +1 -1
  49. data/lib/gitlab/qa/support/gitlab_version_info.rb +30 -10
  50. data/lib/gitlab/qa/support/shell_command.rb +1 -0
  51. data/lib/gitlab/qa/test_logger.rb +2 -2
  52. data/lib/gitlab/qa/version.rb +1 -1
  53. data/lib/gitlab/qa.rb +0 -1
  54. data/support/data/admin_access_token_seed.rb +5 -1
  55. data/support/data/license_usage_seed.rb +3 -1
  56. metadata +16 -45
  57. data/bin/slack +0 -14
  58. data/exe/gitlab-qa-report +0 -10
  59. data/lib/gitlab/qa/report/base_test_results.rb +0 -39
  60. data/lib/gitlab/qa/report/find_set_dri.rb +0 -43
  61. data/lib/gitlab/qa/report/generate_test_session.rb +0 -275
  62. data/lib/gitlab/qa/report/gitlab_issue_client.rb +0 -190
  63. data/lib/gitlab/qa/report/gitlab_issue_dry_client.rb +0 -28
  64. data/lib/gitlab/qa/report/j_unit_test_results.rb +0 -27
  65. data/lib/gitlab/qa/report/json_test_results.rb +0 -29
  66. data/lib/gitlab/qa/report/prepare_stage_reports.rb +0 -86
  67. data/lib/gitlab/qa/report/relate_failure_issue.rb +0 -374
  68. data/lib/gitlab/qa/report/report_as_issue.rb +0 -176
  69. data/lib/gitlab/qa/report/report_results.rb +0 -64
  70. data/lib/gitlab/qa/report/results_in_issues.rb +0 -126
  71. data/lib/gitlab/qa/report/results_in_testcases.rb +0 -111
  72. data/lib/gitlab/qa/report/results_reporter_shared.rb +0 -70
  73. data/lib/gitlab/qa/report/summary_table.rb +0 -43
  74. data/lib/gitlab/qa/report/test_result.rb +0 -184
  75. data/lib/gitlab/qa/report/update_screenshot_path.rb +0 -63
  76. data/lib/gitlab/qa/reporter.rb +0 -131
  77. data/lib/gitlab/qa/runtime/omnibus_configurations/packages.rb +0 -17
  78. data/lib/gitlab/qa/runtime/token_finder.rb +0 -44
  79. data/lib/gitlab/qa/slack/post_to_slack.rb +0 -30
  80. data/lib/gitlab/qa/system_logs/finders/json_log_finder.rb +0 -65
  81. data/lib/gitlab/qa/system_logs/finders/rails/api_log_finder.rb +0 -21
  82. data/lib/gitlab/qa/system_logs/finders/rails/application_log_finder.rb +0 -21
  83. data/lib/gitlab/qa/system_logs/finders/rails/exception_log_finder.rb +0 -21
  84. data/lib/gitlab/qa/system_logs/finders/rails/graphql_log_finder.rb +0 -21
  85. data/lib/gitlab/qa/system_logs/log_types/log.rb +0 -38
  86. data/lib/gitlab/qa/system_logs/log_types/rails/api_log.rb +0 -34
  87. data/lib/gitlab/qa/system_logs/log_types/rails/application_log.rb +0 -27
  88. data/lib/gitlab/qa/system_logs/log_types/rails/exception_log.rb +0 -23
  89. data/lib/gitlab/qa/system_logs/log_types/rails/graphql_log.rb +0 -30
  90. data/lib/gitlab/qa/system_logs/shared_fields.rb +0 -29
  91. data/lib/gitlab/qa/system_logs/system_logs_formatter.rb +0 -65
@@ -1,131 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'optparse'
4
-
5
- module Gitlab
6
- module QA
7
- class Reporter
8
- # rubocop:disable Metrics/AbcSize
9
- # rubocop:disable Metrics/PerceivedComplexity
10
- # rubocop:disable Metrics/CyclomaticComplexity
11
- def self.invoke(args)
12
- report_options = {}
13
- slack_options = {}
14
-
15
- options = OptionParser.new do |opts|
16
- opts.banner = 'Usage: gitlab-qa-reporter [options]'
17
-
18
- opts.on('--prepare-stage-reports FILES', 'Prepare separate reports for each Stage from the provided JUnit XML files') do |files|
19
- report_options[:prepare_stage_reports] = true
20
- report_options[:input_files] = files if files
21
- end
22
-
23
- opts.on('--report-results FILES', String, 'Report test results from JUnit XML files in GitLab test cases and results issues') do |files|
24
- report_options[:report_results] = true
25
- report_options[:input_files] = files if files
26
- end
27
-
28
- opts.on('--relate-failure-issue FILES', String, 'Relate test failures to failure issues from RSpec JSON files') do |files|
29
- report_options[:relate_failure_issue] = true
30
- report_options[:input_files] = files if files
31
- end
32
-
33
- opts.on('--max-diff-ratio DIFF_RATO', Float, 'Max stacktrace diff ratio for QA failure issues detection. Used by with --relate-failure-issue') do |value|
34
- report_options[:max_diff_ratio] = value
35
- end
36
-
37
- opts.on('-p', '--project PROJECT_ID', String, 'A valid project ID. Can be an integer or a group/project string. Required by --relate-failure-issue') do |value|
38
- report_options[:project] = value
39
- end
40
-
41
- opts.on('--results-issue-project RESULTS_ISSUE_PROJECT_ID', String, 'A valid project ID. Can be an integer or a group/project string. Required by --report-results') do |value|
42
- report_options[:results_issue_project] = value
43
- end
44
-
45
- opts.on('--test-case-project TEST_CASE_PROJECT_ID', String, 'A valid project ID. Can be an integer or a group/project string. Required by --report-results') do |value|
46
- report_options[:test_case_project] = value
47
- end
48
-
49
- opts.on('--generate-test-session FILES', String, 'Generate test session report') do |files|
50
- report_options[:generate_test_session] = true
51
- report_options[:input_files] = files if files
52
- end
53
-
54
- opts.on('-t', '--token ACCESS_TOKEN', String, 'A valid access token. Required by --report-results and --relate-failure-issue') do |value|
55
- report_options[:token] = value
56
- end
57
-
58
- opts.on('--post-to-slack MSG', 'Post message to slack') do |msg|
59
- slack_options[:post_to_slack] = true
60
- slack_options[:message] = msg
61
- end
62
-
63
- opts.on('--include-summary-table FILES', 'Create a results summary table to post to slack. To be used with --post-to-slack.') do |files|
64
- raise 'This option should be used with --post-to-slack.' unless slack_options[:post_to_slack]
65
-
66
- slack_options[:message] = slack_options[:message] + "\n\n" + Gitlab::QA::Report::SummaryTable.create(input_files: files)
67
- end
68
-
69
- opts.on('--include-system-log-errors FILES', String, 'Include errors from system logs in failure issues. To be used with --relate-failure-issue') do |files|
70
- raise 'This option should be used with --relate-failure-issue.' unless report_options[:relate_failure_issue]
71
-
72
- report_options[:system_logs] = files if files
73
- end
74
-
75
- opts.on('--update-screenshot-path FILES', "Update the path to screenshots to container's host") do |files|
76
- report_options[:update_screenshot_path] = true
77
- report_options[:files] = files
78
- end
79
-
80
- opts.on('--dry-run', "Perform a dry-run (don't create or update issues or test cases)") do |files|
81
- report_options[:dry_run] = true
82
- end
83
-
84
- opts.on_tail('-v', '--version', 'Show the version') do
85
- require 'gitlab/qa/version'
86
- puts "#{$PROGRAM_NAME} : #{VERSION}"
87
- exit
88
- end
89
-
90
- opts.on_tail('-h', '--help', 'Show the usage') do
91
- puts opts
92
- exit
93
- end
94
-
95
- opts.parse(args)
96
- end
97
-
98
- if args.any?
99
- if report_options.delete(:prepare_stage_reports)
100
- Gitlab::QA::Report::PrepareStageReports.new(**report_options).invoke!
101
-
102
- elsif report_options.delete(:relate_failure_issue)
103
- report_options[:token] = Runtime::TokenFinder.find_token!(report_options[:token])
104
- Gitlab::QA::Report::RelateFailureIssue.new(**report_options).invoke!
105
-
106
- elsif report_options.delete(:report_results)
107
- report_options[:token] = Runtime::TokenFinder.find_token!(report_options[:token])
108
- Gitlab::QA::Report::ReportResults.new(**report_options).invoke!
109
-
110
- elsif report_options.delete(:generate_test_session)
111
- report_options[:token] = Runtime::TokenFinder.find_token!(report_options[:token])
112
- Gitlab::QA::Report::GenerateTestSession.new(**report_options).invoke!
113
-
114
- elsif slack_options.delete(:post_to_slack)
115
- Gitlab::QA::Slack::PostToSlack.new(**slack_options).invoke!
116
-
117
- elsif report_options.delete(:update_screenshot_path)
118
- Gitlab::QA::Report::UpdateScreenshotPath.new(**report_options).invoke!
119
-
120
- end
121
- else
122
- puts options
123
- exit 1
124
- end
125
- end
126
- # rubocop:enable Metrics/CyclomaticComplexity
127
- # rubocop:enable Metrics/PerceivedComplexity
128
- # rubocop:enable Metrics/AbcSize
129
- end
130
- end
131
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module Runtime
6
- module OmnibusConfigurations
7
- class Packages < Default
8
- def configuration
9
- <<~OMNIBUS
10
- gitlab_rails['packages_enabled'] = true
11
- OMNIBUS
12
- end
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module Runtime
6
- class TokenFinder
7
- def self.find_token!(token, suffix: nil)
8
- new(token, suffix).find_token!
9
- end
10
-
11
- attr_reader :token, :suffix
12
-
13
- def initialize(token, suffix)
14
- @token = token
15
- @suffix = suffix
16
- end
17
-
18
- def find_token!
19
- find_token_from_attrs || find_token_from_env || find_token_from_file
20
- end
21
-
22
- def find_token_from_attrs
23
- token
24
- end
25
-
26
- def find_token_from_env
27
- Env.qa_access_token
28
- end
29
-
30
- def find_token_from_file
31
- @token_from_file ||= File.read(token_file_path).strip
32
- rescue Errno::ENOENT
33
- fail "Please provide a valid access token with the `-t/--token` option, the `GITLAB_QA_ACCESS_TOKEN` environment variable, or in the `#{token_file_path}` file!"
34
- end
35
-
36
- private
37
-
38
- def token_file_path
39
- @token_file_path ||= File.expand_path("../api_token#{"_#{suffix}" if suffix}", __dir__)
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module Slack
6
- class PostToSlack
7
- def initialize(message:, icon_emoji: Runtime::Env.slack_icon_emoji, channel: Runtime::Env.slack_qa_channel)
8
- @channel = channel
9
- @message = message
10
- @icon_emoji = icon_emoji
11
- end
12
-
13
- def invoke!
14
- Runtime::Env.require_slack_qa_channel! unless @channel
15
- Runtime::Env.require_ci_slack_webhook_url!
16
-
17
- params = {}
18
- params['channel'] = @channel
19
- params['username'] = "GitLab QA Bot"
20
- params['icon_emoji'] = @icon_emoji
21
- params['text'] = @message
22
-
23
- url = Runtime::Env.ci_slack_webhook_url
24
-
25
- Support::HttpRequest.make_http_request(method: 'post', url: url, params: params, show_response: true)
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Gitlab
6
- module QA
7
- module SystemLogs
8
- module Finders
9
- class JsonLogFinder
10
- def initialize(base_path, file_path)
11
- @base_path = base_path
12
- @file_path = file_path
13
- end
14
-
15
- def find(correlation_id)
16
- log_file_path = "#{@base_path}/#{@file_path}"
17
- logs = []
18
-
19
- if File.exist?(log_file_path) && !correlation_id.nil?
20
- File.foreach(log_file_path) do |line|
21
- begin
22
- json_line = JSON.parse(line, symbolize_names: true)
23
- rescue JSON::ParserError
24
- Runtime::Logger.debug("JsonLogFinder#find attempted to parse invalid JSON: #{line}")
25
-
26
- next
27
- end
28
-
29
- if (json_line[:correlation_id])&.casecmp?(correlation_id)
30
- normalized_line = normalize_keys(json_line)
31
- logs << new_log(normalized_line)
32
- end
33
- end
34
- end
35
-
36
- logs
37
- end
38
-
39
- def new_log(data)
40
- raise 'abstract method new_log must be defined!'
41
- end
42
-
43
- private
44
-
45
- def normalize_keys(json_line)
46
- normalized_hash = {}
47
-
48
- json_line.each_key do |old_key|
49
- key_string = old_key.to_s
50
-
51
- if key_string.include?('.')
52
- normalized_key = key_string.tr('.', '_').to_sym
53
- normalized_hash[normalized_key] = json_line[old_key]
54
- else
55
- normalized_hash[old_key] = json_line[old_key]
56
- end
57
- end
58
-
59
- normalized_hash
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module Finders
7
- module Rails
8
- class ApiLogFinder < JsonLogFinder
9
- def initialize(base_path, file_path = 'gitlab-rails/api_json.log')
10
- super(base_path, file_path)
11
- end
12
-
13
- def new_log(data)
14
- LogTypes::Rails::ApiLog.new(data)
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module Finders
7
- module Rails
8
- class ApplicationLogFinder < JsonLogFinder
9
- def initialize(base_path, file_path = 'gitlab-rails/application_json.log')
10
- super(base_path, file_path)
11
- end
12
-
13
- def new_log(data)
14
- LogTypes::Rails::ApplicationLog.new(data)
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module Finders
7
- module Rails
8
- class ExceptionLogFinder < JsonLogFinder
9
- def initialize(base_path, file_path = 'gitlab-rails/exceptions_json.log')
10
- super(base_path, file_path)
11
- end
12
-
13
- def new_log(data)
14
- LogTypes::Rails::ExceptionLog.new(data)
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module Finders
7
- module Rails
8
- class GraphqlLogFinder < JsonLogFinder
9
- def initialize(base_path, file_path = 'gitlab-rails/graphql_json.log')
10
- super(base_path, file_path)
11
- end
12
-
13
- def new_log(data)
14
- LogTypes::Rails::GraphqlLog.new(data)
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module LogTypes
7
- class Log
8
- def initialize(name, data)
9
- @name = name
10
- @data = data
11
- end
12
-
13
- attr_reader :name, :data
14
-
15
- def summary_fields
16
- [
17
- :severity,
18
- :correlation_id,
19
- :time,
20
- :message
21
- ]
22
- end
23
-
24
- def summary
25
- summary = {}
26
-
27
- summary_fields.each do |field|
28
- value = data[field]
29
- summary[field] = value unless value.nil?
30
- end
31
-
32
- summary
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module LogTypes
7
- module Rails
8
- class ApiLog < Log
9
- include SharedFields::Exception
10
- include SharedFields::Meta
11
-
12
- def initialize(data)
13
- super('Rails API', data)
14
- end
15
-
16
- def summary_fields
17
- super.concat(
18
- [
19
- :method,
20
- :path,
21
- :status,
22
- :params,
23
- :api_error
24
- ],
25
- exception_fields,
26
- meta_fields
27
- )
28
- end
29
- end
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module LogTypes
7
- module Rails
8
- class ApplicationLog < Log
9
- include SharedFields::Exception
10
- include SharedFields::Meta
11
-
12
- def initialize(data)
13
- super('Rails Application', data)
14
- end
15
-
16
- def summary_fields
17
- super.concat(
18
- exception_fields,
19
- meta_fields
20
- )
21
- end
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module LogTypes
7
- module Rails
8
- class ExceptionLog < Log
9
- include SharedFields::Exception
10
-
11
- def initialize(data)
12
- super('Rails Exceptions', data)
13
- end
14
-
15
- def summary_fields
16
- super.concat(exception_fields)
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module LogTypes
7
- module Rails
8
- class GraphqlLog < Log
9
- include SharedFields::Meta
10
-
11
- def initialize(data)
12
- super('Rails GraphQL', data)
13
- end
14
-
15
- def summary_fields
16
- super.concat(
17
- [
18
- :operation_name,
19
- :query_string,
20
- :variables
21
- ],
22
- meta_fields
23
- )
24
- end
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- module SharedFields
7
- module Meta
8
- def meta_fields
9
- [
10
- :meta_user,
11
- :meta_project,
12
- :meta_caller_id
13
- ]
14
- end
15
- end
16
-
17
- module Exception
18
- def exception_fields
19
- [
20
- :exception_class,
21
- :exception_message,
22
- :exception_backtrace
23
- ]
24
- end
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gitlab
4
- module QA
5
- module SystemLogs
6
- class SystemLogsFormatter
7
- NUM_OF_LOG_SECTIONS = 4
8
-
9
- def initialize(base_paths, correlation_id)
10
- @base_paths = base_paths
11
- @correlation_id = correlation_id
12
- end
13
-
14
- def system_logs_summary_markdown
15
- log_sections = Array.new(NUM_OF_LOG_SECTIONS) { [] }
16
-
17
- @base_paths.each do |base_path|
18
- all_logs = [
19
- Finders::Rails::ApiLogFinder.new(base_path).find(@correlation_id),
20
- Finders::Rails::ExceptionLogFinder.new(base_path).find(@correlation_id),
21
- Finders::Rails::ApplicationLogFinder.new(base_path).find(@correlation_id),
22
- Finders::Rails::GraphqlLogFinder.new(base_path).find(@correlation_id)
23
- ]
24
-
25
- create_log_summary_sections!(all_logs, log_sections)
26
- end
27
-
28
- log_sections.prepend('### System Logs') unless log_sections.all?(&:empty?)
29
- log_sections.join("\n").rstrip
30
- end
31
-
32
- private
33
-
34
- def create_log_summary_sections!(all_logs, sections)
35
- sections.zip(all_logs) do |section, logs|
36
- unless logs.empty?
37
- section_title = "\n#### #{logs.first.name}"
38
- section.append(section_title) unless section.include?(section_title)
39
- section.append(create_log_summaries(logs))
40
- end
41
- end
42
- end
43
-
44
- def create_log_summaries(logs)
45
- section = []
46
-
47
- logs.each do |log|
48
- log_summary = <<~MARKDOWN.chomp
49
- <details><summary>Click to expand</summary>
50
-
51
- ```json
52
- #{JSON.pretty_generate(log.summary)}
53
- ```
54
- </details>
55
- MARKDOWN
56
-
57
- section.append(log_summary)
58
- end
59
-
60
- section.join("\n\n")
61
- end
62
- end
63
- end
64
- end
65
- end