dri 0.6.1 → 0.7.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/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
|