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 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