dri 0.2.0 → 0.4.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: 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