dri 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8718d907e41f0cfb472a255898ed513af0fe81ae4cfe782c7aa7d270cb0c2b9f
4
- data.tar.gz: 718f976b309b6eb0cab9f65b32e58de5876edd5507aafcc20a2f098068bb5929
3
+ metadata.gz: 9f99fb005dc6cbc12b4fe9b8b131680c09474c125d7fe396fa3b0fadd8804dba
4
+ data.tar.gz: dfa14e8f527bfed0d17a428d81ac16955251cba3ef19907016a19bc43901e768
5
5
  SHA512:
6
- metadata.gz: 643631388b378a127bca182911c9dfb304a1134b568fb71c716976f637f31f2a8d5056c2e30f0f9e2a72e7528e9e7f58a0bda4c0cbc1066efab02c3159353777
7
- data.tar.gz: 46514571671417008fe79dadc26cee33b97ad642e61822e2a2097f4e83d774290d1389f3d7d5c6a8ddf7be7a1974d733b4f5cacef3e7c0084af16a37db30ee0d
6
+ metadata.gz: f7e619cb7241e804810b8cdd97759df84c98aada62c975e0d39fa9f9125219846f5e8bddb50624ce22cc1f836006b023d49375f0a17e298d0f0086d220390e87
7
+ data.tar.gz: '09e94ad8bc08ffb95839e01456703e81f8ed262c7c925beadeb21d71a816f6632b5772b91af5700901331c5e7560af56b1398ed41ac6dc04d884f61b6ab6043b'
data/.gitignore CHANGED
@@ -13,4 +13,5 @@
13
13
 
14
14
  .dri_profile.yml
15
15
  handover_reports/*
16
+ analyze_reports/*
16
17
  .idea/
data/.gitlab-ci.yml CHANGED
@@ -1,10 +1,10 @@
1
1
  .job_base:
2
- image: ruby:2.7
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.3.4
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.2
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.3.1)
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.18.0)
37
- httparty (~> 0.18)
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.9
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. version](#7-version)
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
- Results are organized by environment (production, staging, staging ref and preprod).
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. version
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'
@@ -3,20 +3,28 @@
3
3
  require "httparty"
4
4
  require "json"
5
5
  require "tty-config"
6
- require 'cgi'
7
- require 'gitlab'
6
+ require "cgi"
7
+ require "gitlab"
8
8
 
9
9
  module Dri
10
- class ApiClient
11
- API_URL = 'https://gitlab.com/api/v4'
12
- TESTCASES_PROJECT_ID = '11229385'
13
- TRIAGE_PROJECT_ID = '15291320'
14
- GITLAB_PROJECT_ID = '278964'
15
- FEATURE_FLAG_LOG_PROJECT_ID = '15208716'
16
- INFRA_TEAM_PROD_PROJECT_ID = '7444821'
17
-
18
- def initialize(config)
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
- @gitlab ||= Gitlab.client(
161
- endpoint: API_URL,
162
- private_token: token
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
- author: 2,
17
- url: 3
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
- urgent = []
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
- labels = failure.labels
65
-
66
- labels.each do |label|
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
- if @options[:urgent]
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