dri 0.3.0 → 0.5.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 +1 -0
- data/dri.gemspec +2 -0
- data/lib/dri/api_client.rb +105 -105
- data/lib/dri/command.rb +4 -3
- data/lib/dri/commands/fetch/failures.rb +7 -8
- data/lib/dri/commands/fetch/featureflags.rb +18 -49
- data/lib/dri/commands/fetch/quarantines.rb +2 -2
- data/lib/dri/commands/fetch/testcases.rb +2 -2
- data/lib/dri/commands/incidents.rb +4 -4
- data/lib/dri/commands/publish/report.rb +91 -17
- data/lib/dri/commands/publish.rb +2 -0
- data/lib/dri/commands/rm/emoji.rb +4 -9
- data/lib/dri/feature_flag_report.rb +47 -0
- data/lib/dri/gitlab/issues.rb +19 -0
- data/lib/dri/report.rb +7 -5
- data/lib/dri/utils/feature_flag_consts.rb +13 -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: 879c4292254a2a17a0ebcd8e2c0a3f8bbc6e654a69c153ad66423c71eb750fc4
|
|
4
|
+
data.tar.gz: 51f8e1acec1894ce4a6ab264ef690cf063259f46512d743609bf2f5a6529132f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad020b17eb8d05322800332e59b5d286dcb8712df055dab50be7f234df2864a5ba719f900d61b0ba25308a1ea130fa2b586b7f01215beec90eafb614cc8c6da1
|
|
7
|
+
data.tar.gz: c0ef20575dcc1f9fa3312df5970b79a8b6fa11b8401a1a80d30561c8a764c7bcd140d4e649c619fbce1be6b9f71c943fb51aa5a48c20b18c5f480af4a821bc07
|
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.
|
|
4
|
+
dri (0.4.0)
|
|
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
|
@@ -145,6 +145,7 @@ $ dri publish report --format=list # formats the report in a list
|
|
|
145
145
|
$ dri publish report --format=table # formats the report in a table (default)
|
|
146
146
|
$ dri publish report --dry-run # the report is only generated locally
|
|
147
147
|
$ dri publish report --actions # activate the actions prompt for each failure
|
|
148
|
+
$ dri publish report --feature-flags # includes a summary of the feature flag changes on each environment
|
|
148
149
|
```
|
|
149
150
|
|
|
150
151
|
**Note:** These options above can be combined like:
|
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,9 +4,10 @@ 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'
|
|
@@ -15,41 +16,53 @@ module Dri
|
|
|
15
16
|
INFRA_TEAM_PROD_PROJECT_ID = '7444821'
|
|
16
17
|
|
|
17
18
|
def initialize(config)
|
|
18
|
-
|
|
19
|
-
@token = profile["settings"]["token"]
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def header
|
|
23
|
-
@header ||= { 'Content-Type' => 'application/json', "Authorization" => "Bearer #{@token}" }
|
|
19
|
+
@token = config.read.dig("settings", "token")
|
|
24
20
|
end
|
|
25
21
|
|
|
22
|
+
# Fetch triaged failures
|
|
23
|
+
#
|
|
24
|
+
# @param [String] emoji
|
|
25
|
+
# @param [String] state
|
|
26
|
+
# @return [Gitlab::ObjectifiedHash]
|
|
26
27
|
def fetch_triaged_failures(emoji:, state:)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
gitlab.issues(
|
|
29
|
+
GITLAB_PROJECT_ID,
|
|
30
|
+
order_by: "updated_at",
|
|
31
|
+
my_reaction_emoji: emoji,
|
|
32
|
+
scope: "all",
|
|
33
|
+
state: state
|
|
34
|
+
)
|
|
32
35
|
end
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
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")
|
|
36
43
|
end
|
|
37
44
|
|
|
45
|
+
# Fetch failing testcases
|
|
46
|
+
#
|
|
47
|
+
# @param [String] pipeline
|
|
48
|
+
# @param [String] state
|
|
49
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
|
38
50
|
def fetch_failing_testcases(pipeline, state:)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
gitlab.issues(
|
|
52
|
+
TESTCASES_PROJECT_ID,
|
|
53
|
+
labels: "#{pipeline}::failed",
|
|
54
|
+
state: state,
|
|
55
|
+
scope: "all",
|
|
56
|
+
"not[labels]": "quarantine"
|
|
57
|
+
).auto_paginate
|
|
45
58
|
end
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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)
|
|
53
66
|
end
|
|
54
67
|
|
|
55
68
|
# Fetch MRs
|
|
@@ -63,104 +76,91 @@ module Dri
|
|
|
63
76
|
# @option options [String] milestone
|
|
64
77
|
# @option options [String] labels
|
|
65
78
|
def fetch_mrs(project_id:, **options)
|
|
66
|
-
|
|
67
|
-
uri.query = HTTParty::HashConversions.to_params(options)
|
|
68
|
-
|
|
69
|
-
# CGI.escape('gitlab-org/gitlab') => 'gitlab-org%2Fgitlab'
|
|
70
|
-
uri.path = File.join(uri.path, 'projects', CGI.escape(project_id), 'merge_requests')
|
|
71
|
-
|
|
72
|
-
fetch_json(uri.to_s)
|
|
79
|
+
gitlab.merge_requests(project_id, per_page: 100, **options).auto_paginate
|
|
73
80
|
end
|
|
74
81
|
|
|
82
|
+
# Fetch current triage issue
|
|
83
|
+
#
|
|
84
|
+
# @return [Gitlab::ObjectifiedHash]
|
|
75
85
|
def fetch_current_triage_issue
|
|
76
|
-
|
|
77
|
-
url << "#{TRIAGE_PROJECT_ID}/issues?state=opened"
|
|
78
|
-
url << "&order_by=updated_at"
|
|
79
|
-
|
|
80
|
-
fetch_json(url.join)
|
|
86
|
+
gitlab.issues(TRIAGE_PROJECT_ID, state: "opened", order_by: "updated_at")
|
|
81
87
|
end
|
|
82
88
|
|
|
89
|
+
# Create triage report note
|
|
90
|
+
#
|
|
91
|
+
# @param [Integer] iid
|
|
92
|
+
# @param [String] body
|
|
93
|
+
# @return [Gitlab::ObjectifiedHash]
|
|
83
94
|
def post_triage_report_note(iid:, body:)
|
|
84
|
-
|
|
85
|
-
url << "#{TRIAGE_PROJECT_ID}/issues/#{iid}/notes"
|
|
86
|
-
|
|
87
|
-
post_json(url.join, body)
|
|
95
|
+
gitlab.create_issue_note(TRIAGE_PROJECT_ID, iid, body)
|
|
88
96
|
end
|
|
89
97
|
|
|
98
|
+
# Fetch new failures
|
|
99
|
+
#
|
|
100
|
+
# @param [String] date
|
|
101
|
+
# @param [String] state
|
|
102
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
|
90
103
|
def fetch_failures(date:, state:)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def delete_award_emoji(url)
|
|
110
|
-
delete_json(url)
|
|
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
|
|
111
121
|
end
|
|
112
122
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
121
143
|
end
|
|
122
144
|
|
|
145
|
+
# Fetch ongoing incidents
|
|
146
|
+
#
|
|
147
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
|
123
148
|
def incidents
|
|
124
|
-
|
|
125
|
-
url << "#{INFRA_TEAM_PROD_PROJECT_ID}/issues"
|
|
126
|
-
url << "?order_by=updated_at&state=opened"
|
|
127
|
-
url << "&labels=incident"
|
|
128
|
-
|
|
129
|
-
fetch_json(url.join)
|
|
149
|
+
gitlab.issues(INFRA_TEAM_PROD_PROJECT_ID, order_by: "updated_at", state: "opened", labels: "incident")
|
|
130
150
|
end
|
|
131
151
|
|
|
132
152
|
private
|
|
133
153
|
|
|
134
|
-
|
|
135
|
-
options = {
|
|
136
|
-
body: { body: body },
|
|
137
|
-
headers: {
|
|
138
|
-
"Authorization" => "Bearer #{@token}"
|
|
139
|
-
}
|
|
140
|
-
}
|
|
154
|
+
attr_reader :token
|
|
141
155
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def handle_response(response)
|
|
152
|
-
response
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def fetch_json(url)
|
|
156
|
-
response = HTTParty.get(url, headers: header)
|
|
157
|
-
|
|
158
|
-
if response.code != 200
|
|
159
|
-
puts "Response error code \"#{response.code}\" - Unable to sync with GitLab"
|
|
160
|
-
exit 1
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
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
|
+
)
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
end
|
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
|
|
|
@@ -25,8 +26,8 @@ module Dri
|
|
|
25
26
|
config = TTY::Config.new
|
|
26
27
|
config.filename = ".dri_profile"
|
|
27
28
|
config.extname = ".yml"
|
|
28
|
-
config.append_path Dir.pwd
|
|
29
29
|
config.append_path Dir.home
|
|
30
|
+
config.append_path Dir.pwd
|
|
30
31
|
config
|
|
31
32
|
end
|
|
32
33
|
end
|
|
@@ -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.merge(interrupt: :exit))
|
|
127
|
+
TTY::Prompt.new(**options.merge(interrupt: :exit))
|
|
127
128
|
end
|
|
128
129
|
end
|
|
129
130
|
end
|
|
@@ -40,20 +40,19 @@ module Dri
|
|
|
40
40
|
spinner.run do # rubocop:disable Metrics/BlockLength
|
|
41
41
|
response = api_client.fetch_failures(date: @today_iso_format, state: 'opened')
|
|
42
42
|
|
|
43
|
-
if response.
|
|
43
|
+
if response.empty?
|
|
44
44
|
logger.info 'Life is great, there are no new failures today!'
|
|
45
45
|
exit 0
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
response.each do |failure|
|
|
49
|
-
title = failure
|
|
50
|
-
author = failure
|
|
51
|
-
url = failure
|
|
52
|
-
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
|
|
53
52
|
triaged = add_color('x', :red)
|
|
54
53
|
|
|
55
|
-
emoji_awards = api_client.fetch_awarded_emojis(
|
|
56
|
-
|
|
54
|
+
emoji_awards = api_client.fetch_awarded_emojis(failure.iid).find do |e|
|
|
55
|
+
e.name == emoji && e.to_h.dig('user', 'username') == username
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
if emoji_awards
|
|
@@ -62,7 +61,7 @@ module Dri
|
|
|
62
61
|
end
|
|
63
62
|
|
|
64
63
|
if @options[:urgent]
|
|
65
|
-
labels = failure
|
|
64
|
+
labels = failure.labels
|
|
66
65
|
|
|
67
66
|
labels.each do |label|
|
|
68
67
|
if label.include?('found:canary.gitlab.com' && 'found:canary.staging.gitlab.com')
|
|
@@ -2,84 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative '../../command'
|
|
4
4
|
require_relative '../../utils/table'
|
|
5
|
+
require_relative '../../utils/feature_flag_consts'
|
|
6
|
+
require_relative '../../feature_flag_report'
|
|
5
7
|
|
|
6
8
|
module Dri
|
|
7
9
|
module Commands
|
|
8
10
|
class Fetch
|
|
9
11
|
class FeatureFlags < Dri::Command
|
|
10
12
|
include Dri::Utils::Table
|
|
11
|
-
|
|
12
|
-
PRODUCTION = 'host::gitlab.com'
|
|
13
|
-
STAGING = 'host::staging.gitlab.com'
|
|
14
|
-
STAGING_REF = 'host::staging-ref.gitlab.com'
|
|
15
|
-
PREPROD = 'host::pre.gitlab.com'
|
|
13
|
+
include Dri::Utils::FeatureFlagConsts
|
|
16
14
|
|
|
17
15
|
def initialize(options)
|
|
18
16
|
@options = options
|
|
19
17
|
@today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
|
|
20
18
|
end
|
|
21
19
|
|
|
22
|
-
def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
20
|
+
def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
23
21
|
verify_config_exists
|
|
24
22
|
|
|
25
23
|
summary = add_color('Summary', :bright_yellow)
|
|
26
24
|
changed_on = add_color('Changed(UTC)', :bright_yellow)
|
|
27
25
|
url = add_color('URL', :bright_yellow)
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
staging_feature_flags = []
|
|
31
|
-
staging_ref_feature_flags = []
|
|
32
|
-
preprod_feature_flags = []
|
|
27
|
+
report = Dri::FeatureFlagReport.new
|
|
33
28
|
|
|
34
29
|
headers = [summary, changed_on, url]
|
|
35
30
|
|
|
36
31
|
logger.info "Fetching today's feature flag changes..."
|
|
37
32
|
|
|
38
|
-
spinner.run do
|
|
39
|
-
|
|
40
|
-
loop do # rubocop:disable Metrics/BlockLength
|
|
41
|
-
response = api_client.fetch_feature_flag_logs(date: @today_iso_format, page: page)
|
|
42
|
-
|
|
43
|
-
if response.nil? && page == 1
|
|
44
|
-
logger.info 'It\'s been quiet...no feature flag changes for today 👀'
|
|
45
|
-
exit 0
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
response.each do |feature_flag|
|
|
49
|
-
summary = feature_flag['title']
|
|
50
|
-
|
|
51
|
-
substrings = ["set to \"true\"", "set to \"false\""]
|
|
52
|
-
next unless substrings.any? { |substr| summary.include?(substr) }
|
|
33
|
+
spinner.run do
|
|
34
|
+
response = api_client.fetch_feature_flag_logs(@today_iso_format)
|
|
53
35
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
labels = feature_flag['labels']
|
|
60
|
-
host_label = labels.select { |label| /^host::/.match(label) }.join('')
|
|
36
|
+
if response.empty?
|
|
37
|
+
logger.info 'It\'s been quiet...no feature flag changes for today 👀'
|
|
38
|
+
break
|
|
39
|
+
end
|
|
61
40
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
prod_feature_flags << feature_flag_data
|
|
65
|
-
when STAGING
|
|
66
|
-
staging_feature_flags << feature_flag_data
|
|
67
|
-
when STAGING_REF
|
|
68
|
-
staging_ref_feature_flags << feature_flag_data
|
|
69
|
-
when PREPROD
|
|
70
|
-
preprod_feature_flags << feature_flag_data
|
|
71
|
-
end
|
|
72
|
-
end
|
|
41
|
+
response.each do |feature_flag|
|
|
42
|
+
next unless TITLE_SUBSTRINGS.any? { |substr| feature_flag.title.include?(substr) }
|
|
73
43
|
|
|
74
|
-
|
|
75
|
-
break if response.count < 100 || response.nil?
|
|
44
|
+
report.add_change(feature_flag)
|
|
76
45
|
end
|
|
77
46
|
end
|
|
78
47
|
|
|
79
|
-
print_results('Production', headers,
|
|
80
|
-
print_results('Staging', headers,
|
|
81
|
-
print_results('Staging Ref', headers,
|
|
82
|
-
print_results('Preprod', headers,
|
|
48
|
+
print_results('Production', headers, report.prod, output) unless report.prod.empty?
|
|
49
|
+
print_results('Staging', headers, report.staging, output) unless report.staging.empty?
|
|
50
|
+
print_results('Staging Ref', headers, report.staging_ref, output) unless report.staging_ref.empty?
|
|
51
|
+
print_results('Preprod', headers, report.preprod, output) unless report.preprod.empty?
|
|
83
52
|
end
|
|
84
53
|
|
|
85
54
|
private
|
|
@@ -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
|
|
@@ -47,8 +47,8 @@ module Dri
|
|
|
47
47
|
|
|
48
48
|
output.puts "♦♦♦♦♦ #{add_color(pipeline, :black, :on_white)}♦♦♦♦♦\n\n"
|
|
49
49
|
|
|
50
|
-
response.each do |
|
|
51
|
-
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}"
|
|
52
52
|
output.puts "#{divider}\n"
|
|
53
53
|
end
|
|
54
54
|
end
|
|
@@ -31,13 +31,13 @@ module Dri
|
|
|
31
31
|
|
|
32
32
|
if response.nil?
|
|
33
33
|
logger.info 'Hooray, no active incidents 🎉.'
|
|
34
|
-
|
|
34
|
+
break
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
response.each do |incident|
|
|
38
|
-
title = incident
|
|
39
|
-
url = incident
|
|
40
|
-
labels = incident
|
|
38
|
+
title = incident.title.truncate(70)
|
|
39
|
+
url = incident.web_url
|
|
40
|
+
labels = incident.labels
|
|
41
41
|
status = "N/A"
|
|
42
42
|
service = "N/A"
|
|
43
43
|
|
|
@@ -2,21 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative '../../command'
|
|
4
4
|
require_relative '../../utils/markdown_lists'
|
|
5
|
-
require_relative
|
|
5
|
+
require_relative '../../utils/feature_flag_consts'
|
|
6
|
+
require_relative '../../report'
|
|
7
|
+
require_relative '../../feature_flag_report'
|
|
6
8
|
|
|
7
9
|
require 'markdown-tables'
|
|
8
10
|
require 'fileutils'
|
|
9
|
-
require
|
|
11
|
+
require 'uri'
|
|
10
12
|
|
|
11
13
|
module Dri
|
|
12
14
|
module Commands
|
|
13
15
|
class Publish
|
|
14
|
-
class Report < Dri::Command
|
|
16
|
+
class Report < Dri::Command # rubocop:disable Metrics/ClassLength
|
|
17
|
+
include Dri::Utils::FeatureFlagConsts
|
|
18
|
+
|
|
15
19
|
def initialize(options)
|
|
16
20
|
@options = options
|
|
17
21
|
|
|
18
22
|
@date = Date.today
|
|
19
23
|
@time = Time.now.to_i
|
|
24
|
+
@today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
|
@@ -26,7 +31,9 @@ module Dri
|
|
|
26
31
|
logger.info "Fetching triaged failures with award emoji #{emoji}..."
|
|
27
32
|
|
|
28
33
|
spinner.start
|
|
34
|
+
|
|
29
35
|
issues = api_client.fetch_triaged_failures(emoji: emoji, state: 'opened')
|
|
36
|
+
|
|
30
37
|
spinner.stop
|
|
31
38
|
|
|
32
39
|
if issues.empty?
|
|
@@ -34,27 +41,28 @@ module Dri
|
|
|
34
41
|
exit 1
|
|
35
42
|
end
|
|
36
43
|
|
|
37
|
-
logger.info
|
|
44
|
+
logger.info 'Assembling the failures report... '
|
|
38
45
|
# sets each failure on the table
|
|
39
46
|
action_options = [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
'pinged SET',
|
|
48
|
+
'reproduced',
|
|
49
|
+
'transient',
|
|
50
|
+
'quarantined',
|
|
51
|
+
'active investigation',
|
|
52
|
+
'blocking pipelines',
|
|
53
|
+
'awaiting for a fix to merge',
|
|
54
|
+
'notified the team',
|
|
55
|
+
'due to feature flag'
|
|
49
56
|
]
|
|
50
57
|
|
|
51
58
|
spinner.start
|
|
59
|
+
|
|
52
60
|
issues.each do |issue|
|
|
53
61
|
actions = []
|
|
54
62
|
|
|
55
63
|
if @options[:actions]
|
|
56
64
|
actions = prompt.multi_select(
|
|
57
|
-
"Please mark the actions on #{add_color(issue
|
|
65
|
+
"Please mark the actions on #{add_color(issue.title, :yellow)}: ",
|
|
58
66
|
action_options,
|
|
59
67
|
per_page: 9
|
|
60
68
|
)
|
|
@@ -77,14 +85,59 @@ module Dri
|
|
|
77
85
|
end
|
|
78
86
|
end
|
|
79
87
|
|
|
88
|
+
spinner.stop
|
|
89
|
+
|
|
90
|
+
if @options[:feature_flags]
|
|
91
|
+
logger.info 'Fetching today\'s feature flag changes...'
|
|
92
|
+
|
|
93
|
+
feature_flag_report = Dri::FeatureFlagReport.new
|
|
94
|
+
|
|
95
|
+
spinner.start
|
|
96
|
+
|
|
97
|
+
feature_flags = api_client.fetch_feature_flag_logs(@today_iso_format)
|
|
98
|
+
|
|
99
|
+
feature_flags.each do |feature_flag|
|
|
100
|
+
next unless TITLE_SUBSTRINGS.any? { |substr| feature_flag.title.include?(substr) }
|
|
101
|
+
|
|
102
|
+
feature_flag_report.add_change(feature_flag)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
spinner.stop
|
|
106
|
+
|
|
107
|
+
logger.info 'Assembling the feature flags report...'
|
|
108
|
+
|
|
109
|
+
spinner.start
|
|
110
|
+
|
|
111
|
+
feature_flag_note = "\n\n## Feature Flag Changes"
|
|
112
|
+
feature_flag_changes = ''
|
|
113
|
+
|
|
114
|
+
format_type = @options[:format] == 'list' ? :list : :table
|
|
115
|
+
|
|
116
|
+
feature_flag_report.get_all_flag_changes.each do |env, changes|
|
|
117
|
+
next if changes.empty?
|
|
118
|
+
|
|
119
|
+
feature_flag_changes += format_feature_flag_changes(
|
|
120
|
+
env, changes, feature_flag_report.labels, format_type
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
feature_flag_note += if feature_flag_changes.empty?
|
|
125
|
+
"\n\nNo changes found today"
|
|
126
|
+
else
|
|
127
|
+
"\n\n<details><summary>Click to expand</summary>#{feature_flag_changes}</details>"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
spinner.stop
|
|
131
|
+
end
|
|
132
|
+
|
|
80
133
|
report.set_header(timezone, username)
|
|
81
134
|
note = "#{report.header}\n\n#{format_style}"
|
|
82
135
|
|
|
83
|
-
|
|
136
|
+
note += feature_flag_note if @options[:feature_flags]
|
|
84
137
|
|
|
85
138
|
# creates an .md file with the report locally in /handover_reports
|
|
86
139
|
if @options[:dry_run]
|
|
87
|
-
logger.info
|
|
140
|
+
logger.info 'Downloading the report... '
|
|
88
141
|
|
|
89
142
|
spinner.start
|
|
90
143
|
|
|
@@ -104,7 +157,7 @@ module Dri
|
|
|
104
157
|
|
|
105
158
|
# sends note to the weekly triage report
|
|
106
159
|
issues = api_client.fetch_current_triage_issue
|
|
107
|
-
current_issue_iid = issues
|
|
160
|
+
current_issue_iid = issues.first.iid
|
|
108
161
|
|
|
109
162
|
api_client.post_triage_report_note(iid: current_issue_iid, body: note)
|
|
110
163
|
|
|
@@ -113,6 +166,27 @@ module Dri
|
|
|
113
166
|
Thanks @#{username}, your report was posted at https://gitlab.com/gitlab-org/quality/pipeline-triage/-/issues/#{current_issue_iid} 🎉
|
|
114
167
|
MSG
|
|
115
168
|
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def format_feature_flag_changes(env, changes, labels, format_type)
|
|
173
|
+
unless format_type == :table || format_type == :list
|
|
174
|
+
raise ArgumentError, 'format_type must be one of type :table or :list'
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
case format_type
|
|
178
|
+
when :list
|
|
179
|
+
formatted_changes = Utils::MarkdownLists.make_list(labels, changes)
|
|
180
|
+
when :table
|
|
181
|
+
formatted_changes = MarkdownTables.make_table(
|
|
182
|
+
labels,
|
|
183
|
+
changes,
|
|
184
|
+
is_rows: true, align: %w[l l l]
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
"\n\n### #{env.to_s.capitalize.tr('_', ' ')}\n\n#{formatted_changes}"
|
|
189
|
+
end
|
|
116
190
|
end
|
|
117
191
|
end
|
|
118
192
|
end
|
data/lib/dri/commands/publish.rb
CHANGED
|
@@ -14,6 +14,8 @@ module Dri
|
|
|
14
14
|
desc: 'Formats the report'
|
|
15
15
|
method_option :actions, type: :boolean,
|
|
16
16
|
desc: 'Updates actions on failures'
|
|
17
|
+
method_option :feature_flags, type: :boolean,
|
|
18
|
+
desc: 'Adds summary of feature flag changes'
|
|
17
19
|
def report(*)
|
|
18
20
|
if options[:help]
|
|
19
21
|
invoke :help, ['report']
|
|
@@ -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,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative './utils/feature_flag_consts'
|
|
4
|
+
|
|
5
|
+
module Dri
|
|
6
|
+
class FeatureFlagReport
|
|
7
|
+
include Dri::Utils::FeatureFlagConsts
|
|
8
|
+
|
|
9
|
+
attr_reader :header, :labels, :prod, :staging, :staging_ref, :preprod
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@header = '## Feature Flag Changes'
|
|
13
|
+
@labels = %w[Summary Changed(UTC) URL]
|
|
14
|
+
@prod = []
|
|
15
|
+
@staging = []
|
|
16
|
+
@staging_ref = []
|
|
17
|
+
@preprod = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_change(feature_flag)
|
|
21
|
+
summary = feature_flag.title
|
|
22
|
+
|
|
23
|
+
changed_on = feature_flag.description[/(?<=Changed on \(in UTC\): ).+?(?=\n)/].delete('`')
|
|
24
|
+
url = feature_flag.web_url
|
|
25
|
+
|
|
26
|
+
feature_flag_data = [summary, changed_on, url]
|
|
27
|
+
|
|
28
|
+
labels = feature_flag.labels
|
|
29
|
+
host_label = labels.select { |label| /^host::/.match(label) }.join('')
|
|
30
|
+
|
|
31
|
+
case host_label
|
|
32
|
+
when PRODUCTION
|
|
33
|
+
@prod << feature_flag_data
|
|
34
|
+
when STAGING
|
|
35
|
+
@staging << feature_flag_data
|
|
36
|
+
when STAGING_REF
|
|
37
|
+
@staging_ref << feature_flag_data
|
|
38
|
+
when PREPROD
|
|
39
|
+
@preprod << feature_flag_data
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get_all_flag_changes
|
|
44
|
+
{ production: @prod, staging: @staging, staging_ref: @staging_ref, preprod: @preprod }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -191,7 +193,7 @@ module Dri
|
|
|
191
193
|
path = "Failure in `#{path.strip}`" if path.delete_prefix!('Failure in ')
|
|
192
194
|
path = path.strip.gsub(' ', ' ') if desc
|
|
193
195
|
|
|
194
|
-
|
|
196
|
+
[path, desc].compact.join(' | ')
|
|
195
197
|
end
|
|
196
198
|
end
|
|
197
199
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dri
|
|
4
|
+
module Utils
|
|
5
|
+
module FeatureFlagConsts
|
|
6
|
+
PRODUCTION = 'host::gitlab.com'
|
|
7
|
+
STAGING = 'host::staging.gitlab.com'
|
|
8
|
+
STAGING_REF = 'host::staging-ref.gitlab.com'
|
|
9
|
+
PREPROD = 'host::pre.gitlab.com'
|
|
10
|
+
TITLE_SUBSTRINGS = ["set to \"true\"", "set to \"false\""].freeze
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
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.5.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-06-09 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
|
|
@@ -305,9 +333,12 @@ files:
|
|
|
305
333
|
- lib/dri/commands/rm/emoji.rb
|
|
306
334
|
- lib/dri/commands/rm/profile.rb
|
|
307
335
|
- lib/dri/commands/rm/reports.rb
|
|
336
|
+
- lib/dri/feature_flag_report.rb
|
|
337
|
+
- lib/dri/gitlab/issues.rb
|
|
308
338
|
- lib/dri/refinements/truncate.rb
|
|
309
339
|
- lib/dri/report.rb
|
|
310
340
|
- lib/dri/templates/incidents/.gitkeep
|
|
341
|
+
- lib/dri/utils/feature_flag_consts.rb
|
|
311
342
|
- lib/dri/utils/markdown_lists.rb
|
|
312
343
|
- lib/dri/utils/table.rb
|
|
313
344
|
- lib/dri/version.rb
|