dri 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +8 -0
- data/.ruby-version +1 -1
- data/Gemfile.lock +14 -2
- data/README.md +11 -2
- data/dri.gemspec +2 -0
- data/lib/dri/api_client.rb +107 -97
- data/lib/dri/cli.rb +12 -0
- data/lib/dri/command.rb +3 -2
- data/lib/dri/commands/fetch/failures.rb +16 -8
- data/lib/dri/commands/fetch/featureflags.rb +24 -31
- data/lib/dri/commands/fetch/quarantines.rb +2 -2
- data/lib/dri/commands/fetch/testcases.rb +2 -3
- data/lib/dri/commands/fetch.rb +2 -0
- data/lib/dri/commands/incidents.rb +59 -0
- data/lib/dri/commands/publish/report.rb +13 -2
- data/lib/dri/commands/rm/emoji.rb +4 -9
- data/lib/dri/gitlab/issues.rb +19 -0
- data/lib/dri/report.rb +25 -9
- data/lib/dri/templates/incidents/.gitkeep +1 -0
- data/lib/dri/version.rb +1 -1
- metadata +33 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8718d907e41f0cfb472a255898ed513af0fe81ae4cfe782c7aa7d270cb0c2b9f
|
4
|
+
data.tar.gz: 718f976b309b6eb0cab9f65b32e58de5876edd5507aafcc20a2f098068bb5929
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 643631388b378a127bca182911c9dfb304a1134b568fb71c716976f637f31f2a8d5056c2e30f0f9e2a72e7528e9e7f58a0bda4c0cbc1066efab02c3159353777
|
7
|
+
data.tar.gz: 46514571671417008fe79dadc26cee33b97ad642e61822e2a2097f4e83d774290d1389f3d7d5c6a8ddf7be7a1974d733b4f5cacef3e7c0084af16a37db30ee0d
|
data/.gitlab-ci.yml
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.4
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dri (0.1
|
4
|
+
dri (0.3.1)
|
5
|
+
gitlab (~> 4.18)
|
5
6
|
httparty (~> 0.20.0)
|
6
7
|
json (~> 2.6.1)
|
7
8
|
markdown-tables (~> 1.1.1)
|
@@ -27,10 +28,14 @@ GEM
|
|
27
28
|
addressable (2.8.0)
|
28
29
|
public_suffix (>= 2.0.2, < 5.0)
|
29
30
|
ast (2.4.2)
|
31
|
+
coderay (1.1.3)
|
30
32
|
concurrent-ruby (1.1.10)
|
31
33
|
crack (0.4.5)
|
32
34
|
rexml
|
33
35
|
diff-lcs (1.5.0)
|
36
|
+
gitlab (4.18.0)
|
37
|
+
httparty (~> 0.18)
|
38
|
+
terminal-table (>= 1.5.1)
|
34
39
|
gitlab-styles (7.0.0)
|
35
40
|
rubocop (~> 0.91, >= 0.91.1)
|
36
41
|
rubocop-gitlab-security (~> 0.1.1)
|
@@ -44,8 +49,9 @@ GEM
|
|
44
49
|
multi_xml (>= 0.5.2)
|
45
50
|
i18n (1.10.0)
|
46
51
|
concurrent-ruby (~> 1.0)
|
47
|
-
json (2.6.
|
52
|
+
json (2.6.2)
|
48
53
|
markdown-tables (1.1.1)
|
54
|
+
method_source (1.0.0)
|
49
55
|
mime-types (3.4.1)
|
50
56
|
mime-types-data (~> 3.2015)
|
51
57
|
mime-types-data (3.2022.0105)
|
@@ -56,6 +62,9 @@ GEM
|
|
56
62
|
ast (~> 2.4.1)
|
57
63
|
pastel (0.8.0)
|
58
64
|
tty-color (~> 0.5)
|
65
|
+
pry (0.14.1)
|
66
|
+
coderay (~> 1.1)
|
67
|
+
method_source (~> 1.0)
|
59
68
|
public_suffix (4.0.6)
|
60
69
|
rack (2.2.3)
|
61
70
|
rainbow (3.1.1)
|
@@ -106,6 +115,8 @@ GEM
|
|
106
115
|
unicode-display_width (>= 1.5, < 3.0)
|
107
116
|
unicode_utils (~> 1.4)
|
108
117
|
strings-ansi (0.2.0)
|
118
|
+
terminal-table (3.0.2)
|
119
|
+
unicode-display_width (>= 1.1.1, < 3)
|
109
120
|
thor (1.0.1)
|
110
121
|
timecop (0.9.5)
|
111
122
|
tty-box (0.7.0)
|
@@ -150,6 +161,7 @@ PLATFORMS
|
|
150
161
|
DEPENDENCIES
|
151
162
|
dri!
|
152
163
|
gitlab-styles (~> 7.0.0)
|
164
|
+
pry (~> 0.14.1)
|
153
165
|
rake
|
154
166
|
rspec (~> 3.10.0)
|
155
167
|
timecop (~> 0.9.1)
|
data/README.md
CHANGED
@@ -65,7 +65,8 @@ $ dri profile
|
|
65
65
|
- emoji
|
66
66
|
- profile
|
67
67
|
- reports
|
68
|
-
- [6.
|
68
|
+
- [6. incidents](#6-incidents)
|
69
|
+
- [7. version](#7-version)
|
69
70
|
|
70
71
|
#### 1. init
|
71
72
|
|
@@ -181,7 +182,15 @@ $ dri rm profile
|
|
181
182
|
|
182
183
|
Removes the profile currently in use.
|
183
184
|
|
184
|
-
#### 6.
|
185
|
+
#### 6. incidents
|
186
|
+
|
187
|
+
```shell
|
188
|
+
$ dri incidents
|
189
|
+
```
|
190
|
+
|
191
|
+
Have a quick look at currently active/mitigated incidents on GitLab services.
|
192
|
+
|
193
|
+
#### 7. version
|
185
194
|
|
186
195
|
```shell
|
187
196
|
$ 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 "gitlab", "~> 4.18"
|
25
26
|
spec.add_dependency 'httparty', '~> 0.20.0'
|
26
27
|
spec.add_dependency 'json', '~> 2.6.1'
|
27
28
|
spec.add_dependency 'markdown-tables', '~> 1.1.1'
|
@@ -37,6 +38,7 @@ Gem::Specification.new do |spec|
|
|
37
38
|
spec.add_dependency 'tty-table', '~> 0.12.0'
|
38
39
|
|
39
40
|
spec.add_development_dependency 'gitlab-styles', '~> 7.0.0'
|
41
|
+
spec.add_development_dependency "pry", "~> 0.14.1"
|
40
42
|
spec.add_development_dependency 'rake'
|
41
43
|
spec.add_development_dependency 'rspec', '~> 3.10.0'
|
42
44
|
spec.add_development_dependency "timecop", "~> 0.9.1"
|
data/lib/dri/api_client.rb
CHANGED
@@ -4,51 +4,65 @@ require "httparty"
|
|
4
4
|
require "json"
|
5
5
|
require "tty-config"
|
6
6
|
require 'cgi'
|
7
|
+
require 'gitlab'
|
7
8
|
|
8
9
|
module Dri
|
9
|
-
class ApiClient
|
10
|
+
class ApiClient
|
10
11
|
API_URL = 'https://gitlab.com/api/v4'
|
11
12
|
TESTCASES_PROJECT_ID = '11229385'
|
12
13
|
TRIAGE_PROJECT_ID = '15291320'
|
13
14
|
GITLAB_PROJECT_ID = '278964'
|
14
15
|
FEATURE_FLAG_LOG_PROJECT_ID = '15208716'
|
16
|
+
INFRA_TEAM_PROD_PROJECT_ID = '7444821'
|
15
17
|
|
16
18
|
def initialize(config)
|
17
|
-
|
18
|
-
@token = profile["settings"]["token"]
|
19
|
-
end
|
20
|
-
|
21
|
-
def header
|
22
|
-
@header ||= { 'Content-Type' => 'application/json', "Authorization" => "Bearer #{@token}" }
|
19
|
+
@token = config.read.dig("settings", "token")
|
23
20
|
end
|
24
21
|
|
22
|
+
# Fetch triaged failures
|
23
|
+
#
|
24
|
+
# @param [String] emoji
|
25
|
+
# @param [String] state
|
26
|
+
# @return [Gitlab::ObjectifiedHash]
|
25
27
|
def fetch_triaged_failures(emoji:, state:)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
gitlab.issues(
|
29
|
+
GITLAB_PROJECT_ID,
|
30
|
+
order_by: "updated_at",
|
31
|
+
my_reaction_emoji: emoji,
|
32
|
+
scope: "all",
|
33
|
+
state: state
|
34
|
+
)
|
31
35
|
end
|
32
36
|
|
33
|
-
|
34
|
-
|
37
|
+
# Fetch award emojis
|
38
|
+
#
|
39
|
+
# @param [Integer] issue_iid
|
40
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
41
|
+
def fetch_awarded_emojis(issue_iid)
|
42
|
+
gitlab.award_emojis(GITLAB_PROJECT_ID, issue_iid, "issue")
|
35
43
|
end
|
36
44
|
|
45
|
+
# Fetch failing testcases
|
46
|
+
#
|
47
|
+
# @param [String] pipeline
|
48
|
+
# @param [String] state
|
49
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
37
50
|
def fetch_failing_testcases(pipeline, state:)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
51
|
+
gitlab.issues(
|
52
|
+
TESTCASES_PROJECT_ID,
|
53
|
+
labels: "#{pipeline}::failed",
|
54
|
+
state: state,
|
55
|
+
scope: "all",
|
56
|
+
"not[labels]": "quarantine"
|
57
|
+
).auto_paginate
|
44
58
|
end
|
45
59
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
60
|
+
# Fetch related issue mrs
|
61
|
+
#
|
62
|
+
# @param [Integer] issue_iid
|
63
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
64
|
+
def fetch_related_mrs(issue_iid)
|
65
|
+
gitlab.related_issue_merge_requests(GITLAB_PROJECT_ID, issue_iid)
|
52
66
|
end
|
53
67
|
|
54
68
|
# Fetch MRs
|
@@ -62,95 +76,91 @@ module Dri
|
|
62
76
|
# @option options [String] milestone
|
63
77
|
# @option options [String] labels
|
64
78
|
def fetch_mrs(project_id:, **options)
|
65
|
-
|
66
|
-
uri.query = HTTParty::HashConversions.to_params(options)
|
67
|
-
|
68
|
-
# CGI.escape('gitlab-org/gitlab') => 'gitlab-org%2Fgitlab'
|
69
|
-
uri.path = File.join(uri.path, 'projects', CGI.escape(project_id), 'merge_requests')
|
70
|
-
|
71
|
-
fetch_json(uri.to_s)
|
79
|
+
gitlab.merge_requests(project_id, per_page: 100, **options).auto_paginate
|
72
80
|
end
|
73
81
|
|
82
|
+
# Fetch current triage issue
|
83
|
+
#
|
84
|
+
# @return [Gitlab::ObjectifiedHash]
|
74
85
|
def fetch_current_triage_issue
|
75
|
-
|
76
|
-
url << "#{TRIAGE_PROJECT_ID}/issues?state=opened"
|
77
|
-
url << "&order_by=updated_at"
|
78
|
-
|
79
|
-
fetch_json(url.join)
|
86
|
+
gitlab.issues(TRIAGE_PROJECT_ID, state: "opened", order_by: "updated_at")
|
80
87
|
end
|
81
88
|
|
89
|
+
# Create triage report note
|
90
|
+
#
|
91
|
+
# @param [Integer] iid
|
92
|
+
# @param [String] body
|
93
|
+
# @return [Gitlab::ObjectifiedHash]
|
82
94
|
def post_triage_report_note(iid:, body:)
|
83
|
-
|
84
|
-
url << "#{TRIAGE_PROJECT_ID}/issues/#{iid}/notes"
|
85
|
-
|
86
|
-
post_json(url.join, body)
|
95
|
+
gitlab.create_issue_note(TRIAGE_PROJECT_ID, iid, body)
|
87
96
|
end
|
88
97
|
|
98
|
+
# Fetch new failures
|
99
|
+
#
|
100
|
+
# @param [String] date
|
101
|
+
# @param [String] state
|
102
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
89
103
|
def fetch_failures(date:, state:)
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
104
|
+
gitlab.issues(
|
105
|
+
GITLAB_PROJECT_ID,
|
106
|
+
labels: "failure::new",
|
107
|
+
order_by: "updated_at",
|
108
|
+
state: state,
|
109
|
+
scope: "all",
|
110
|
+
created_after: date,
|
111
|
+
per_page: 100
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Fetch failure notes
|
116
|
+
#
|
117
|
+
# @param [Integer] issue_iid
|
118
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
119
|
+
def fetch_failure_notes(issue_iid)
|
120
|
+
gitlab.issue_notes(GITLAB_PROJECT_ID, issue_iid, per_page: 100).auto_paginate
|
105
121
|
end
|
106
122
|
|
107
|
-
|
108
|
-
|
123
|
+
# Delete award emoji
|
124
|
+
#
|
125
|
+
# @param [Integer] issue_iid
|
126
|
+
# @param [Integer] emoji_id
|
127
|
+
# @return [Gitlab::ObjectifiedHash]
|
128
|
+
def delete_award_emoji(issue_iid, emoji_id)
|
129
|
+
gitlab.delete_award_emoji(
|
130
|
+
GITLAB_PROJECT_ID,
|
131
|
+
issue_iid,
|
132
|
+
"issue",
|
133
|
+
emoji_id
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Fetch feature flag log issues
|
138
|
+
#
|
139
|
+
# @param [String] date
|
140
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
141
|
+
def fetch_feature_flag_logs(date)
|
142
|
+
gitlab.issues(FEATURE_FLAG_LOG_PROJECT_ID, created_after: date, per_page: 100).auto_paginate
|
109
143
|
end
|
110
144
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
url << "&page=#{page}"
|
117
|
-
|
118
|
-
fetch_json(url.join)
|
145
|
+
# Fetch ongoing incidents
|
146
|
+
#
|
147
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
148
|
+
def incidents
|
149
|
+
gitlab.issues(INFRA_TEAM_PROD_PROJECT_ID, order_by: "updated_at", state: "opened", labels: "incident")
|
119
150
|
end
|
120
151
|
|
121
152
|
private
|
122
153
|
|
123
|
-
|
124
|
-
options = {
|
125
|
-
body: { body: body },
|
126
|
-
headers: {
|
127
|
-
"Authorization" => "Bearer #{@token}"
|
128
|
-
}
|
129
|
-
}
|
154
|
+
attr_reader :token
|
130
155
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
def handle_response(response)
|
141
|
-
# puts response.to_json
|
142
|
-
response
|
143
|
-
end
|
144
|
-
|
145
|
-
def fetch_json(url)
|
146
|
-
response = HTTParty.get(url, headers: header)
|
147
|
-
|
148
|
-
if response.code != 200
|
149
|
-
puts "Response error code \"#{response.code}\" - Unable to sync with GitLab"
|
150
|
-
exit 1
|
151
|
-
end
|
152
|
-
|
153
|
-
handle_response(JSON.parse(response.body))
|
156
|
+
# Gitlab client
|
157
|
+
#
|
158
|
+
# @return [Gitlab::Client]
|
159
|
+
def gitlab
|
160
|
+
@gitlab ||= Gitlab.client(
|
161
|
+
endpoint: API_URL,
|
162
|
+
private_token: token
|
163
|
+
)
|
154
164
|
end
|
155
165
|
end
|
156
166
|
end
|
data/lib/dri/cli.rb
CHANGED
@@ -34,6 +34,18 @@ module Dri
|
|
34
34
|
end
|
35
35
|
map %w[--version -v] => :version
|
36
36
|
|
37
|
+
desc 'incidents', 'View current incidents'
|
38
|
+
method_option :help, aliases: '-h', type: :boolean,
|
39
|
+
desc: 'Display usage information'
|
40
|
+
def incidents(*)
|
41
|
+
if options[:help]
|
42
|
+
invoke :help, ['incidents']
|
43
|
+
else
|
44
|
+
require_relative 'commands/incidents'
|
45
|
+
Dri::Commands::Incidents.new(options).execute
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
37
49
|
require_relative 'commands/rm'
|
38
50
|
register Dri::Commands::Rm, 'rm', 'rm [SUBCOMMAND]', 'Remove triage-related items'
|
39
51
|
|
data/lib/dri/command.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'command'
|
4
4
|
require_relative 'api_client'
|
5
|
+
require_relative 'gitlab/issues'
|
5
6
|
|
6
7
|
require 'dri/refinements/truncate'
|
7
8
|
|
@@ -93,7 +94,7 @@ module Dri
|
|
93
94
|
# @api public
|
94
95
|
def command(**options)
|
95
96
|
require 'tty-command'
|
96
|
-
TTY::Command.new(options)
|
97
|
+
TTY::Command.new(**options)
|
97
98
|
end
|
98
99
|
|
99
100
|
# The cursor movement
|
@@ -123,7 +124,7 @@ module Dri
|
|
123
124
|
# @api public
|
124
125
|
def prompt(**options)
|
125
126
|
require 'tty-prompt'
|
126
|
-
TTY::Prompt.new(options)
|
127
|
+
TTY::Prompt.new(**options.merge(interrupt: :exit))
|
127
128
|
end
|
128
129
|
end
|
129
130
|
end
|
@@ -10,6 +10,13 @@ module Dri
|
|
10
10
|
include Dri::Utils::Table
|
11
11
|
using Refinements
|
12
12
|
|
13
|
+
SORT_BY_OPTIONS = {
|
14
|
+
title: 0,
|
15
|
+
triaged: 1,
|
16
|
+
author: 2,
|
17
|
+
url: 3
|
18
|
+
}.freeze
|
19
|
+
|
13
20
|
def initialize(options)
|
14
21
|
@options = options
|
15
22
|
@today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
|
@@ -33,20 +40,19 @@ module Dri
|
|
33
40
|
spinner.run do # rubocop:disable Metrics/BlockLength
|
34
41
|
response = api_client.fetch_failures(date: @today_iso_format, state: 'opened')
|
35
42
|
|
36
|
-
if response.
|
43
|
+
if response.empty?
|
37
44
|
logger.info 'Life is great, there are no new failures today!'
|
38
45
|
exit 0
|
39
46
|
end
|
40
47
|
|
41
48
|
response.each do |failure|
|
42
|
-
title = failure
|
43
|
-
author = failure
|
44
|
-
url = failure
|
45
|
-
award_emoji_url = failure['_links']['award_emoji']
|
49
|
+
title = failure.title.truncate(60)
|
50
|
+
author = failure.to_h.dig('author', 'username')
|
51
|
+
url = failure.web_url
|
46
52
|
triaged = add_color('x', :red)
|
47
53
|
|
48
|
-
emoji_awards = api_client.fetch_awarded_emojis(
|
49
|
-
|
54
|
+
emoji_awards = api_client.fetch_awarded_emojis(failure.iid).find do |e|
|
55
|
+
e.name == emoji && e.to_h.dig('user', 'username') == username
|
50
56
|
end
|
51
57
|
|
52
58
|
if emoji_awards
|
@@ -55,7 +61,7 @@ module Dri
|
|
55
61
|
end
|
56
62
|
|
57
63
|
if @options[:urgent]
|
58
|
-
labels = failure
|
64
|
+
labels = failure.labels
|
59
65
|
|
60
66
|
labels.each do |label|
|
61
67
|
if label.include?('found:canary.gitlab.com' && 'found:canary.staging.gitlab.com')
|
@@ -65,6 +71,8 @@ module Dri
|
|
65
71
|
end
|
66
72
|
|
67
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]
|
68
76
|
end
|
69
77
|
end
|
70
78
|
|
@@ -35,44 +35,37 @@ module Dri
|
|
35
35
|
|
36
36
|
logger.info "Fetching today's feature flag changes..."
|
37
37
|
|
38
|
-
spinner.run do
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
logger.info 'It\'s been quiet...no feature flag changes for today 👀'
|
45
|
-
exit 0
|
46
|
-
end
|
38
|
+
spinner.run do
|
39
|
+
response = api_client.fetch_feature_flag_logs(@today_iso_format)
|
40
|
+
if response.empty?
|
41
|
+
logger.info 'It\'s been quiet...no feature flag changes for today 👀'
|
42
|
+
break
|
43
|
+
end
|
47
44
|
|
48
|
-
|
49
|
-
|
45
|
+
response.each do |feature_flag|
|
46
|
+
summary = feature_flag.title
|
50
47
|
|
51
|
-
|
52
|
-
|
48
|
+
substrings = ["set to \"true\"", "set to \"false\""]
|
49
|
+
next unless substrings.any? { |substr| summary.include?(substr) }
|
53
50
|
|
54
|
-
|
55
|
-
|
51
|
+
changed_on = feature_flag.description[/(?<=Changed on \(in UTC\): ).+?(?=\n)/].delete('`')
|
52
|
+
url = feature_flag.web_url
|
56
53
|
|
57
|
-
|
54
|
+
feature_flag_data = [summary, changed_on, url]
|
58
55
|
|
59
|
-
|
60
|
-
|
56
|
+
labels = feature_flag.labels
|
57
|
+
host_label = labels.select { |label| /^host::/.match(label) }.join('')
|
61
58
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
59
|
+
case host_label
|
60
|
+
when PRODUCTION
|
61
|
+
prod_feature_flags << feature_flag_data
|
62
|
+
when STAGING
|
63
|
+
staging_feature_flags << feature_flag_data
|
64
|
+
when STAGING_REF
|
65
|
+
staging_ref_feature_flags << feature_flag_data
|
66
|
+
when PREPROD
|
67
|
+
preprod_feature_flags << feature_flag_data
|
72
68
|
end
|
73
|
-
|
74
|
-
page += 1
|
75
|
-
break if response.count < 100 || response.nil?
|
76
69
|
end
|
77
70
|
end
|
78
71
|
|
@@ -37,8 +37,8 @@ module Dri
|
|
37
37
|
)
|
38
38
|
|
39
39
|
mrs = response.each_with_object([]) do |mr, found_mrs|
|
40
|
-
title = mr
|
41
|
-
url = mr
|
40
|
+
title = mr.title[13..].truncate(60) # remove the "[QUARANTINE] " prefix
|
41
|
+
url = mr.web_url
|
42
42
|
|
43
43
|
found_mrs << [title, url]
|
44
44
|
end
|
@@ -17,7 +17,6 @@ module Dri
|
|
17
17
|
nightly
|
18
18
|
production
|
19
19
|
staging-canary
|
20
|
-
staging-orchestrated
|
21
20
|
staging-ref
|
22
21
|
staging
|
23
22
|
]
|
@@ -48,8 +47,8 @@ module Dri
|
|
48
47
|
|
49
48
|
output.puts "♦♦♦♦♦ #{add_color(pipeline, :black, :on_white)}♦♦♦♦♦\n\n"
|
50
49
|
|
51
|
-
response.each do |
|
52
|
-
output.puts "#{title_label} #{
|
50
|
+
response.each do |test_case|
|
51
|
+
output.puts "#{title_label} #{test_case.title}\n#{url_label} #{test_case.web_url}"
|
53
52
|
output.puts "#{divider}\n"
|
54
53
|
end
|
55
54
|
end
|
data/lib/dri/commands/fetch.rb
CHANGED
@@ -50,6 +50,8 @@ module Dri
|
|
50
50
|
desc: 'Display usage information'
|
51
51
|
method_option :urgent, type: :boolean,
|
52
52
|
desc: 'Shows failures that quickly escalated'
|
53
|
+
method_option :sort_by, type: :string,
|
54
|
+
desc: 'Shows failures in specified order'
|
53
55
|
def failures(*)
|
54
56
|
if options[:help]
|
55
57
|
invoke :help, ['failures']
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
require_relative '../utils/table'
|
5
|
+
|
6
|
+
module Dri
|
7
|
+
module Commands
|
8
|
+
class Incidents < Dri::Command
|
9
|
+
include Dri::Utils::Table
|
10
|
+
using Refinements
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize
|
17
|
+
verify_config_exists
|
18
|
+
|
19
|
+
incident = add_color('Incident', :bright_yellow)
|
20
|
+
service = add_color('Service', :bright_yellow)
|
21
|
+
status = add_color('Status', :bright_yellow)
|
22
|
+
url = add_color('URL', :bright_yellow)
|
23
|
+
|
24
|
+
header = [incident, service, status, url]
|
25
|
+
|
26
|
+
logger.info "Looking for open incidents..."
|
27
|
+
incidents = []
|
28
|
+
|
29
|
+
spinner.run do
|
30
|
+
response = api_client.incidents
|
31
|
+
|
32
|
+
if response.nil?
|
33
|
+
logger.info 'Hooray, no active incidents 🎉.'
|
34
|
+
break
|
35
|
+
end
|
36
|
+
|
37
|
+
response.each do |incident|
|
38
|
+
title = incident.title.truncate(70)
|
39
|
+
url = incident.web_url
|
40
|
+
labels = incident.labels
|
41
|
+
status = "N/A"
|
42
|
+
service = "N/A"
|
43
|
+
|
44
|
+
labels.each do |label|
|
45
|
+
status = label.gsub!('Incident::', ' ').to_s if label.include? "Incident::"
|
46
|
+
service = label.gsub!('Service::', ' ').to_s if label.include? "Service::"
|
47
|
+
end
|
48
|
+
|
49
|
+
incidents << [title, service, status, url]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
print_table(header, incidents, alignments: [:left, :center, :center, :center])
|
53
|
+
output.puts(<<~MSG)
|
54
|
+
Found: #{incidents.size} incident(s).
|
55
|
+
MSG
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -36,7 +36,17 @@ module Dri
|
|
36
36
|
|
37
37
|
logger.info "Assembling the report... "
|
38
38
|
# sets each failure on the table
|
39
|
-
action_options = [
|
39
|
+
action_options = [
|
40
|
+
"pinged SET",
|
41
|
+
"reproduced",
|
42
|
+
"transient",
|
43
|
+
"quarantined",
|
44
|
+
"active investigation",
|
45
|
+
"blocking pipelines",
|
46
|
+
"awaiting for a fix to merge",
|
47
|
+
"notified the team",
|
48
|
+
"due to feature flag"
|
49
|
+
]
|
40
50
|
|
41
51
|
spinner.start
|
42
52
|
issues.each do |issue|
|
@@ -45,7 +55,8 @@ module Dri
|
|
45
55
|
if @options[:actions]
|
46
56
|
actions = prompt.multi_select(
|
47
57
|
"Please mark the actions on #{add_color(issue['title'], :yellow)}: ",
|
48
|
-
action_options
|
58
|
+
action_options,
|
59
|
+
per_page: 9
|
49
60
|
)
|
50
61
|
end
|
51
62
|
|
@@ -29,18 +29,13 @@ module Dri
|
|
29
29
|
spinner.stop
|
30
30
|
|
31
31
|
issues_with_award_emoji.each do |issue|
|
32
|
-
logger.info "Removing #{emoji} from #{issue
|
32
|
+
logger.info "Removing #{emoji} from #{issue.web_url}..."
|
33
33
|
|
34
|
-
|
34
|
+
response = api_client.fetch_awarded_emojis(issue.iid)
|
35
35
|
|
36
|
-
|
36
|
+
emoji_found = response.find { |e| e.name == emoji && e.to_h.dig('user', 'username') == username }
|
37
37
|
|
38
|
-
emoji_found
|
39
|
-
|
40
|
-
unless emoji_found.nil?
|
41
|
-
url = "#{award_emoji_url}/#{emoji_found['id']}"
|
42
|
-
api_client.delete_award_emoji(url)
|
43
|
-
end
|
38
|
+
api_client.delete_award_emoji(issue.iid, emoji_found.id) unless emoji_found.nil?
|
44
39
|
end
|
45
40
|
output.puts "Done! ✅"
|
46
41
|
logger.success "Removed #{emoji} from #{issues_with_award_emoji.size} issue(s)."
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Gitlab gem monkeypatch for related merge requests
|
4
|
+
# TODO: Add method upstream
|
5
|
+
#
|
6
|
+
class Gitlab::Client
|
7
|
+
module Issues
|
8
|
+
# List related merge requests
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# Gitlab.related_issue_merge_requests(3, 42)
|
12
|
+
#
|
13
|
+
# @param [Integer, String] project The ID or name of a project.
|
14
|
+
# @param [Integer] id The ID of an issue.
|
15
|
+
def related_issue_merge_requests(project, id)
|
16
|
+
get("/projects/#{url_encode project}/issues/#{id}/related_merge_requests")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/dri/report.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module Dri
|
4
4
|
class Report # rubocop:disable Metrics/ClassLength
|
5
|
+
using Refinements
|
6
|
+
|
5
7
|
attr_reader :header, :failures, :labels
|
6
8
|
|
7
9
|
def initialize(config)
|
@@ -28,7 +30,7 @@ module Dri
|
|
28
30
|
assignees = failure["assignees"]
|
29
31
|
description = failure["description"]
|
30
32
|
|
31
|
-
related_mrs = @api_client.fetch_related_mrs(
|
33
|
+
related_mrs = @api_client.fetch_related_mrs(iid)
|
32
34
|
emoji = classify_failure_emoji(created_at)
|
33
35
|
emojified_link = "#{emoji} #{link}"
|
34
36
|
|
@@ -59,7 +61,7 @@ module Dri
|
|
59
61
|
label_pipeline_map = {
|
60
62
|
'gitlab.com' => '/quality/production',
|
61
63
|
'canary.gitlab.com' => '/quality/canary',
|
62
|
-
|
64
|
+
'canary.staging.gitlab.com' => '/quality/staging-canary',
|
63
65
|
'main' => '/gitlab-org/gitlab-qa-mirror',
|
64
66
|
'master' => '/gitlab-org/gitlab-qa-mirror',
|
65
67
|
'nightly' => '/quality/nightly',
|
@@ -69,7 +71,7 @@ module Dri
|
|
69
71
|
'release' => '/quality/release'
|
70
72
|
}
|
71
73
|
|
72
|
-
failure_notes = @api_client.fetch_failure_notes(
|
74
|
+
failure_notes = @api_client.fetch_failure_notes(iid)
|
73
75
|
|
74
76
|
return if pipelines.empty?
|
75
77
|
|
@@ -81,10 +83,10 @@ module Dri
|
|
81
83
|
pipeline_markdown = pipeline.gsub(/.gitlab.com/, '')
|
82
84
|
|
83
85
|
failure_notes.each do |note|
|
84
|
-
next unless note
|
86
|
+
next unless note.body.include?(label_pipeline_map.fetch(pipeline))
|
85
87
|
|
86
88
|
pipeline_in_notes_found = true
|
87
|
-
pipeline_link = URI.extract(note
|
89
|
+
pipeline_link = URI.extract(note.body, %w[https])
|
88
90
|
end
|
89
91
|
|
90
92
|
unless pipeline_in_notes_found
|
@@ -102,16 +104,25 @@ module Dri
|
|
102
104
|
linked.join(', ')
|
103
105
|
end
|
104
106
|
|
105
|
-
def actions_status_template(failure_type, assigned_status, actions_opts)
|
106
|
-
notified_set = ''
|
107
|
+
def actions_status_template(failure_type, assigned_status, actions_opts) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity/MethodLength
|
107
108
|
quarantined = ''
|
108
109
|
reproduced = ''
|
109
110
|
transient = ''
|
111
|
+
active_investigation = ''
|
112
|
+
blocking_pipelines = ''
|
113
|
+
wait_for_fix = ''
|
114
|
+
notified_team = ''
|
115
|
+
feature_flag = ''
|
110
116
|
|
111
117
|
notified_set = '<li>[x] notified SET</li>' if actions_opts.include? 'pinged SET'
|
112
118
|
quarantined = '<li>[x] quarantined</li>' if actions_opts.include? 'quarantined'
|
113
119
|
reproduced = '<li>[x] reproduced</li>' if actions_opts.include? 'reproduced'
|
114
120
|
transient = '<li>[x] transient</li>' if actions_opts.include? 'transient'
|
121
|
+
active_investigation = '<li>[x] active investigation</li>' if actions_opts.include? 'active investigation'
|
122
|
+
blocking_pipelines = '<li>[x] is blocking pipelines</li>' if actions_opts.include? 'blocking pipelines'
|
123
|
+
wait_for_fix = '<li>[x] waiting for fix to merge</li>' if actions_opts.include? 'awaiting for a fix to merge'
|
124
|
+
notified_team = '<li>[x] notified the team</li>' if actions_opts.include? 'notified the team'
|
125
|
+
feature_flag = '<li>[x] due to feature flag</li>' if actions_opts.include? 'due to feature flag'
|
115
126
|
|
116
127
|
action_status = ["<i>Status:</i><ul>"]
|
117
128
|
action_status << "<li>#{failure_type}</li>"
|
@@ -120,13 +131,18 @@ module Dri
|
|
120
131
|
action_status << quarantined
|
121
132
|
action_status << reproduced
|
122
133
|
action_status << transient
|
134
|
+
action_status << active_investigation
|
135
|
+
action_status << blocking_pipelines
|
136
|
+
action_status << wait_for_fix
|
137
|
+
action_status << notified_team
|
138
|
+
action_status << feature_flag
|
123
139
|
|
124
140
|
action_status.join
|
125
141
|
end
|
126
142
|
|
127
143
|
def actions_fixes_template(related_mrs)
|
128
144
|
mrs = related_mrs.each_with_object(['<i>Potential fixes:</i><br>']) do |mr, fixes|
|
129
|
-
fixes << "<li>[#{mr['title']}](#{mr['web_url']})</li>"
|
145
|
+
fixes << "<li>[#{mr['title'].truncate(40)}](#{mr['web_url']})</li>"
|
130
146
|
end
|
131
147
|
|
132
148
|
"<ul>#{mrs.join}</ul>"
|
@@ -177,7 +193,7 @@ module Dri
|
|
177
193
|
path = "Failure in `#{path.strip}`" if path.delete_prefix!('Failure in ')
|
178
194
|
path = path.strip.gsub(' ', ' ') if desc
|
179
195
|
|
180
|
-
|
196
|
+
[path, desc].compact.join(' | ')
|
181
197
|
end
|
182
198
|
end
|
183
199
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
#
|
data/lib/dri/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dri
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.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: 2022-
|
11
|
+
date: 2022-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gitlab
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.18'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.18'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: httparty
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +220,20 @@ dependencies:
|
|
206
220
|
- - "~>"
|
207
221
|
- !ruby/object:Gem::Version
|
208
222
|
version: 7.0.0
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: pry
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - "~>"
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: 0.14.1
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - "~>"
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: 0.14.1
|
209
237
|
- !ruby/object:Gem::Dependency
|
210
238
|
name: rake
|
211
239
|
requirement: !ruby/object:Gem::Requirement
|
@@ -296,6 +324,7 @@ files:
|
|
296
324
|
- lib/dri/commands/fetch/quarantines.rb
|
297
325
|
- lib/dri/commands/fetch/testcases.rb
|
298
326
|
- lib/dri/commands/fetch/triaged.rb
|
327
|
+
- lib/dri/commands/incidents.rb
|
299
328
|
- lib/dri/commands/init.rb
|
300
329
|
- lib/dri/commands/profile.rb
|
301
330
|
- lib/dri/commands/publish.rb
|
@@ -304,8 +333,10 @@ files:
|
|
304
333
|
- lib/dri/commands/rm/emoji.rb
|
305
334
|
- lib/dri/commands/rm/profile.rb
|
306
335
|
- lib/dri/commands/rm/reports.rb
|
336
|
+
- lib/dri/gitlab/issues.rb
|
307
337
|
- lib/dri/refinements/truncate.rb
|
308
338
|
- lib/dri/report.rb
|
339
|
+
- lib/dri/templates/incidents/.gitkeep
|
309
340
|
- lib/dri/utils/markdown_lists.rb
|
310
341
|
- lib/dri/utils/table.rb
|
311
342
|
- lib/dri/version.rb
|