dri 0.2.0 → 0.4.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: 3ae68ce5f20bab397121604708b813e8f488316559f82392a389f1750e57c89c
4
- data.tar.gz: 30b16b0291beb7e18cc74acb9c54100c869544bba883c79729f577c8bda5dd7f
3
+ metadata.gz: 8718d907e41f0cfb472a255898ed513af0fe81ae4cfe782c7aa7d270cb0c2b9f
4
+ data.tar.gz: 718f976b309b6eb0cab9f65b32e58de5876edd5507aafcc20a2f098068bb5929
5
5
  SHA512:
6
- metadata.gz: ad35cf45172de050ecd86c376527abc98ba535f9ad97f54f84f7be3875882c46ae897039d26a4c9f4db3c7df598608c81e8613518b7c7eeb44b7903610899406
7
- data.tar.gz: a790667c5528fc3ad94f610b2add55b36a4fb5efd9bad3e8e10a78d44e58a8140721d4717a75def60ca829715389bebf001704a38a1a8127e1e6872228dd0fe6
6
+ metadata.gz: 643631388b378a127bca182911c9dfb304a1134b568fb71c716976f637f31f2a8d5056c2e30f0f9e2a72e7528e9e7f58a0bda4c0cbc1066efab02c3159353777
7
+ data.tar.gz: 46514571671417008fe79dadc26cee33b97ad642e61822e2a2097f4e83d774290d1389f3d7d5c6a8ddf7be7a1974d733b4f5cacef3e7c0084af16a37db30ee0d
data/.gitlab-ci.yml CHANGED
@@ -45,3 +45,11 @@ rspec:
45
45
  extends: .job_base
46
46
  script:
47
47
  - bundle exec rspec --color
48
+
49
+ rspec 3.0:
50
+ stage: test
51
+ extends: .job_base
52
+ image: ruby:3.0
53
+ script:
54
+ - bundle exec rspec --color
55
+
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.7.5
1
+ 3.0.4
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dri (0.1.3)
4
+ dri (0.3.1)
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.1)
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
@@ -65,7 +65,8 @@ $ dri profile
65
65
  - emoji
66
66
  - profile
67
67
  - reports
68
- - [6. version](#6-version)
68
+ - [6. incidents](#6-incidents)
69
+ - [7. version](#7-version)
69
70
 
70
71
  #### 1. init
71
72
 
@@ -181,7 +182,15 @@ $ dri rm profile
181
182
 
182
183
  Removes the profile currently in use.
183
184
 
184
- #### 6. version
185
+ #### 6. incidents
186
+
187
+ ```shell
188
+ $ dri incidents
189
+ ```
190
+
191
+ Have a quick look at currently active/mitigated incidents on GitLab services.
192
+
193
+ #### 7. version
185
194
 
186
195
  ```shell
187
196
  $ dri version
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"
@@ -4,51 +4,65 @@ 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 # rubocop:disable Metrics/ClassLength
10
+ class ApiClient
10
11
  API_URL = 'https://gitlab.com/api/v4'
11
12
  TESTCASES_PROJECT_ID = '11229385'
12
13
  TRIAGE_PROJECT_ID = '15291320'
13
14
  GITLAB_PROJECT_ID = '278964'
14
15
  FEATURE_FLAG_LOG_PROJECT_ID = '15208716'
16
+ INFRA_TEAM_PROD_PROJECT_ID = '7444821'
15
17
 
16
18
  def initialize(config)
17
- profile = config.read
18
- @token = profile["settings"]["token"]
19
- end
20
-
21
- def header
22
- @header ||= { 'Content-Type' => 'application/json', "Authorization" => "Bearer #{@token}" }
19
+ @token = config.read.dig("settings", "token")
23
20
  end
24
21
 
22
+ # Fetch triaged failures
23
+ #
24
+ # @param [String] emoji
25
+ # @param [String] state
26
+ # @return [Gitlab::ObjectifiedHash]
25
27
  def fetch_triaged_failures(emoji:, state:)
26
- url = ["#{API_URL}/issues"]
27
- url << "?order_by=updated_at&my_reaction_emoji=#{emoji}"
28
- url << "&scope=all&state=#{state}"
29
-
30
- fetch_json(url.join)
28
+ gitlab.issues(
29
+ GITLAB_PROJECT_ID,
30
+ order_by: "updated_at",
31
+ my_reaction_emoji: emoji,
32
+ scope: "all",
33
+ state: state
34
+ )
31
35
  end
32
36
 
33
- def fetch_awarded_emojis(url)
34
- fetch_json(url)
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")
35
43
  end
36
44
 
45
+ # Fetch failing testcases
46
+ #
47
+ # @param [String] pipeline
48
+ # @param [String] state
49
+ # @return [Array<Gitlab::ObjectifiedHash>]
37
50
  def fetch_failing_testcases(pipeline, state:)
38
- url = ["#{API_URL}/projects/"]
39
- url << "#{TESTCASES_PROJECT_ID}/issues?labels=#{pipeline}::failed"
40
- url << "&state=#{state}&not[labels]=quarantine"
41
- url << "&scope=all"
42
-
43
- fetch_json(url.join)
51
+ gitlab.issues(
52
+ TESTCASES_PROJECT_ID,
53
+ labels: "#{pipeline}::failed",
54
+ state: state,
55
+ scope: "all",
56
+ "not[labels]": "quarantine"
57
+ ).auto_paginate
44
58
  end
45
59
 
46
- def fetch_related_mrs(issue_iid:)
47
- url = ["#{API_URL}/projects/"]
48
- url << "#{GITLAB_PROJECT_ID}/issues/"
49
- url << "#{issue_iid}/related_merge_requests"
50
-
51
- fetch_json(url.join)
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)
52
66
  end
53
67
 
54
68
  # Fetch MRs
@@ -62,95 +76,91 @@ module Dri
62
76
  # @option options [String] milestone
63
77
  # @option options [String] labels
64
78
  def fetch_mrs(project_id:, **options)
65
- uri = URI(API_URL)
66
- uri.query = HTTParty::HashConversions.to_params(options)
67
-
68
- # CGI.escape('gitlab-org/gitlab') => 'gitlab-org%2Fgitlab'
69
- uri.path = File.join(uri.path, 'projects', CGI.escape(project_id), 'merge_requests')
70
-
71
- fetch_json(uri.to_s)
79
+ gitlab.merge_requests(project_id, per_page: 100, **options).auto_paginate
72
80
  end
73
81
 
82
+ # Fetch current triage issue
83
+ #
84
+ # @return [Gitlab::ObjectifiedHash]
74
85
  def fetch_current_triage_issue
75
- url = ["#{API_URL}/projects/"]
76
- url << "#{TRIAGE_PROJECT_ID}/issues?state=opened"
77
- url << "&order_by=updated_at"
78
-
79
- fetch_json(url.join)
86
+ gitlab.issues(TRIAGE_PROJECT_ID, state: "opened", order_by: "updated_at")
80
87
  end
81
88
 
89
+ # Create triage report note
90
+ #
91
+ # @param [Integer] iid
92
+ # @param [String] body
93
+ # @return [Gitlab::ObjectifiedHash]
82
94
  def post_triage_report_note(iid:, body:)
83
- url = ["#{API_URL}/projects/"]
84
- url << "#{TRIAGE_PROJECT_ID}/issues/#{iid}/notes"
85
-
86
- post_json(url.join, body)
95
+ gitlab.create_issue_note(TRIAGE_PROJECT_ID, iid, body)
87
96
  end
88
97
 
98
+ # Fetch new failures
99
+ #
100
+ # @param [String] date
101
+ # @param [String] state
102
+ # @return [Array<Gitlab::ObjectifiedHash>]
89
103
  def fetch_failures(date:, state:)
90
- url = ["#{API_URL}/issues"]
91
- url << "?labels=failure::new"
92
- url << "&order_by=updated_at&state=#{state}"
93
- url << "&scope=all"
94
- url << "&created_after=#{date}"
95
-
96
- fetch_json(url.join)
97
- end
98
-
99
- def fetch_failure_notes(issue_iid:)
100
- url = ["#{API_URL}/projects/"]
101
- url << "#{GITLAB_PROJECT_ID}/issues/"
102
- url << "#{issue_iid}/notes?per_page=15"
103
-
104
- fetch_json(url.join)
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
105
121
  end
106
122
 
107
- def delete_award_emoji(url)
108
- delete_json(url)
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
109
143
  end
110
144
 
111
- def fetch_feature_flag_logs(date:, page:)
112
- url = ["#{API_URL}/projects/"]
113
- url << "#{FEATURE_FLAG_LOG_PROJECT_ID}/issues"
114
- url << "?created_after=#{date}"
115
- url << "&per_page=100"
116
- url << "&page=#{page}"
117
-
118
- fetch_json(url.join)
145
+ # Fetch ongoing incidents
146
+ #
147
+ # @return [Array<Gitlab::ObjectifiedHash>]
148
+ def incidents
149
+ gitlab.issues(INFRA_TEAM_PROD_PROJECT_ID, order_by: "updated_at", state: "opened", labels: "incident")
119
150
  end
120
151
 
121
152
  private
122
153
 
123
- def post_json(url, body)
124
- options = {
125
- body: { body: body },
126
- headers: {
127
- "Authorization" => "Bearer #{@token}"
128
- }
129
- }
154
+ attr_reader :token
130
155
 
131
- response = HTTParty.post(url, options)
132
- handle_response(response)
133
- end
134
-
135
- def delete_json(url)
136
- response = HTTParty.delete(url, headers: header)
137
- handle_response(response)
138
- end
139
-
140
- def handle_response(response)
141
- # puts response.to_json
142
- response
143
- end
144
-
145
- def fetch_json(url)
146
- response = HTTParty.get(url, headers: header)
147
-
148
- if response.code != 200
149
- puts "Response error code \"#{response.code}\" - Unable to sync with GitLab"
150
- exit 1
151
- end
152
-
153
- 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
+ )
154
164
  end
155
165
  end
156
166
  end
data/lib/dri/cli.rb CHANGED
@@ -34,6 +34,18 @@ module Dri
34
34
  end
35
35
  map %w[--version -v] => :version
36
36
 
37
+ desc 'incidents', 'View current incidents'
38
+ method_option :help, aliases: '-h', type: :boolean,
39
+ desc: 'Display usage information'
40
+ def incidents(*)
41
+ if options[:help]
42
+ invoke :help, ['incidents']
43
+ else
44
+ require_relative 'commands/incidents'
45
+ Dri::Commands::Incidents.new(options).execute
46
+ end
47
+ end
48
+
37
49
  require_relative 'commands/rm'
38
50
  register Dri::Commands::Rm, 'rm', 'rm [SUBCOMMAND]', 'Remove triage-related items'
39
51
 
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
 
@@ -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)
127
+ TTY::Prompt.new(**options.merge(interrupt: :exit))
127
128
  end
128
129
  end
129
130
  end
@@ -10,6 +10,13 @@ module Dri
10
10
  include Dri::Utils::Table
11
11
  using Refinements
12
12
 
13
+ SORT_BY_OPTIONS = {
14
+ title: 0,
15
+ triaged: 1,
16
+ author: 2,
17
+ url: 3
18
+ }.freeze
19
+
13
20
  def initialize(options)
14
21
  @options = options
15
22
  @today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
@@ -33,20 +40,19 @@ module Dri
33
40
  spinner.run do # rubocop:disable Metrics/BlockLength
34
41
  response = api_client.fetch_failures(date: @today_iso_format, state: 'opened')
35
42
 
36
- if response.nil?
43
+ if response.empty?
37
44
  logger.info 'Life is great, there are no new failures today!'
38
45
  exit 0
39
46
  end
40
47
 
41
48
  response.each do |failure|
42
- title = failure['title'].truncate(60)
43
- author = failure['author']['username']
44
- url = failure['web_url']
45
- 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
46
52
  triaged = add_color('x', :red)
47
53
 
48
- emoji_awards = api_client.fetch_awarded_emojis(award_emoji_url).find do |e|
49
- (e['name'] == emoji) && (e['user']['username'] == username)
54
+ emoji_awards = api_client.fetch_awarded_emojis(failure.iid).find do |e|
55
+ e.name == emoji && e.to_h.dig('user', 'username') == username
50
56
  end
51
57
 
52
58
  if emoji_awards
@@ -55,7 +61,7 @@ module Dri
55
61
  end
56
62
 
57
63
  if @options[:urgent]
58
- labels = failure['labels']
64
+ labels = failure.labels
59
65
 
60
66
  labels.each do |label|
61
67
  if label.include?('found:canary.gitlab.com' && 'found:canary.staging.gitlab.com')
@@ -65,6 +71,8 @@ module Dri
65
71
  end
66
72
 
67
73
  failures << [title, triaged, author, url]
74
+
75
+ failures.sort_by! { |e| e[SORT_BY_OPTIONS[@options[:sort_by].to_sym]] } if @options[:sort_by]
68
76
  end
69
77
  end
70
78
 
@@ -35,44 +35,37 @@ module Dri
35
35
 
36
36
  logger.info "Fetching today's feature flag changes..."
37
37
 
38
- spinner.run do # rubocop:disable Metrics/BlockLength
39
- page = 1
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
38
+ spinner.run do
39
+ response = api_client.fetch_feature_flag_logs(@today_iso_format)
40
+ if response.empty?
41
+ logger.info 'It\'s been quiet...no feature flag changes for today 👀'
42
+ break
43
+ end
47
44
 
48
- response.each do |feature_flag|
49
- summary = feature_flag['title']
45
+ response.each do |feature_flag|
46
+ summary = feature_flag.title
50
47
 
51
- substrings = ["set to \"true\"", "set to \"false\""]
52
- next unless substrings.any? { |substr| summary.include?(substr) }
48
+ substrings = ["set to \"true\"", "set to \"false\""]
49
+ next unless substrings.any? { |substr| summary.include?(substr) }
53
50
 
54
- changed_on = feature_flag['description'][/(?<=Changed on \(in UTC\): ).+?(?=\n)/].delete('`')
55
- url = feature_flag['web_url']
51
+ changed_on = feature_flag.description[/(?<=Changed on \(in UTC\): ).+?(?=\n)/].delete('`')
52
+ url = feature_flag.web_url
56
53
 
57
- feature_flag_data = [summary, changed_on, url]
54
+ feature_flag_data = [summary, changed_on, url]
58
55
 
59
- labels = feature_flag['labels']
60
- host_label = labels.select { |label| /^host::/.match(label) }.join('')
56
+ labels = feature_flag.labels
57
+ host_label = labels.select { |label| /^host::/.match(label) }.join('')
61
58
 
62
- case host_label
63
- when PRODUCTION
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
59
+ case host_label
60
+ when PRODUCTION
61
+ prod_feature_flags << feature_flag_data
62
+ when STAGING
63
+ staging_feature_flags << feature_flag_data
64
+ when STAGING_REF
65
+ staging_ref_feature_flags << feature_flag_data
66
+ when PREPROD
67
+ preprod_feature_flags << feature_flag_data
72
68
  end
73
-
74
- page += 1
75
- break if response.count < 100 || response.nil?
76
69
  end
77
70
  end
78
71
 
@@ -37,8 +37,8 @@ module Dri
37
37
  )
38
38
 
39
39
  mrs = response.each_with_object([]) do |mr, found_mrs|
40
- title = mr['title'][13..].truncate(60) # remove the "[QUARANTINE] " prefix
41
- url = mr['web_url']
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
@@ -17,7 +17,6 @@ module Dri
17
17
  nightly
18
18
  production
19
19
  staging-canary
20
- staging-orchestrated
21
20
  staging-ref
22
21
  staging
23
22
  ]
@@ -48,8 +47,8 @@ module Dri
48
47
 
49
48
  output.puts "♦♦♦♦♦ #{add_color(pipeline, :black, :on_white)}♦♦♦♦♦\n\n"
50
49
 
51
- response.each do |pipeline|
52
- output.puts "#{title_label} #{pipeline['title']}\n#{url_label} #{pipeline['web_url']}"
50
+ response.each do |test_case|
51
+ output.puts "#{title_label} #{test_case.title}\n#{url_label} #{test_case.web_url}"
53
52
  output.puts "#{divider}\n"
54
53
  end
55
54
  end
@@ -50,6 +50,8 @@ module Dri
50
50
  desc: 'Display usage information'
51
51
  method_option :urgent, type: :boolean,
52
52
  desc: 'Shows failures that quickly escalated'
53
+ method_option :sort_by, type: :string,
54
+ desc: 'Shows failures in specified order'
53
55
  def failures(*)
54
56
  if options[:help]
55
57
  invoke :help, ['failures']
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../command'
4
+ require_relative '../utils/table'
5
+
6
+ module Dri
7
+ module Commands
8
+ class Incidents < Dri::Command
9
+ include Dri::Utils::Table
10
+ using Refinements
11
+
12
+ def initialize(options)
13
+ @options = options
14
+ end
15
+
16
+ def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize
17
+ verify_config_exists
18
+
19
+ incident = add_color('Incident', :bright_yellow)
20
+ service = add_color('Service', :bright_yellow)
21
+ status = add_color('Status', :bright_yellow)
22
+ url = add_color('URL', :bright_yellow)
23
+
24
+ header = [incident, service, status, url]
25
+
26
+ logger.info "Looking for open incidents..."
27
+ incidents = []
28
+
29
+ spinner.run do
30
+ response = api_client.incidents
31
+
32
+ if response.nil?
33
+ logger.info 'Hooray, no active incidents 🎉.'
34
+ break
35
+ end
36
+
37
+ response.each do |incident|
38
+ title = incident.title.truncate(70)
39
+ url = incident.web_url
40
+ labels = incident.labels
41
+ status = "N/A"
42
+ service = "N/A"
43
+
44
+ labels.each do |label|
45
+ status = label.gsub!('Incident::', ' ').to_s if label.include? "Incident::"
46
+ service = label.gsub!('Service::', ' ').to_s if label.include? "Service::"
47
+ end
48
+
49
+ incidents << [title, service, status, url]
50
+ end
51
+ end
52
+ print_table(header, incidents, alignments: [:left, :center, :center, :center])
53
+ output.puts(<<~MSG)
54
+ Found: #{incidents.size} incident(s).
55
+ MSG
56
+ end
57
+ end
58
+ end
59
+ end
@@ -36,7 +36,17 @@ module Dri
36
36
 
37
37
  logger.info "Assembling the report... "
38
38
  # sets each failure on the table
39
- action_options = ["pinged SET", "reproduced", "transient", "quarantined"]
39
+ action_options = [
40
+ "pinged SET",
41
+ "reproduced",
42
+ "transient",
43
+ "quarantined",
44
+ "active investigation",
45
+ "blocking pipelines",
46
+ "awaiting for a fix to merge",
47
+ "notified the team",
48
+ "due to feature flag"
49
+ ]
40
50
 
41
51
  spinner.start
42
52
  issues.each do |issue|
@@ -45,7 +55,8 @@ module Dri
45
55
  if @options[:actions]
46
56
  actions = prompt.multi_select(
47
57
  "Please mark the actions on #{add_color(issue['title'], :yellow)}: ",
48
- action_options
58
+ action_options,
59
+ per_page: 9
49
60
  )
50
61
  end
51
62
 
@@ -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['web_url']}..."
32
+ logger.info "Removing #{emoji} from #{issue.web_url}..."
33
33
 
34
- award_emoji_url = issue["_links"]["award_emoji"]
34
+ response = api_client.fetch_awarded_emojis(issue.iid)
35
35
 
36
- response = api_client.fetch_awarded_emojis(award_emoji_url)
36
+ emoji_found = response.find { |e| e.name == emoji && e.to_h.dig('user', 'username') == username }
37
37
 
38
- emoji_found = response.find { |e| e['name'] == emoji && e['user']['username'] == username }
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,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(issue_iid: iid)
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
 
@@ -59,7 +61,7 @@ module Dri
59
61
  label_pipeline_map = {
60
62
  'gitlab.com' => '/quality/production',
61
63
  'canary.gitlab.com' => '/quality/canary',
62
- # 'canary.staging.gitlab.com' => '',
64
+ 'canary.staging.gitlab.com' => '/quality/staging-canary',
63
65
  'main' => '/gitlab-org/gitlab-qa-mirror',
64
66
  'master' => '/gitlab-org/gitlab-qa-mirror',
65
67
  'nightly' => '/quality/nightly',
@@ -69,7 +71,7 @@ module Dri
69
71
  'release' => '/quality/release'
70
72
  }
71
73
 
72
- failure_notes = @api_client.fetch_failure_notes(issue_iid: iid)
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["body"].include?(label_pipeline_map.fetch(pipeline))
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["body"], %w[https])
89
+ pipeline_link = URI.extract(note.body, %w[https])
88
90
  end
89
91
 
90
92
  unless pipeline_in_notes_found
@@ -102,16 +104,25 @@ module Dri
102
104
  linked.join(', ')
103
105
  end
104
106
 
105
- def actions_status_template(failure_type, assigned_status, actions_opts)
106
- notified_set = ''
107
+ def actions_status_template(failure_type, assigned_status, actions_opts) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity/MethodLength
107
108
  quarantined = ''
108
109
  reproduced = ''
109
110
  transient = ''
111
+ active_investigation = ''
112
+ blocking_pipelines = ''
113
+ wait_for_fix = ''
114
+ notified_team = ''
115
+ feature_flag = ''
110
116
 
111
117
  notified_set = '<li>[x] notified SET</li>' if actions_opts.include? 'pinged SET'
112
118
  quarantined = '<li>[x] quarantined</li>' if actions_opts.include? 'quarantined'
113
119
  reproduced = '<li>[x] reproduced</li>' if actions_opts.include? 'reproduced'
114
120
  transient = '<li>[x] transient</li>' if actions_opts.include? 'transient'
121
+ active_investigation = '<li>[x] active investigation</li>' if actions_opts.include? 'active investigation'
122
+ blocking_pipelines = '<li>[x] is blocking pipelines</li>' if actions_opts.include? 'blocking pipelines'
123
+ wait_for_fix = '<li>[x] waiting for fix to merge</li>' if actions_opts.include? 'awaiting for a fix to merge'
124
+ notified_team = '<li>[x] notified the team</li>' if actions_opts.include? 'notified the team'
125
+ feature_flag = '<li>[x] due to feature flag</li>' if actions_opts.include? 'due to feature flag'
115
126
 
116
127
  action_status = ["<i>Status:</i><ul>"]
117
128
  action_status << "<li>#{failure_type}</li>"
@@ -120,13 +131,18 @@ module Dri
120
131
  action_status << quarantined
121
132
  action_status << reproduced
122
133
  action_status << transient
134
+ action_status << active_investigation
135
+ action_status << blocking_pipelines
136
+ action_status << wait_for_fix
137
+ action_status << notified_team
138
+ action_status << feature_flag
123
139
 
124
140
  action_status.join
125
141
  end
126
142
 
127
143
  def actions_fixes_template(related_mrs)
128
144
  mrs = related_mrs.each_with_object(['<i>Potential fixes:</i><br>']) do |mr, fixes|
129
- fixes << "<li>[#{mr['title']}](#{mr['web_url']})</li>"
145
+ fixes << "<li>[#{mr['title'].truncate(40)}](#{mr['web_url']})</li>"
130
146
  end
131
147
 
132
148
  "<ul>#{mrs.join}</ul>"
@@ -177,7 +193,7 @@ module Dri
177
193
  path = "Failure in `#{path.strip}`" if path.delete_prefix!('Failure in ')
178
194
  path = path.strip.gsub(' ', '&nbsp;') if desc
179
195
 
180
- "#{path} | #{desc}"
196
+ [path, desc].compact.join(' | ')
181
197
  end
182
198
  end
183
199
  end
@@ -0,0 +1 @@
1
+ #
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.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
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.2.0
4
+ version: 0.4.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-04-08 00:00:00.000000000 Z
11
+ date: 2022-05-25 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
@@ -296,6 +324,7 @@ files:
296
324
  - lib/dri/commands/fetch/quarantines.rb
297
325
  - lib/dri/commands/fetch/testcases.rb
298
326
  - lib/dri/commands/fetch/triaged.rb
327
+ - lib/dri/commands/incidents.rb
299
328
  - lib/dri/commands/init.rb
300
329
  - lib/dri/commands/profile.rb
301
330
  - lib/dri/commands/publish.rb
@@ -304,8 +333,10 @@ files:
304
333
  - lib/dri/commands/rm/emoji.rb
305
334
  - lib/dri/commands/rm/profile.rb
306
335
  - lib/dri/commands/rm/reports.rb
336
+ - lib/dri/gitlab/issues.rb
307
337
  - lib/dri/refinements/truncate.rb
308
338
  - lib/dri/report.rb
339
+ - lib/dri/templates/incidents/.gitkeep
309
340
  - lib/dri/utils/markdown_lists.rb
310
341
  - lib/dri/utils/table.rb
311
342
  - lib/dri/version.rb