dri 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -0
- data/lib/dri/api_client.rb +25 -9
- data/lib/dri/command.rb +4 -0
- data/lib/dri/commands/fetch/pipelines.rb +30 -18
- data/lib/dri/commands/fetch.rb +1 -1
- data/lib/dri/commands/init.rb +3 -0
- data/lib/dri/commands/publish/report.rb +30 -9
- data/lib/dri/commands/rm/emoji.rb +17 -7
- data/lib/dri/commands/rm/reports.rb +1 -1
- data/lib/dri/report.rb +18 -1
- data/lib/dri/utils/constants.rb +3 -3
- data/lib/dri/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6ee21fc2a0243e0850f7bab281b0611021807a1a4559e89433741b1faed40f2
|
4
|
+
data.tar.gz: 5b97afcdbc1e1e9307b084eab6e2afddbff94556cc102f2b174a0dedc3f51c33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5b54fee2e4f0d99d0d013d0c9a211306eda149dabb6c0cedf05ec0c5a92274dd4038377fc270b6185782ba2dfdbe5c2be6028136c77a4e77dceb21989a82894
|
7
|
+
data.tar.gz: c2d5056697c04ea976e00db46f183cd716091e3681687ad369c6dc3c11dab7ecd5592022460948a19ce779da7b114c2c90c4ba3c712a0bfe6256524cf6859934
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -146,6 +146,7 @@ $ dri publish report
|
|
146
146
|
```
|
147
147
|
|
148
148
|
Publishes a handover report on the latest triage issue, in the [pipeline-triage](https://gitlab.com/gitlab-org/quality/pipeline-triage) project.
|
149
|
+
The report includes automatically both triaged failures and incidents.
|
149
150
|
|
150
151
|
**Options**
|
151
152
|
|
data/lib/dri/api_client.rb
CHANGED
@@ -11,11 +11,12 @@ module Dri
|
|
11
11
|
class ApiClient # rubocop:disable Metrics/ClassLength
|
12
12
|
API_URL = "https://gitlab.com/api/v4"
|
13
13
|
OPS_API_URL = "https://ops.gitlab.net/api/v4"
|
14
|
-
TESTCASES_PROJECT_ID =
|
15
|
-
TRIAGE_PROJECT_ID =
|
16
|
-
GITLAB_PROJECT_ID =
|
17
|
-
FEATURE_FLAG_LOG_PROJECT_ID =
|
18
|
-
INFRA_TEAM_PROD_PROJECT_ID =
|
14
|
+
TESTCASES_PROJECT_ID = 11229385
|
15
|
+
TRIAGE_PROJECT_ID = 15291320
|
16
|
+
GITLAB_PROJECT_ID = 278964
|
17
|
+
FEATURE_FLAG_LOG_PROJECT_ID = 15208716
|
18
|
+
INFRA_TEAM_PROD_PROJECT_ID = 7444821
|
19
|
+
|
19
20
|
def initialize(config, ops = false)
|
20
21
|
@token = config.read.dig("settings", "token")
|
21
22
|
@ops_token = config.read.dig("settings", "ops_token")
|
@@ -42,12 +43,26 @@ module Dri
|
|
42
43
|
)
|
43
44
|
end
|
44
45
|
|
46
|
+
# Fetch triaged incidents
|
47
|
+
#
|
48
|
+
# @param [String] emoji
|
49
|
+
def fetch_triaged_incidents(emoji:)
|
50
|
+
gitlab.issues(
|
51
|
+
INFRA_TEAM_PROD_PROJECT_ID,
|
52
|
+
order_by: "updated_at",
|
53
|
+
my_reaction_emoji: emoji,
|
54
|
+
state: "all",
|
55
|
+
labels: "incident"
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
45
59
|
# Fetch award emojis
|
46
60
|
#
|
47
61
|
# @param [Integer] issue_iid
|
62
|
+
# @param [Integer] project_id
|
48
63
|
# @return [Array<Gitlab::ObjectifiedHash>]
|
49
|
-
def fetch_awarded_emojis(issue_iid)
|
50
|
-
gitlab.award_emojis(
|
64
|
+
def fetch_awarded_emojis(issue_iid, project_id: GITLAB_PROJECT_ID)
|
65
|
+
gitlab.award_emojis(project_id, issue_iid, "issue")
|
51
66
|
end
|
52
67
|
|
53
68
|
# Fetch failing testcases
|
@@ -144,10 +159,11 @@ module Dri
|
|
144
159
|
#
|
145
160
|
# @param [Integer] issue_iid
|
146
161
|
# @param [Integer] emoji_id
|
162
|
+
# @param [Integer] project_id
|
147
163
|
# @return [Gitlab::ObjectifiedHash]
|
148
|
-
def delete_award_emoji(issue_iid, emoji_id)
|
164
|
+
def delete_award_emoji(issue_iid, emoji_id:, project_id: GITLAB_PROJECT_ID)
|
149
165
|
gitlab.delete_award_emoji(
|
150
|
-
|
166
|
+
project_id,
|
151
167
|
issue_iid,
|
152
168
|
"issue",
|
153
169
|
emoji_id
|
data/lib/dri/command.rb
CHANGED
@@ -61,7 +61,7 @@ module Dri
|
|
61
61
|
bridges = api_client.pipeline_bridges(project_id, pipeline_id)
|
62
62
|
return if bridges.empty? # If downstream pipeline doesn't exist, which triggers the QA tests, return
|
63
63
|
|
64
|
-
bridges.
|
64
|
+
bridges.find { |it| it.name == "e2e:package-and-test" }&.downstream_pipeline
|
65
65
|
end
|
66
66
|
|
67
67
|
# Get jobs from a pipeline
|
@@ -136,6 +136,14 @@ module Dri
|
|
136
136
|
pipeline_id: pipeline.id, ops: ops)
|
137
137
|
end
|
138
138
|
|
139
|
+
# Master pipeline run
|
140
|
+
#
|
141
|
+
# @param [String] pipeline_name
|
142
|
+
# @return [Boolean]
|
143
|
+
def master?(pipeline_name)
|
144
|
+
pipeline_name == "master"
|
145
|
+
end
|
146
|
+
|
139
147
|
# Constructs allure report url for each pipeline
|
140
148
|
# @param [String] pipeline_name
|
141
149
|
# @param [Integer] pipeline_id
|
@@ -151,7 +159,7 @@ module Dri
|
|
151
159
|
def allure_bucket_name(pipeline_name, sanity)
|
152
160
|
case pipeline_name
|
153
161
|
when "master"
|
154
|
-
"package-and-
|
162
|
+
"e2e-package-and-test"
|
155
163
|
when "nightly"
|
156
164
|
pipeline_name
|
157
165
|
when "pre_prod"
|
@@ -182,16 +190,19 @@ module Dri
|
|
182
190
|
url.include?("ops.gitlab.net")
|
183
191
|
end
|
184
192
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
193
|
+
# Slack notification job name
|
194
|
+
#
|
195
|
+
# @param [Boolean] ops
|
196
|
+
# @return [String]
|
197
|
+
def notify_job_name(ops)
|
198
|
+
ops ? "notify-slack-qa-fail" : "notify-slack-fail"
|
189
199
|
end
|
190
200
|
|
191
201
|
# Returns child pipeline if it is master pipeline
|
202
|
+
# @param [String] pipeline_name
|
192
203
|
# @param [Gitlab::ObjectifiedHash] pipeline
|
193
|
-
def pipeline_with_qa_tests(pipeline)
|
194
|
-
if
|
204
|
+
def pipeline_with_qa_tests(pipeline_name, pipeline)
|
205
|
+
if master?(pipeline_name)
|
195
206
|
bridge_pipeline(pipeline.project_id, pipeline.id)
|
196
207
|
else
|
197
208
|
pipeline
|
@@ -202,9 +213,13 @@ module Dri
|
|
202
213
|
# @param [Hash] details
|
203
214
|
# @param [Boolean] ops
|
204
215
|
def options(details, ops)
|
205
|
-
options = {
|
206
|
-
|
207
|
-
|
216
|
+
options = {
|
217
|
+
order_by: "updated_at",
|
218
|
+
scope: "finished",
|
219
|
+
ref: "master",
|
220
|
+
updated_after: past_timestamp(details[:search_hours_ago])
|
221
|
+
}
|
222
|
+
options.merge(username: "gitlab-bot") if ops || master?(details[:name])
|
208
223
|
options
|
209
224
|
end
|
210
225
|
|
@@ -218,7 +233,7 @@ module Dri
|
|
218
233
|
# @param [Hash] details Pipeline environment details
|
219
234
|
# @return [Array] Array of last executed pipeline details
|
220
235
|
# rubocop:disable Metrics/PerceivedComplexity
|
221
|
-
def fetch_pipeline(pipeline_name:, details:) # rubocop:disable Metrics/CyclomaticComplexity
|
236
|
+
def fetch_pipeline(pipeline_name:, details:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
|
222
237
|
ops = ops_pipeline?(details[:url])
|
223
238
|
options = options(details, ops)
|
224
239
|
# instance is ops.gitlab.net or gitlab.com
|
@@ -228,18 +243,15 @@ module Dri
|
|
228
243
|
|
229
244
|
# Return empty data to the table if no matching pipelines were found from the query
|
230
245
|
response.each do |pipeline|
|
231
|
-
pipeline_to_scan = pipeline_with_qa_tests(pipeline) # Fetch child pipeline if it is master
|
246
|
+
pipeline_to_scan = pipeline_with_qa_tests(pipeline_name, pipeline) # Fetch child pipeline if it is master
|
232
247
|
next if pipeline_to_scan.nil?
|
233
248
|
|
234
249
|
pipeline_jobs = jobs(project_id: pipeline.project_id, pipeline_id: pipeline_to_scan.id, ops: ops)
|
235
|
-
next unless contains_job?(pipeline_jobs, job_name:
|
250
|
+
next unless master?(pipeline_name) || contains_job?(pipeline_jobs, job_name: notify_job_name(ops))
|
236
251
|
|
237
252
|
# Need to know if it is a sanity or a full run to construct allure report url
|
238
|
-
sanity = sanity?(pipeline_jobs: pipeline_jobs, pipeline: pipeline_to_scan,
|
239
|
-
ops: ops)
|
240
|
-
|
253
|
+
sanity = sanity?(pipeline_jobs: pipeline_jobs, pipeline: pipeline_to_scan, ops: ops)
|
241
254
|
next if sanity.nil? # To filter out some "clean up" pipelines present in live environments
|
242
|
-
|
243
255
|
next if sanity && @options[:full_runs_only] # Filter out sanity runs if --full-runs-only option is passed
|
244
256
|
|
245
257
|
name = ops ? "#{pipeline_name}_#{run_type(sanity)}" : pipeline_name
|
data/lib/dri/commands/fetch.rb
CHANGED
data/lib/dri/commands/init.rb
CHANGED
@@ -34,6 +34,8 @@ module Dri
|
|
34
34
|
@ops_token = prompt.mask("Please provide your ops.gitlab.net personal access token:")
|
35
35
|
@timezone = prompt.select("Choose your current timezone?", %w[EMEA AMER APAC])
|
36
36
|
@emoji = prompt.ask("Have a triage emoji?")
|
37
|
+
@handover_report_path = prompt.ask("Please provide a path for handover report",
|
38
|
+
default: "#{Dir.pwd}/handover_reports")
|
37
39
|
|
38
40
|
if (@emoji || @token || @username || @ops_token).nil?
|
39
41
|
logger.error "Please provide a username, gitlab token, ops token, timezone and emoji used for triage."
|
@@ -45,6 +47,7 @@ module Dri
|
|
45
47
|
config.set(:settings, :ops_token, value: @ops_token)
|
46
48
|
config.set(:settings, :timezone, value: @timezone)
|
47
49
|
config.set(:settings, :emoji, value: @emoji)
|
50
|
+
config.set(:settings, :handover_report_path, value: @handover_report_path)
|
48
51
|
config.write(force: true)
|
49
52
|
|
50
53
|
logger.success "✅ We're ready to go 🚀"
|
@@ -28,20 +28,21 @@ module Dri
|
|
28
28
|
verify_config_exists
|
29
29
|
report = Dri::Report.new(config)
|
30
30
|
|
31
|
-
logger.info "Fetching triaged failures with award emoji #{emoji}..."
|
31
|
+
logger.info "Fetching triaged failures and incidents with award emoji #{emoji}..."
|
32
32
|
|
33
33
|
spinner.start
|
34
34
|
|
35
35
|
issues = api_client.fetch_triaged_failures(emoji: emoji, state: 'opened')
|
36
|
+
incidents = api_client.fetch_triaged_incidents(emoji: emoji)
|
36
37
|
|
37
38
|
spinner.stop
|
38
39
|
|
39
|
-
if issues.empty?
|
40
|
-
logger.warn "Found no issues associated with \"#{emoji}\" emoji. Will exit. Bye 👋"
|
40
|
+
if issues.empty? && incidents.empty?
|
41
|
+
logger.warn "Found no issues nor incidents associated with \"#{emoji}\" emoji. Will exit. Bye 👋"
|
41
42
|
exit 1
|
42
43
|
end
|
43
44
|
|
44
|
-
logger.info 'Assembling the
|
45
|
+
logger.info 'Assembling the handover report... '
|
45
46
|
# sets each failure on the table
|
46
47
|
action_options = [
|
47
48
|
'pinged SET',
|
@@ -71,18 +72,38 @@ module Dri
|
|
71
72
|
report.add_failure(issue, actions)
|
72
73
|
end
|
73
74
|
|
75
|
+
unless incidents.empty?
|
76
|
+
incidents.each do |issue|
|
77
|
+
report.add_incident(issue)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
74
81
|
if @options[:format] == 'list'
|
75
82
|
# generates markdown list with failures
|
76
|
-
|
83
|
+
unless report.failures.empty?
|
84
|
+
failure_report = Utils::MarkdownLists.make_list(report.labels, report.failures)
|
85
|
+
end
|
86
|
+
# generates markdown list with incidents
|
87
|
+
unless report.incidents.empty?
|
88
|
+
incidents_report = Utils::MarkdownLists.make_list(report.labels_incidents, report.incidents)
|
89
|
+
end
|
77
90
|
else
|
78
91
|
# generates markdown table with rows as failures
|
79
92
|
unless report.failures.empty?
|
80
|
-
|
93
|
+
failure_report = MarkdownTables.make_table(
|
81
94
|
report.labels,
|
82
95
|
report.failures,
|
83
96
|
is_rows: true, align: %w[l l l l l]
|
84
97
|
)
|
85
98
|
end
|
99
|
+
# generates markdown table with rows as incidents
|
100
|
+
unless report.incidents.empty?
|
101
|
+
incidents_report = MarkdownTables.make_table(
|
102
|
+
report.labels_incidents,
|
103
|
+
report.incidents,
|
104
|
+
is_rows: true, align: %w[l l l l]
|
105
|
+
)
|
106
|
+
end
|
86
107
|
end
|
87
108
|
|
88
109
|
spinner.stop
|
@@ -131,7 +152,7 @@ module Dri
|
|
131
152
|
end
|
132
153
|
|
133
154
|
report.set_header(timezone, username)
|
134
|
-
note = "#{report.header}\n\n#{
|
155
|
+
note = "#{report.header}\n\n#{failure_report}\n\n#{incidents_report}"
|
135
156
|
|
136
157
|
note += feature_flag_note if @options[:feature_flags]
|
137
158
|
|
@@ -141,8 +162,8 @@ module Dri
|
|
141
162
|
|
142
163
|
spinner.start
|
143
164
|
|
144
|
-
FileUtils.mkdir_p(
|
145
|
-
report_path = "
|
165
|
+
FileUtils.mkdir_p(handover_report_path)
|
166
|
+
report_path = "#{handover_report_path.chomp('/')}/report-#{@date}-#{@time}.md"
|
146
167
|
|
147
168
|
File.open(report_path, 'a') do |out_file|
|
148
169
|
out_file.puts note
|
@@ -20,25 +20,35 @@ module Dri
|
|
20
20
|
exit 0
|
21
21
|
end
|
22
22
|
|
23
|
-
logger.info "Removing #{emoji} from issues..."
|
23
|
+
logger.info "Removing #{emoji} emoji from issues..."
|
24
24
|
|
25
25
|
spinner.start
|
26
26
|
|
27
|
-
|
27
|
+
failures_with_award_emoji = api_client.fetch_triaged_failures(emoji: emoji, state: 'opened')
|
28
|
+
incidents_with_award_emoji = api_client.fetch_triaged_incidents(emoji: emoji)
|
29
|
+
|
30
|
+
issues_with_award_emoji = failures_with_award_emoji + incidents_with_award_emoji
|
28
31
|
|
29
32
|
spinner.stop
|
30
33
|
|
34
|
+
cleared_issues_counter = 0
|
31
35
|
issues_with_award_emoji.each do |issue|
|
32
|
-
logger.info "Removing #{emoji} from #{issue.web_url}
|
33
|
-
|
34
|
-
response = api_client.fetch_awarded_emojis(issue.iid)
|
36
|
+
logger.info "Removing #{emoji} emoji from #{issue.web_url}"
|
35
37
|
|
38
|
+
response = api_client.fetch_awarded_emojis(issue.iid, project_id: issue.project_id)
|
36
39
|
emoji_found = response.find { |e| e.name == emoji && e.to_h.dig('user', 'username') == username }
|
37
40
|
|
38
|
-
|
41
|
+
if emoji_found.nil?
|
42
|
+
logger.error "Emoji #{add_color(emoji, :red)} not found for username: #{add_color(username, :red)}"
|
43
|
+
next
|
44
|
+
else
|
45
|
+
api_client.delete_award_emoji(issue.iid, emoji_id: emoji_found.id, project_id: issue.project_id)
|
46
|
+
cleared_issues_counter += 1
|
47
|
+
end
|
39
48
|
end
|
49
|
+
|
40
50
|
output.puts "Done! ✅"
|
41
|
-
logger.success "Removed #{emoji} from #{
|
51
|
+
logger.success "Removed #{emoji} from #{cleared_issues_counter} issue(s)."
|
42
52
|
end
|
43
53
|
end
|
44
54
|
end
|
data/lib/dri/report.rb
CHANGED
@@ -4,11 +4,13 @@ module Dri
|
|
4
4
|
class Report # rubocop:disable Metrics/ClassLength
|
5
5
|
using Refinements
|
6
6
|
|
7
|
-
attr_reader :header, :failures, :labels
|
7
|
+
attr_reader :header, :failures, :labels, :labels_incidents, :incidents
|
8
8
|
|
9
9
|
def initialize(config)
|
10
10
|
@labels = ['Title', 'Issue', 'Pipelines', 'Stack Trace', 'Actions']
|
11
|
+
@labels_incidents = %w[Incident Service Status URL]
|
11
12
|
@failures = []
|
13
|
+
@incidents = []
|
12
14
|
@date = Date.today
|
13
15
|
@today = Date.today.strftime("%Y-%m-%d")
|
14
16
|
@weekday = Date.today.strftime("%A")
|
@@ -21,6 +23,21 @@ module Dri
|
|
21
23
|
@header = "# #{timezone}, #{@weekday} - #{@date}\n posted by: @#{username}"
|
22
24
|
end
|
23
25
|
|
26
|
+
def add_incident(incident)
|
27
|
+
title = incident["title"]
|
28
|
+
url = incident["web_url"]
|
29
|
+
labels = incident["labels"]
|
30
|
+
status = 'N/A'
|
31
|
+
service = 'N/A'
|
32
|
+
|
33
|
+
labels.each do |label|
|
34
|
+
status = label.gsub!('Incident::', ' ').to_s if label.include? "Incident::"
|
35
|
+
service = label.gsub!('Service::', ' ').to_s if label.include? "Service::"
|
36
|
+
end
|
37
|
+
|
38
|
+
@incidents << [title, service, status, url]
|
39
|
+
end
|
40
|
+
|
24
41
|
def add_failure(failure, actions_opts = [])
|
25
42
|
iid = failure["iid"]
|
26
43
|
title = format_title(failure["title"])
|
data/lib/dri/utils/constants.rb
CHANGED
@@ -49,9 +49,9 @@ module Dri
|
|
49
49
|
},
|
50
50
|
master: {
|
51
51
|
name: "master",
|
52
|
-
url: "https://gitlab.com/gitlab-org/gitlab
|
53
|
-
project_id: "
|
54
|
-
search_hours_ago:
|
52
|
+
url: "https://gitlab.com/gitlab-org/gitlab",
|
53
|
+
project_id: "278964",
|
54
|
+
search_hours_ago: 3
|
55
55
|
}
|
56
56
|
}.freeze
|
57
57
|
end
|
data/lib/dri/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dri
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.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-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: amatch
|