dri 0.2.0 → 0.4.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/.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
|