gitlab_quality-test_tooling 2.22.0 → 2.23.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/Gemfile.lock +1 -1
- data/exe/post-to-slack +4 -0
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +43 -1
- data/lib/gitlab_quality/test_tooling/slack/post_to_slack.rb +103 -3
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/utils.rb +1 -1
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c015244f26f542bc2a00450d783e83ce4067e785e4f3dcdd242178b5144a722
|
4
|
+
data.tar.gz: 46f7ed259a123fcee7fb3ce26f8e31aa15beed4f3eac08f41dfadb2715abfa76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5e93d7172b231765c6e58bd92eb567cb02fd1fdd522f749241359ce38af74ea058c8323a749e6eb54c5de6091236b3ab8a7494aa4da9870f0dfc5f7d1e2e41f
|
7
|
+
data.tar.gz: c9f71fd4df1d8af85264396cd0625e2b7cbaab934d164037c8edfc8bc90d542c780ea2394b9950c61e7e909cfcd40d482e66332c3cfecf1f2e58e29c80a77461
|
data/Gemfile.lock
CHANGED
data/exe/post-to-slack
CHANGED
@@ -77,6 +77,10 @@ options = OptionParser.new do |opts|
|
|
77
77
|
params[:icon_emoji] = icon_emoji
|
78
78
|
end
|
79
79
|
|
80
|
+
opts.on('-e', '--environment-issues-file FILE', String, 'Add environment issues alert based on grouped failure data in a file') do |file|
|
81
|
+
params[:environment_issues_file] = file
|
82
|
+
end
|
83
|
+
|
80
84
|
opts.on_tail('-v', '--version', 'Show the version') do
|
81
85
|
require_relative "../lib/gitlab_quality/test_tooling/version"
|
82
86
|
puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
|
@@ -71,6 +71,7 @@ module GitlabQuality
|
|
71
71
|
exclude_labels_for_search: nil,
|
72
72
|
metrics_files: [],
|
73
73
|
group_similar: false,
|
74
|
+
environment_issues_output_file: nil,
|
74
75
|
**kwargs)
|
75
76
|
super
|
76
77
|
@max_diff_ratio = max_diff_ratio.to_f
|
@@ -81,11 +82,13 @@ module GitlabQuality
|
|
81
82
|
@commented_issue_list = Set.new
|
82
83
|
@metrics_files = Array(metrics_files)
|
83
84
|
@group_similar = group_similar
|
85
|
+
@environment_issues_output_file = environment_issues_output_file
|
84
86
|
end
|
85
87
|
|
86
88
|
private
|
87
89
|
|
88
|
-
attr_reader :max_diff_ratio, :system_logs, :base_issue_labels, :exclude_labels_for_search, :metrics_files, :ops_gitlab_client, :group_similar
|
90
|
+
attr_reader :max_diff_ratio, :system_logs, :base_issue_labels, :exclude_labels_for_search, :metrics_files, :ops_gitlab_client, :group_similar,
|
91
|
+
:environment_issues_output_file
|
89
92
|
|
90
93
|
def run!
|
91
94
|
puts "Reporting test failures in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
|
@@ -123,6 +126,8 @@ module GitlabQuality
|
|
123
126
|
|
124
127
|
grouper.process_failures(failure_data)
|
125
128
|
grouper.process_issues
|
129
|
+
|
130
|
+
export_environment_issues_for_slack(environment_issues_output_file) if environment_issues_output_file
|
126
131
|
end
|
127
132
|
|
128
133
|
def collect_all_test_results
|
@@ -690,6 +695,43 @@ module GitlabQuality
|
|
690
695
|
|
691
696
|
"|| [Screenshot](#{ci_job_url}/artifacts/file/#{screenshot_path})"
|
692
697
|
end
|
698
|
+
|
699
|
+
def export_environment_issues_for_slack(output_file)
|
700
|
+
return unless similar_issues_grouped?
|
701
|
+
|
702
|
+
File.write(output_file, build_environment_issues_data.to_json)
|
703
|
+
rescue StandardError => e
|
704
|
+
puts "Warning: Failed to export environment issues for Slack: #{e.message}"
|
705
|
+
end
|
706
|
+
|
707
|
+
def build_environment_issues_data
|
708
|
+
{
|
709
|
+
grouped_failures: format_grouped_failures,
|
710
|
+
summary: grouper.summary
|
711
|
+
}
|
712
|
+
end
|
713
|
+
|
714
|
+
def format_grouped_failures
|
715
|
+
grouper.grouped_failures.map do |_fingerprint, grouped_failure|
|
716
|
+
{
|
717
|
+
fingerprint: grouped_failure[:fingerprint],
|
718
|
+
pattern_name: grouped_failure[:pattern_name],
|
719
|
+
normalized_message: grouped_failure[:normalized_message],
|
720
|
+
failure_count: grouped_failure[:failures].size,
|
721
|
+
failures: format_individual_failures(grouped_failure[:failures])
|
722
|
+
}
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
def format_individual_failures(failures)
|
727
|
+
failures.map do |failure|
|
728
|
+
{
|
729
|
+
description: failure[:description],
|
730
|
+
file_path: failure[:file_path],
|
731
|
+
ci_job_url: failure[:ci_job_url]
|
732
|
+
}
|
733
|
+
end
|
734
|
+
end
|
693
735
|
end
|
694
736
|
end
|
695
737
|
end
|
@@ -1,15 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module GitlabQuality
|
4
6
|
module TestTooling
|
5
7
|
module Slack
|
6
8
|
class PostToSlack
|
7
|
-
|
9
|
+
MAX_PATTERN_MESSAGE_LENGTH = 150
|
10
|
+
MAX_GROUPED_FAILURES_TO_DISPLAY = 10
|
11
|
+
|
12
|
+
def initialize(slack_webhook_url:, channel:, message:, username:, icon_emoji:, environment_issues_file: nil)
|
8
13
|
@slack_webhook_url = slack_webhook_url
|
9
14
|
@channel = channel
|
10
15
|
@message = message
|
11
16
|
@username = username
|
12
17
|
@icon_emoji = icon_emoji
|
18
|
+
@environment_issues_file = environment_issues_file
|
13
19
|
end
|
14
20
|
|
15
21
|
def invoke!
|
@@ -17,7 +23,7 @@ module GitlabQuality
|
|
17
23
|
params['channel'] = channel
|
18
24
|
params['username'] = username
|
19
25
|
params['icon_emoji'] = icon_emoji
|
20
|
-
params['text'] =
|
26
|
+
params['text'] = build_message
|
21
27
|
|
22
28
|
Support::HttpRequest.make_http_request(
|
23
29
|
method: 'post',
|
@@ -29,7 +35,101 @@ module GitlabQuality
|
|
29
35
|
|
30
36
|
private
|
31
37
|
|
32
|
-
attr_reader :slack_webhook_url, :channel, :message, :username, :icon_emoji
|
38
|
+
attr_reader :slack_webhook_url, :channel, :message, :username, :icon_emoji, :environment_issues_file
|
39
|
+
|
40
|
+
def build_message
|
41
|
+
messages = []
|
42
|
+
messages << message if message && !message.empty?
|
43
|
+
messages << format_environment_issues if environment_issues_file && File.exist?(environment_issues_file)
|
44
|
+
|
45
|
+
messages.join("\n\n")
|
46
|
+
end
|
47
|
+
|
48
|
+
def format_environment_issues
|
49
|
+
issues_data = JSON.parse(File.read(environment_issues_file))
|
50
|
+
return nil if issues_data.nil? || issues_data['grouped_failures'].empty?
|
51
|
+
|
52
|
+
build_slack_message(issues_data)
|
53
|
+
rescue JSON::ParserError => e
|
54
|
+
":x: Error parsing environment issues file: #{e.message}"
|
55
|
+
rescue StandardError => e
|
56
|
+
":x: Error formatting environment issues: #{e.message}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def format_single_environment_issue(failure)
|
60
|
+
pattern_title = pattern_display_name(failure['pattern_name'])
|
61
|
+
|
62
|
+
issue_text = build_issue_header(pattern_title, failure)
|
63
|
+
issue_text + build_job_info(failure)
|
64
|
+
end
|
65
|
+
|
66
|
+
def pattern_display_name(pattern_name)
|
67
|
+
case pattern_name&.downcase
|
68
|
+
when /http_500/
|
69
|
+
"HTTP 500 Internal Server Errors"
|
70
|
+
when /http_400/
|
71
|
+
"HTTP 400 Bad Request Errors"
|
72
|
+
when /http_503/
|
73
|
+
"HTTP 503 Service Unavailable"
|
74
|
+
when /timeout/
|
75
|
+
"Timeout Errors"
|
76
|
+
when /git_rpc|repository/
|
77
|
+
"Git/Repository Errors"
|
78
|
+
else
|
79
|
+
"#{pattern_name&.humanize || 'Unknown'} Errors"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_slack_message(issues_data)
|
84
|
+
header = ":warning: *Environment Issues Detected*\n"
|
85
|
+
|
86
|
+
issue_messages = format_issue_messages(issues_data['grouped_failures'])
|
87
|
+
truncation_note = build_truncation_note(issues_data['grouped_failures'].size)
|
88
|
+
summary = build_summary_text(issues_data['summary'])
|
89
|
+
|
90
|
+
header + issue_messages + truncation_note + summary
|
91
|
+
end
|
92
|
+
|
93
|
+
def format_issue_messages(grouped_failures)
|
94
|
+
failures_to_show = grouped_failures.first(MAX_GROUPED_FAILURES_TO_DISPLAY)
|
95
|
+
failures_to_show.map { |failure| format_single_environment_issue(failure) }.join("\n\n")
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_truncation_note(total_failures)
|
99
|
+
return "" unless total_failures > MAX_GROUPED_FAILURES_TO_DISPLAY
|
100
|
+
|
101
|
+
"\n_... and #{total_failures - MAX_GROUPED_FAILURES_TO_DISPLAY} more environment issue(s)_"
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_issue_header(pattern_title, failure)
|
105
|
+
<<~TEXT
|
106
|
+
*#{pattern_title}*
|
107
|
+
• Affected tests: #{failure['failure_count']}
|
108
|
+
• Pattern: `#{truncate_message(failure['normalized_message'])}`
|
109
|
+
TEXT
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_job_info(failure)
|
113
|
+
return "" unless failure['failures']&.any?
|
114
|
+
|
115
|
+
job_urls = failure['failures'].filter_map { |f| f['ci_job_url'] }.uniq
|
116
|
+
job_urls.any? ? "• Jobs affected: #{job_urls.size}\n" : ""
|
117
|
+
end
|
118
|
+
|
119
|
+
def build_summary_text(summary)
|
120
|
+
<<~TEXT
|
121
|
+
|
122
|
+
*Summary:* #{summary['grouped_issues']} environment issue(s) affecting #{summary['total_grouped_failures']} test(s)
|
123
|
+
|
124
|
+
_Note: Future improvements will include direct GitLab issue links and enhanced filtering._
|
125
|
+
_Track progress: https://gitlab.com/groups/gitlab-org/quality/quality-engineering/-/epics/168_
|
126
|
+
TEXT
|
127
|
+
end
|
128
|
+
|
129
|
+
def truncate_message(message)
|
130
|
+
text = message.to_s
|
131
|
+
text.length > MAX_PATTERN_MESSAGE_LENGTH ? "#{text[0..MAX_PATTERN_MESSAGE_LENGTH]}..." : text
|
132
|
+
end
|
33
133
|
end
|
34
134
|
end
|
35
135
|
end
|
@@ -78,7 +78,7 @@ module GitlabQuality
|
|
78
78
|
)
|
79
79
|
ENGINE = MergeTree()
|
80
80
|
PARTITION BY toYYYYMM(timestamp)
|
81
|
-
ORDER BY (ci_project_path,
|
81
|
+
ORDER BY (ci_project_path, status, run_type, feature_category, file_path, timestamp, ci_pipeline_id)
|
82
82
|
SETTINGS index_granularity = 8192;
|
83
83
|
SQL
|
84
84
|
return if config.extra_metadata_columns.empty?
|
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: 2.
|
4
|
+
version: 2.23.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: 2025-10-
|
11
|
+
date: 2025-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: climate_control
|