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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 990d9ed4c79b5b7c7ff2e5535dba71599a3189d4c742b918f98d3063a6e15c9f
4
- data.tar.gz: 15e48f44496a55d5d13185409a91d9ddd525d127fd8dbe73265dc176c6d14634
3
+ metadata.gz: e6ee21fc2a0243e0850f7bab281b0611021807a1a4559e89433741b1faed40f2
4
+ data.tar.gz: 5b97afcdbc1e1e9307b084eab6e2afddbff94556cc102f2b174a0dedc3f51c33
5
5
  SHA512:
6
- metadata.gz: 22a9838a3821c90cb24346f6f98b6158e2a71ec9bd0c4e7b5d9e447a75b676202be1d365e1603d7d12a8441f6b5d448f26337c33163b60f346819933da24fea3
7
- data.tar.gz: 6b117a44436b2c4c064c3b19b22deb454a42c8d6d460492af7813e72299600d50ad2cef612e5e1f2a84566588f560d31c82da3c5c92b35f1d8749ea445e9664e
6
+ metadata.gz: f5b54fee2e4f0d99d0d013d0c9a211306eda149dabb6c0cedf05ec0c5a92274dd4038377fc270b6185782ba2dfdbe5c2be6028136c77a4e77dceb21989a82894
7
+ data.tar.gz: c2d5056697c04ea976e00db46f183cd716091e3681687ad369c6dc3c11dab7ecd5592022460948a19ce779da7b114c2c90c4ba3c712a0bfe6256524cf6859934
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dri (0.6.1)
4
+ dri (0.7.0)
5
5
  amatch (~> 0.4.1)
6
6
  gitlab (~> 4.18)
7
7
  httparty (~> 0.20.0)
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
 
@@ -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 = "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"
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(GITLAB_PROJECT_ID, issue_iid, "issue")
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
- GITLAB_PROJECT_ID,
166
+ project_id,
151
167
  issue_iid,
152
168
  "issue",
153
169
  emoji_id
data/lib/dri/command.rb CHANGED
@@ -60,6 +60,10 @@ module Dri
60
60
  @timezone ||= profile["settings"]["timezone"]
61
61
  end
62
62
 
63
+ def handover_report_path
64
+ @handover_report_path ||= profile["settings"]["handover_report_path"]
65
+ end
66
+
63
67
  def verify_config_exists
64
68
  return if config.exist?
65
69
 
@@ -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.first["downstream_pipeline"]
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-qa"
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
- def notify_slack_job_name(pipeline_name, ops)
186
- return "notify-slack-qa-fail" if ops
187
-
188
- pipeline_name.to_s.include?("master") ? "notify_slack" : "notify-slack-fail"
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 pipeline.web_url.to_s.include?("gitlab-qa-mirror")
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 = { order_by: "updated_at", scope: "finished",
206
- updated_after: past_timestamp(details[:search_hours_ago]) }
207
- options.merge(username: "gitlab-bot") if ops
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: notify_slack_job_name(pipeline_name, ops))
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
@@ -19,7 +19,7 @@ module Dri
19
19
  end
20
20
  end
21
21
 
22
- desc 'triaged', 'Command description...'
22
+ desc 'triaged', 'Display triaged failures'
23
23
  method_option :help, aliases: '-h', type: :boolean,
24
24
  desc: 'Display usage information'
25
25
  def triaged(*)
@@ -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 failures report... '
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
- format_style = Utils::MarkdownLists.make_list(report.labels, report.failures) unless report.failures.empty?
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
- format_style = MarkdownTables.make_table(
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#{format_style}"
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("#{Dir.pwd}/handover_reports")
145
- report_path = "handover_reports/report-#{@date}-#{@time}.md"
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
- issues_with_award_emoji = api_client.fetch_triaged_failures(emoji: emoji, state: 'opened')
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
- api_client.delete_award_emoji(issue.iid, emoji_found.id) unless emoji_found.nil?
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 #{issues_with_award_emoji.size} issue(s)."
51
+ logger.success "Removed #{emoji} from #{cleared_issues_counter} issue(s)."
42
52
  end
43
53
  end
44
54
  end
@@ -12,7 +12,7 @@ module Dri
12
12
  end
13
13
 
14
14
  def execute(input: $stdin, output: $stdout)
15
- FileUtils.rm_rf("#{Dir.pwd}/handover_reports")
15
+ FileUtils.rm_rf(handover_report_path) if prompt.yes?("Remove everything in #{handover_report_path}?")
16
16
  end
17
17
  end
18
18
  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"])
@@ -49,9 +49,9 @@ module Dri
49
49
  },
50
50
  master: {
51
51
  name: "master",
52
- url: "https://gitlab.com/gitlab-org/gitlab-qa-mirror",
53
- project_id: "14707715",
54
- search_hours_ago: 6
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dri
4
- VERSION = "0.6.1"
4
+ VERSION = "0.7.0"
5
5
  end
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.6.1
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-07-14 00:00:00.000000000 Z
11
+ date: 2022-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: amatch