dri 0.4.0 → 0.6.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/.gitignore +1 -0
- data/.gitlab-ci.yml +15 -12
- data/.tool-versions +1 -1
- data/Gemfile.lock +19 -5
- data/README.md +23 -3
- data/dri.gemspec +2 -1
- data/lib/dri/api_client.rb +91 -16
- data/lib/dri/cli.rb +3 -0
- data/lib/dri/command.rb +7 -3
- data/lib/dri/commands/analyze/stack_traces.rb +106 -0
- data/lib/dri/commands/analyze.rb +20 -0
- data/lib/dri/commands/fetch/failures.rb +30 -26
- data/lib/dri/commands/fetch/featureflags.rb +12 -36
- data/lib/dri/commands/fetch/pipelines.rb +254 -0
- data/lib/dri/commands/fetch.rb +12 -0
- data/lib/dri/commands/init.rb +4 -2
- data/lib/dri/commands/profile.rb +1 -0
- data/lib/dri/commands/publish/report.rb +91 -17
- data/lib/dri/commands/publish.rb +2 -0
- data/lib/dri/feature_flag_report.rb +47 -0
- data/lib/dri/report.rb +1 -1
- data/lib/dri/utils/constants.rb +59 -0
- data/lib/dri/utils/feature_flag_consts.rb +13 -0
- data/lib/dri/utils/table.rb +2 -2
- data/lib/dri/version.rb +1 -1
- metadata +26 -7
- data/lib/dri/templates/incidents/.gitkeep +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f99fb005dc6cbc12b4fe9b8b131680c09474c125d7fe396fa3b0fadd8804dba
|
4
|
+
data.tar.gz: dfa14e8f527bfed0d17a428d81ac16955251cba3ef19907016a19bc43901e768
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7e619cb7241e804810b8cdd97759df84c98aada62c975e0d39fa9f9125219846f5e8bddb50624ce22cc1f836006b023d49375f0a17e298d0f0086d220390e87
|
7
|
+
data.tar.gz: '09e94ad8bc08ffb95839e01456703e81f8ed262c7c925beadeb21d71a816f6632b5772b91af5700901331c5e7560af56b1398ed41ac6dc04d884f61b6ab6043b'
|
data/.gitignore
CHANGED
data/.gitlab-ci.yml
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
.job_base:
|
2
|
-
image: ruby:2.
|
2
|
+
image: registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3
|
3
3
|
variables:
|
4
4
|
BUNDLE_PATH: vendor/bundle
|
5
5
|
BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES: "true"
|
6
|
+
BUNDLE_FROZEN: "true"
|
6
7
|
before_script:
|
7
|
-
- gem install bundler -v 2.3.9 --no-document
|
8
8
|
- bundle install
|
9
9
|
cache:
|
10
10
|
key:
|
@@ -13,26 +13,34 @@
|
|
13
13
|
- Gemfile.lock
|
14
14
|
paths:
|
15
15
|
- vendor/bundle
|
16
|
+
policy: pull
|
16
17
|
rules:
|
17
18
|
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
18
19
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
19
20
|
|
20
21
|
include:
|
21
22
|
- project: gitlab-org/quality/pipeline-common
|
22
|
-
ref: 0.
|
23
|
+
ref: 0.15.1
|
23
24
|
file:
|
24
25
|
- /ci/gem-release.yml
|
26
|
+
- /ci/ref-update.gitlab-ci.yml
|
27
|
+
|
28
|
+
variables:
|
29
|
+
RUBY_VERSION: "2.7"
|
25
30
|
|
26
31
|
stages:
|
27
32
|
- build
|
28
33
|
- test
|
29
34
|
- deploy
|
35
|
+
- update
|
30
36
|
|
31
37
|
build_gem:
|
32
38
|
stage: build
|
33
39
|
extends: .job_base
|
34
40
|
script:
|
35
41
|
- gem build
|
42
|
+
cache:
|
43
|
+
policy: pull-push
|
36
44
|
|
37
45
|
rubocop:
|
38
46
|
stage: test
|
@@ -43,13 +51,8 @@ rubocop:
|
|
43
51
|
rspec:
|
44
52
|
stage: test
|
45
53
|
extends: .job_base
|
54
|
+
parallel:
|
55
|
+
matrix:
|
56
|
+
- RUBY_VERSION: ['2.7', '3.0']
|
46
57
|
script:
|
47
|
-
- bundle exec rspec --color
|
48
|
-
|
49
|
-
rspec 3.0:
|
50
|
-
stage: test
|
51
|
-
extends: .job_base
|
52
|
-
image: ruby:3.0
|
53
|
-
script:
|
54
|
-
- bundle exec rspec --color
|
55
|
-
|
58
|
+
- bundle exec rspec --force-color
|
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 3.0.
|
1
|
+
ruby 3.0.4
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dri (0.
|
4
|
+
dri (0.6.0)
|
5
|
+
amatch (~> 0.4.1)
|
5
6
|
gitlab (~> 4.18)
|
6
7
|
httparty (~> 0.20.0)
|
7
8
|
json (~> 2.6.1)
|
@@ -27,14 +28,17 @@ GEM
|
|
27
28
|
tzinfo (~> 2.0)
|
28
29
|
addressable (2.8.0)
|
29
30
|
public_suffix (>= 2.0.2, < 5.0)
|
31
|
+
amatch (0.4.1)
|
32
|
+
mize
|
33
|
+
tins (~> 1.0)
|
30
34
|
ast (2.4.2)
|
31
35
|
coderay (1.1.3)
|
32
36
|
concurrent-ruby (1.1.10)
|
33
37
|
crack (0.4.5)
|
34
38
|
rexml
|
35
39
|
diff-lcs (1.5.0)
|
36
|
-
gitlab (4.
|
37
|
-
httparty (~> 0.
|
40
|
+
gitlab (4.19.0)
|
41
|
+
httparty (~> 0.20)
|
38
42
|
terminal-table (>= 1.5.1)
|
39
43
|
gitlab-styles (7.0.0)
|
40
44
|
rubocop (~> 0.91, >= 0.91.1)
|
@@ -56,12 +60,16 @@ GEM
|
|
56
60
|
mime-types-data (~> 3.2015)
|
57
61
|
mime-types-data (3.2022.0105)
|
58
62
|
minitest (5.15.0)
|
63
|
+
mize (0.4.0)
|
64
|
+
protocol (~> 2.0)
|
59
65
|
multi_xml (0.6.0)
|
60
66
|
parallel (1.22.1)
|
61
67
|
parser (3.1.1.0)
|
62
68
|
ast (~> 2.4.1)
|
63
69
|
pastel (0.8.0)
|
64
70
|
tty-color (~> 0.5)
|
71
|
+
protocol (2.0.0)
|
72
|
+
ruby_parser (~> 3.0)
|
65
73
|
pry (0.14.1)
|
66
74
|
coderay (~> 1.1)
|
67
75
|
method_source (~> 1.0)
|
@@ -110,15 +118,21 @@ GEM
|
|
110
118
|
rubocop (~> 0.87)
|
111
119
|
rubocop-ast (>= 0.7.1)
|
112
120
|
ruby-progressbar (1.11.0)
|
121
|
+
ruby_parser (3.19.1)
|
122
|
+
sexp_processor (~> 4.16)
|
123
|
+
sexp_processor (4.16.1)
|
113
124
|
strings (0.2.1)
|
114
125
|
strings-ansi (~> 0.2)
|
115
126
|
unicode-display_width (>= 1.5, < 3.0)
|
116
127
|
unicode_utils (~> 1.4)
|
117
128
|
strings-ansi (0.2.0)
|
129
|
+
sync (0.5.0)
|
118
130
|
terminal-table (3.0.2)
|
119
131
|
unicode-display_width (>= 1.1.1, < 3)
|
120
132
|
thor (1.0.1)
|
121
133
|
timecop (0.9.5)
|
134
|
+
tins (1.31.1)
|
135
|
+
sync
|
122
136
|
tty-box (0.7.0)
|
123
137
|
pastel (~> 0.8)
|
124
138
|
strings (~> 0.2.0)
|
@@ -162,10 +176,10 @@ DEPENDENCIES
|
|
162
176
|
dri!
|
163
177
|
gitlab-styles (~> 7.0.0)
|
164
178
|
pry (~> 0.14.1)
|
165
|
-
rake
|
179
|
+
rake (~> 13.0)
|
166
180
|
rspec (~> 3.10.0)
|
167
181
|
timecop (~> 0.9.1)
|
168
182
|
webmock (~> 3.5)
|
169
183
|
|
170
184
|
BUNDLED WITH
|
171
|
-
2.3.
|
185
|
+
2.3.16
|
data/README.md
CHANGED
@@ -66,7 +66,9 @@ $ dri profile
|
|
66
66
|
- profile
|
67
67
|
- reports
|
68
68
|
- [6. incidents](#6-incidents)
|
69
|
-
- [7.
|
69
|
+
- [7. analyze](#7-analyze)
|
70
|
+
- stacktraces
|
71
|
+
- [8. version](#8-version)
|
70
72
|
|
71
73
|
#### 1. init
|
72
74
|
|
@@ -123,12 +125,19 @@ $ dri fetch dequarantines
|
|
123
125
|
|
124
126
|
Fetches open dequarantine Merge Requests to be reviewed
|
125
127
|
|
128
|
+
Results are organized by environment (production, staging, staging ref and preprod).
|
126
129
|
```shell
|
127
130
|
$ dri fetch featureflags
|
128
131
|
```
|
129
132
|
|
130
133
|
Fetches a list of today's feature flag changes, including the date and time in UTC of when the change occurred as well as a link to the corresponding issue from the feature-flag-log project.
|
131
|
-
|
134
|
+
|
135
|
+
```shell
|
136
|
+
$ dri fetch pipelines
|
137
|
+
```
|
138
|
+
|
139
|
+
Fetches a table containing last executed pipeline and its test report link for all monitored pipelines.
|
140
|
+
The timestamps are in UTC
|
132
141
|
|
133
142
|
#### 4. publish
|
134
143
|
|
@@ -145,6 +154,7 @@ $ dri publish report --format=list # formats the report in a list
|
|
145
154
|
$ dri publish report --format=table # formats the report in a table (default)
|
146
155
|
$ dri publish report --dry-run # the report is only generated locally
|
147
156
|
$ dri publish report --actions # activate the actions prompt for each failure
|
157
|
+
$ dri publish report --feature-flags # includes a summary of the feature flag changes on each environment
|
148
158
|
```
|
149
159
|
|
150
160
|
**Note:** These options above can be combined like:
|
@@ -190,7 +200,17 @@ $ dri incidents
|
|
190
200
|
|
191
201
|
Have a quick look at currently active/mitigated incidents on GitLab services.
|
192
202
|
|
193
|
-
#### 7.
|
203
|
+
#### 7. analyze
|
204
|
+
|
205
|
+
```shell
|
206
|
+
$ dri analyze stacktraces
|
207
|
+
```
|
208
|
+
Searches through any open test failure issues and publishes a report that identifies
|
209
|
+
issues that have similar stack traces.
|
210
|
+
This may be useful to identify situations where a common test failure is presenting
|
211
|
+
itself across multiple individual test cases, over a period of time.
|
212
|
+
|
213
|
+
#### 8. version
|
194
214
|
|
195
215
|
```shell
|
196
216
|
$ dri version
|
data/dri.gemspec
CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ['lib']
|
24
24
|
|
25
|
+
spec.add_dependency 'amatch', '~> 0.4.1'
|
25
26
|
spec.add_dependency "gitlab", "~> 4.18"
|
26
27
|
spec.add_dependency 'httparty', '~> 0.20.0'
|
27
28
|
spec.add_dependency 'json', '~> 2.6.1'
|
@@ -39,7 +40,7 @@ Gem::Specification.new do |spec|
|
|
39
40
|
|
40
41
|
spec.add_development_dependency 'gitlab-styles', '~> 7.0.0'
|
41
42
|
spec.add_development_dependency "pry", "~> 0.14.1"
|
42
|
-
spec.add_development_dependency 'rake'
|
43
|
+
spec.add_development_dependency 'rake', "~> 13.0"
|
43
44
|
spec.add_development_dependency 'rspec', '~> 3.10.0'
|
44
45
|
spec.add_development_dependency "timecop", "~> 0.9.1"
|
45
46
|
spec.add_development_dependency 'webmock', '~> 3.5'
|
data/lib/dri/api_client.rb
CHANGED
@@ -3,20 +3,28 @@
|
|
3
3
|
require "httparty"
|
4
4
|
require "json"
|
5
5
|
require "tty-config"
|
6
|
-
require
|
7
|
-
require
|
6
|
+
require "cgi"
|
7
|
+
require "gitlab"
|
8
8
|
|
9
9
|
module Dri
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
TokenNotProvidedError = Class.new(StandardError)
|
11
|
+
class ApiClient # rubocop:disable Metrics/ClassLength
|
12
|
+
API_URL = "https://gitlab.com/api/v4"
|
13
|
+
OPS_API_URL = "https://ops.gitlab.net/api/v4"
|
14
|
+
TESTCASES_PROJECT_ID = "11229385"
|
15
|
+
TRIAGE_PROJECT_ID = "15291320"
|
16
|
+
GITLAB_PROJECT_ID = "278964"
|
17
|
+
FEATURE_FLAG_LOG_PROJECT_ID = "15208716"
|
18
|
+
INFRA_TEAM_PROD_PROJECT_ID = "7444821"
|
19
|
+
def initialize(config, ops = false)
|
19
20
|
@token = config.read.dig("settings", "token")
|
21
|
+
@ops_token = config.read.dig("settings", "ops_token")
|
22
|
+
if @token.nil? || @ops_token.nil?
|
23
|
+
raise TokenNotProvidedError, "Gitlab API client cannot be initialized without both access tokens. " \
|
24
|
+
"Run dri init again or dri profile --edit"
|
25
|
+
end
|
26
|
+
|
27
|
+
@ops_instance = ops
|
20
28
|
end
|
21
29
|
|
22
30
|
# Fetch triaged failures
|
@@ -57,6 +65,18 @@ module Dri
|
|
57
65
|
).auto_paginate
|
58
66
|
end
|
59
67
|
|
68
|
+
# Fetch issues related to failing test cases
|
69
|
+
#
|
70
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
71
|
+
def fetch_test_failure_issues(labels: 'failure::new')
|
72
|
+
gitlab.issues(
|
73
|
+
GITLAB_PROJECT_ID,
|
74
|
+
labels: labels,
|
75
|
+
state: 'opened',
|
76
|
+
scope: "all"
|
77
|
+
).auto_paginate
|
78
|
+
end
|
79
|
+
|
60
80
|
# Fetch related issue mrs
|
61
81
|
#
|
62
82
|
# @param [Integer] issue_iid
|
@@ -149,18 +169,73 @@ module Dri
|
|
149
169
|
gitlab.issues(INFRA_TEAM_PROD_PROJECT_ID, order_by: "updated_at", state: "opened", labels: "incident")
|
150
170
|
end
|
151
171
|
|
172
|
+
# Fetch pipelines
|
173
|
+
#
|
174
|
+
# @param [Integer] project_id
|
175
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
176
|
+
def pipelines(project_id:, options:, auto_paginate: false)
|
177
|
+
if auto_paginate
|
178
|
+
gitlab.pipelines(project_id, options).auto_paginate
|
179
|
+
else
|
180
|
+
gitlab.pipelines(project_id, options)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Fetch single pipeline
|
185
|
+
#
|
186
|
+
# @param [Integer] project_id
|
187
|
+
# @param [Integer] pipeline_id
|
188
|
+
# @return [<Gitlab::ObjectifiedHash>]
|
189
|
+
def pipeline(project_id, pipeline_id)
|
190
|
+
gitlab.pipeline(project_id, pipeline_id)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Fetch test report from a pipeline
|
194
|
+
#
|
195
|
+
# @param [Integer] project_id
|
196
|
+
# @param [Integer] pipeline_id
|
197
|
+
# @return [<Gitlab::ObjectifiedHash>]
|
198
|
+
def pipeline_test_report(project_id, pipeline_id)
|
199
|
+
gitlab.pipeline_test_report(project_id, pipeline_id)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Fetch pipeline bridges/downstream pipelines
|
203
|
+
#
|
204
|
+
# @param [Integer] project_id
|
205
|
+
# @param [Integer] pipeline_id
|
206
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
207
|
+
def pipeline_bridges(project_id, pipeline_id, options = {})
|
208
|
+
gitlab.pipeline_bridges(project_id, pipeline_id, options).auto_paginate
|
209
|
+
end
|
210
|
+
|
211
|
+
# Fetch jobs from a pipeline
|
212
|
+
#
|
213
|
+
# @param [Integer] project_id
|
214
|
+
# @param [Integer] pipeline_id
|
215
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
216
|
+
def pipeline_jobs(project_id, pipeline_id, options = {})
|
217
|
+
gitlab.pipeline_jobs(project_id, pipeline_id, options).auto_paginate
|
218
|
+
end
|
219
|
+
|
152
220
|
private
|
153
221
|
|
154
|
-
attr_reader :token
|
222
|
+
attr_reader :token, :ops_token
|
155
223
|
|
156
224
|
# Gitlab client
|
157
225
|
#
|
158
226
|
# @return [Gitlab::Client]
|
159
227
|
def gitlab
|
160
|
-
@
|
161
|
-
|
162
|
-
|
163
|
-
|
228
|
+
if @ops_instance
|
229
|
+
@ops_client ||= Gitlab.client(
|
230
|
+
endpoint: OPS_API_URL,
|
231
|
+
private_token: ops_token
|
232
|
+
)
|
233
|
+
else
|
234
|
+
@gitlab_client ||= Gitlab.client(
|
235
|
+
endpoint: API_URL,
|
236
|
+
private_token: token
|
237
|
+
)
|
238
|
+
end
|
164
239
|
end
|
165
240
|
end
|
166
241
|
end
|
data/lib/dri/cli.rb
CHANGED
@@ -82,5 +82,8 @@ module Dri
|
|
82
82
|
|
83
83
|
require_relative 'commands/publish'
|
84
84
|
register Dri::Commands::Publish, 'publish', 'publish [SUBCOMMAND]', 'Publish report for handover'
|
85
|
+
|
86
|
+
require_relative 'commands/analyze'
|
87
|
+
register Dri::Commands::Analyze, 'analyze', 'analyze [SUBCOMMAND]', 'Analysis of test failures and issues'
|
85
88
|
end
|
86
89
|
end
|
data/lib/dri/command.rb
CHANGED
@@ -26,14 +26,14 @@ module Dri
|
|
26
26
|
config = TTY::Config.new
|
27
27
|
config.filename = ".dri_profile"
|
28
28
|
config.extname = ".yml"
|
29
|
-
config.append_path Dir.pwd
|
30
29
|
config.append_path Dir.home
|
30
|
+
config.append_path Dir.pwd
|
31
31
|
config
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
def api_client
|
36
|
-
ApiClient.new(config)
|
35
|
+
def api_client(ops: false)
|
36
|
+
ApiClient.new(config, ops)
|
37
37
|
end
|
38
38
|
|
39
39
|
def profile
|
@@ -52,6 +52,10 @@ module Dri
|
|
52
52
|
@token ||= profile["settings"]["token"]
|
53
53
|
end
|
54
54
|
|
55
|
+
def ops_token
|
56
|
+
@ops_token ||= profile["settings"]["ops_token"]
|
57
|
+
end
|
58
|
+
|
55
59
|
def timezone
|
56
60
|
@timezone ||= profile["settings"]["timezone"]
|
57
61
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
require_relative '../../utils/table'
|
5
|
+
require 'amatch'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
module Dri
|
9
|
+
module Commands
|
10
|
+
class Analyze
|
11
|
+
class StackTraces < Dri::Command
|
12
|
+
include Amatch
|
13
|
+
include Dri::Utils::Table
|
14
|
+
|
15
|
+
def initialize(options)
|
16
|
+
@options = options
|
17
|
+
@labels = options[:labels] || 'failure::new'
|
18
|
+
@similarity_score_threshold = options[:similarity_score_threshold] || 0.9
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize
|
22
|
+
verify_config_exists
|
23
|
+
logger.info "#{Time.now.utc} Fetching issues"
|
24
|
+
|
25
|
+
data = []
|
26
|
+
|
27
|
+
spinner.run do
|
28
|
+
response = api_client.fetch_test_failure_issues(labels: @labels)
|
29
|
+
logger.info "#{Time.now.utc} API response completed"
|
30
|
+
|
31
|
+
if response.empty?
|
32
|
+
logger.info 'There are no failure::new issues identified!'
|
33
|
+
exit 0
|
34
|
+
end
|
35
|
+
|
36
|
+
data = identify_similar_issues(response)
|
37
|
+
|
38
|
+
logger.info "#{Time.now.utc} Processing Data Complete"
|
39
|
+
end
|
40
|
+
|
41
|
+
similar_stack_traces = data.each_with_object([]) do |item, similar_items|
|
42
|
+
next if item[:stack_trace].empty?
|
43
|
+
next if item[:related_errors].length < 2
|
44
|
+
|
45
|
+
similar_items << [item[:stack_trace], item[:related_errors]]
|
46
|
+
end
|
47
|
+
|
48
|
+
FileUtils.mkdir_p("#{Dir.pwd}/analyze_reports/stacktraces")
|
49
|
+
report_path = "analyze_reports/stacktraces/report-#{Time.now.utc.to_i}.md"
|
50
|
+
write_report(similar_stack_traces, report_path)
|
51
|
+
logger.success "Analyze StackTraces report is ready at: #{report_path}"
|
52
|
+
|
53
|
+
errors_count = similar_stack_traces.count
|
54
|
+
issues_count = similar_stack_traces.sum { |st| st[1].size }
|
55
|
+
output.puts "Found #{errors_count} common errors across a combination of #{issues_count} issues"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def write_report(similar_stack_traces, report_path)
|
61
|
+
File.open(report_path, 'a') do |out_file|
|
62
|
+
out_file.puts "## Stack Trace Analysis"
|
63
|
+
similar_stack_traces.each do |st|
|
64
|
+
out_file.puts "### StackTrace"
|
65
|
+
out_file.puts st[0]
|
66
|
+
out_file.puts "### Related issues"
|
67
|
+
st[1].each { |err| out_file.puts "* #{err}" }
|
68
|
+
out_file.puts "-" * 80
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def identify_similar_issues(response)
|
74
|
+
data = []
|
75
|
+
response.each do |item|
|
76
|
+
stack_trace = extract_stack_trace(item['description'])
|
77
|
+
|
78
|
+
data.find(-> { add_stack_trace(data, stack_trace) }) do |row|
|
79
|
+
calc_similarity(row[:stack_trace], stack_trace) >= @similarity_score_threshold
|
80
|
+
end[:related_errors].append(item['web_url'])
|
81
|
+
end
|
82
|
+
data
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_stack_trace(data, st)
|
86
|
+
obj = { stack_trace: st, related_errors: [] }
|
87
|
+
data.append(obj)
|
88
|
+
obj
|
89
|
+
end
|
90
|
+
|
91
|
+
def extract_stack_trace(description)
|
92
|
+
stack_trace = description[/```(.*)```/m]
|
93
|
+
# Remove common patterns that may impact matching
|
94
|
+
stack_trace&.gsub!(/^*Correlation Id: \S*$/, '')
|
95
|
+
stack_trace&.gsub!(/^*Sentry Url: \S*$/, '')
|
96
|
+
stack_trace&.gsub!(/^*Kibana Url: \S*$/, '')
|
97
|
+
stack_trace || ''
|
98
|
+
end
|
99
|
+
|
100
|
+
def calc_similarity(s1, s2)
|
101
|
+
JaroWinkler.new(s1).match(s2)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Dri
|
6
|
+
module Commands
|
7
|
+
class Analyze < Thor
|
8
|
+
namespace :analyze
|
9
|
+
|
10
|
+
desc 'stacktraces', 'Identify commonalities and patterns between stack traces'
|
11
|
+
method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
|
12
|
+
def stacktraces(*)
|
13
|
+
return invoke :help, %w[stacktraces] if options[:help]
|
14
|
+
|
15
|
+
require_relative 'analyze/stack_traces'
|
16
|
+
Dri::Commands::Analyze::StackTraces.new(options).execute
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -13,8 +13,9 @@ module Dri
|
|
13
13
|
SORT_BY_OPTIONS = {
|
14
14
|
title: 0,
|
15
15
|
triaged: 1,
|
16
|
-
|
17
|
-
|
16
|
+
environment: 2,
|
17
|
+
author: 3,
|
18
|
+
url: 4
|
18
19
|
}.freeze
|
19
20
|
|
20
21
|
def initialize(options)
|
@@ -25,14 +26,16 @@ module Dri
|
|
25
26
|
def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
26
27
|
verify_config_exists
|
27
28
|
|
29
|
+
urgent_environments = %w[canary canary.staging]
|
30
|
+
|
28
31
|
title = add_color('Title', :bright_yellow)
|
29
32
|
triaged = add_color('Triaged?', :bright_yellow)
|
33
|
+
environment = add_color('Environment', :bright_yellow)
|
30
34
|
author = add_color('Author', :bright_yellow)
|
31
35
|
url = add_color('URL', :bright_yellow)
|
32
36
|
|
33
37
|
failures = []
|
34
|
-
|
35
|
-
labels = [title, triaged, author, url]
|
38
|
+
labels = [title, triaged, environment, author, url]
|
36
39
|
triaged_counter = 0
|
37
40
|
|
38
41
|
logger.info "Fetching today's failures..."
|
@@ -50,6 +53,12 @@ module Dri
|
|
50
53
|
author = failure.to_h.dig('author', 'username')
|
51
54
|
url = failure.web_url
|
52
55
|
triaged = add_color('x', :red)
|
56
|
+
envs = failure.labels.select { |l| l.include?('found:') }.map do |l|
|
57
|
+
env = l.split(':').last.gsub('.gitlab.com', '')
|
58
|
+
|
59
|
+
env == 'gitlab.com' ? 'production' : env
|
60
|
+
end
|
61
|
+
urgent = urgent_environments.all? { |env| envs.include?(env) }
|
53
62
|
|
54
63
|
emoji_awards = api_client.fetch_awarded_emojis(failure.iid).find do |e|
|
55
64
|
e.name == emoji && e.to_h.dig('user', 'username') == username
|
@@ -61,32 +70,27 @@ module Dri
|
|
61
70
|
end
|
62
71
|
|
63
72
|
if @options[:urgent]
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
if label.include?('found:canary.gitlab.com' && 'found:canary.staging.gitlab.com')
|
68
|
-
urgent << [title, triaged, author, url]
|
69
|
-
end
|
70
|
-
end
|
73
|
+
failures << [title, triaged, envs.first, author, url] if urgent
|
74
|
+
else
|
75
|
+
failures << [title, triaged, envs.first, author, url]
|
71
76
|
end
|
72
|
-
|
73
|
-
failures << [title, triaged, author, url]
|
74
|
-
|
75
|
-
failures.sort_by! { |e| e[SORT_BY_OPTIONS[@options[:sort_by].to_sym]] } if @options[:sort_by]
|
76
77
|
end
|
77
|
-
end
|
78
78
|
|
79
|
-
|
80
|
-
print_table(labels, urgent, alignments: [:left, :center, :center, :left])
|
81
|
-
output.puts(<<~MSG)
|
82
|
-
Found: #{urgent.size} urgent failures, occurring in both canary.gitlab.com and canary.staging.gitlab.com.
|
83
|
-
MSG
|
84
|
-
else
|
85
|
-
print_table(labels, failures, alignments: [:left, :center, :center, :left])
|
86
|
-
output.puts(<<~MSG)
|
87
|
-
Found: #{failures.size} failures, of these #{triaged_counter} have been triaged with a #{emoji}.
|
88
|
-
MSG
|
79
|
+
failures.sort_by! { |failure| failure[SORT_BY_OPTIONS[@options[:sort_by]&.to_sym || :environment]] }
|
89
80
|
end
|
81
|
+
|
82
|
+
msg = if @options[:urgent]
|
83
|
+
<<~MSG
|
84
|
+
Found: #{failures.size} urgent failures, occurring in both canary.gitlab.com and canary.staging.gitlab.com.
|
85
|
+
MSG
|
86
|
+
else
|
87
|
+
<<~MSG
|
88
|
+
Found: #{failures.size} failures, of these #{triaged_counter} have been triaged with a #{emoji}.
|
89
|
+
MSG
|
90
|
+
end
|
91
|
+
|
92
|
+
print_table(labels, failures, alignments: [:left, :center, :center, :left])
|
93
|
+
output.puts(msg)
|
90
94
|
end
|
91
95
|
end
|
92
96
|
end
|