dri 0.1.2 → 0.3.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.
@@ -1,19 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../command'
4
-
5
- require 'tty-table'
4
+ require_relative '../../utils/table'
6
5
 
7
6
  module Dri
8
7
  module Commands
9
8
  class Fetch
10
9
  class Failures < Dri::Command
10
+ include Dri::Utils::Table
11
+ using Refinements
12
+
13
+ SORT_BY_OPTIONS = {
14
+ title: 0,
15
+ triaged: 1,
16
+ author: 2,
17
+ url: 3
18
+ }.freeze
19
+
11
20
  def initialize(options)
12
21
  @options = options
13
22
  @today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
14
23
  end
15
24
 
16
- def execute(input: $stdin, output: $stdout)
25
+ def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
17
26
  verify_config_exists
18
27
 
19
28
  title = add_color('Title', :bright_yellow)
@@ -23,63 +32,63 @@ module Dri
23
32
 
24
33
  failures = []
25
34
  urgent = []
26
- labels = [ title, triaged, author, url ]
35
+ labels = [title, triaged, author, url]
27
36
  triaged_counter = 0
28
37
 
29
- logger.info "Fetching today\'s failures..."
30
-
31
- spinner.run do
38
+ logger.info "Fetching today's failures..."
32
39
 
40
+ spinner.run do # rubocop:disable Metrics/BlockLength
33
41
  response = api_client.fetch_failures(date: @today_iso_format, state: 'opened')
34
42
 
35
43
  if response.nil?
36
- logger.info "Life is great, there are no new failures today!"
44
+ logger.info 'Life is great, there are no new failures today!'
37
45
  exit 0
38
46
  end
39
47
 
40
48
  response.each do |failure|
41
- title = truncate(failure["title"], 60)
42
- author = failure["author"]["username"]
43
- url = failure["web_url"]
44
- award_emoji_url = failure["_links"]["award_emoji"]
49
+ title = failure['title'].truncate(60)
50
+ author = failure['author']['username']
51
+ url = failure['web_url']
52
+ award_emoji_url = failure['_links']['award_emoji']
45
53
  triaged = add_color('x', :red)
46
54
 
47
- emoji_awards = api_client.fetch_awarded_emojis(award_emoji_url)
55
+ emoji_awards = api_client.fetch_awarded_emojis(award_emoji_url).find do |e|
56
+ (e['name'] == emoji) && (e['user']['username'] == username)
57
+ end
48
58
 
49
- triaged = add_color('✓', :green) && triaged_counter += 1 if emoji_awards.find do |e|
50
- e['name'] == emoji && e['user']['username'] == @username
59
+ if emoji_awards
60
+ triaged = add_color('', :green)
61
+ triaged_counter += 1
51
62
  end
52
63
 
53
64
  if @options[:urgent]
54
- labels = failure["labels"]
65
+ labels = failure['labels']
55
66
 
56
67
  labels.each do |label|
57
- if label.include? ("found:canary.gitlab.com" && "found:canary.staging.gitlab.com")
68
+ if label.include?('found:canary.gitlab.com' && 'found:canary.staging.gitlab.com')
58
69
  urgent << [title, triaged, author, url]
59
70
  end
60
71
  end
61
72
  end
62
73
 
63
74
  failures << [title, triaged, author, url]
75
+
76
+ failures.sort_by! { |e| e[SORT_BY_OPTIONS[@options[:sort_by].to_sym]] } if @options[:sort_by]
64
77
  end
65
78
  end
66
79
 
67
80
  if @options[:urgent]
68
- table = TTY::Table.new(labels, urgent)
69
- puts table.render(:ascii, resize: true, alignments: [:center, :center, :center, :center])
70
- output.puts "\nFound: #{urgent.size} urgent failures, ocurring in both canary.gitlab.com and canary.staging.gitlab.com."
81
+ print_table(labels, urgent, alignments: [:left, :center, :center, :left])
82
+ output.puts(<<~MSG)
83
+ Found: #{urgent.size} urgent failures, occurring in both canary.gitlab.com and canary.staging.gitlab.com.
84
+ MSG
71
85
  else
72
- table = TTY::Table.new(labels, failures)
73
- puts table.render(:ascii, resize: true, alignments: [:center, :center, :center, :center])
74
- output.puts "\nFound: #{failures.size} failures, of these #{triaged_counter} have been triaged with a #{emoji}."
86
+ print_table(labels, failures, alignments: [:left, :center, :center, :left])
87
+ output.puts(<<~MSG)
88
+ Found: #{failures.size} failures, of these #{triaged_counter} have been triaged with a #{emoji}.
89
+ MSG
75
90
  end
76
91
  end
77
-
78
- private
79
-
80
- def truncate(string, max)
81
- string.length > max ? "#{string[0...max]}..." : string
82
- end
83
92
  end
84
93
  end
85
94
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../command'
4
+ require_relative '../../utils/table'
5
+
6
+ module Dri
7
+ module Commands
8
+ class Fetch
9
+ class FeatureFlags < Dri::Command
10
+ include Dri::Utils::Table
11
+
12
+ PRODUCTION = 'host::gitlab.com'
13
+ STAGING = 'host::staging.gitlab.com'
14
+ STAGING_REF = 'host::staging-ref.gitlab.com'
15
+ PREPROD = 'host::pre.gitlab.com'
16
+
17
+ def initialize(options)
18
+ @options = options
19
+ @today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
20
+ end
21
+
22
+ def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
23
+ verify_config_exists
24
+
25
+ summary = add_color('Summary', :bright_yellow)
26
+ changed_on = add_color('Changed(UTC)', :bright_yellow)
27
+ url = add_color('URL', :bright_yellow)
28
+
29
+ prod_feature_flags = []
30
+ staging_feature_flags = []
31
+ staging_ref_feature_flags = []
32
+ preprod_feature_flags = []
33
+
34
+ headers = [summary, changed_on, url]
35
+
36
+ logger.info "Fetching today's feature flag changes..."
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
47
+
48
+ response.each do |feature_flag|
49
+ summary = feature_flag['title']
50
+
51
+ substrings = ["set to \"true\"", "set to \"false\""]
52
+ next unless substrings.any? { |substr| summary.include?(substr) }
53
+
54
+ changed_on = feature_flag['description'][/(?<=Changed on \(in UTC\): ).+?(?=\n)/].delete('`')
55
+ url = feature_flag['web_url']
56
+
57
+ feature_flag_data = [summary, changed_on, url]
58
+
59
+ labels = feature_flag['labels']
60
+ host_label = labels.select { |label| /^host::/.match(label) }.join('')
61
+
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
72
+ end
73
+
74
+ page += 1
75
+ break if response.count < 100 || response.nil?
76
+ end
77
+ end
78
+
79
+ print_results('Production', headers, prod_feature_flags, output) unless prod_feature_flags.empty?
80
+ print_results('Staging', headers, staging_feature_flags, output) unless staging_feature_flags.empty?
81
+ print_results('Staging Ref', headers, staging_ref_feature_flags, output) unless staging_ref_feature_flags.empty? # rubocop:disable Layout/LineLength
82
+ print_results('Preprod', headers, preprod_feature_flags, output) unless preprod_feature_flags.empty?
83
+ end
84
+
85
+ private
86
+
87
+ def print_results(env, headers, rows, output_src)
88
+ puts "\n#{add_color(env, :bright_blue)}"
89
+ print_table(headers, rows)
90
+ output_src.puts "\nFound: #{rows.count} feature flag change(s) set to true or false on #{env}."
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../command'
4
+ require_relative '../../utils/table'
5
+
6
+ module Dri
7
+ module Commands
8
+ class Fetch
9
+ class Quarantines < Dri::Command
10
+ include Dri::Utils::Table
11
+ using Refinements
12
+
13
+ def initialize(options, search: '[QUARANTINE]')
14
+ @options = options
15
+ @search = search
16
+
17
+ @today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
18
+ end
19
+
20
+ def execute(input: $stdin, output: $stdout)
21
+ verify_config_exists
22
+ title = add_color('Example name', :bright_yellow)
23
+ url = add_color('URL', :bright_yellow)
24
+
25
+ headers = [title, url]
26
+ mrs = []
27
+
28
+ logger.info "Fetching #{@search} MRs..."
29
+
30
+ spinner.run do
31
+ response = api_client.fetch_mrs(
32
+ project_id: 'gitlab-org/gitlab',
33
+ labels: 'QA,Quality',
34
+ search: @search,
35
+ in: :title,
36
+ state: :opened
37
+ )
38
+
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']
42
+
43
+ found_mrs << [title, url]
44
+ end
45
+ end
46
+
47
+ print_table(headers, mrs, alignments: [:left, :left])
48
+ output.puts "Found #{mrs.size} open #{@search} MRs"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -8,13 +8,21 @@ module Dri
8
8
  module Commands
9
9
  class Fetch
10
10
  class Testcases < Dri::Command
11
-
12
11
  def initialize(options)
13
12
  @options = options
14
- @available_pipelines = %w(main canary master nightly production staging-canary staging-orchestrated staging-ref staging)
13
+ @available_pipelines = %w[
14
+ main
15
+ canary
16
+ master
17
+ nightly
18
+ production
19
+ staging-canary
20
+ staging-ref
21
+ staging
22
+ ]
15
23
  end
16
24
 
17
- def execute(input: $stdin, output: $stdout)
25
+ def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize
18
26
  verify_config_exists
19
27
 
20
28
  if @options[:filter_pipelines]
@@ -31,22 +39,20 @@ module Dri
31
39
 
32
40
  pipelines = @options[:filter_pipelines] ? filtered_pipelines : @available_pipelines
33
41
 
34
- if pipelines.empty?
35
- logger.error "No pipelines selected to continue to fetch testcases."
36
- end
42
+ logger.error "No pipelines selected to continue to fetch testcases." if pipelines.empty?
37
43
 
38
44
  pipelines.each do |pipeline|
39
45
  logger.info "Fetching failing testcases in #{pipeline}\n"
40
46
  response = api_client.fetch_failing_testcases(pipeline, state: 'opened')
41
-
47
+
42
48
  output.puts "♦♦♦♦♦ #{add_color(pipeline, :black, :on_white)}♦♦♦♦♦\n\n"
43
49
 
44
50
  response.each do |pipeline|
45
- output.puts "#{title_label} #{pipeline["title"]}\n#{url_label} #{pipeline["web_url"]}"
51
+ output.puts "#{title_label} #{pipeline['title']}\n#{url_label} #{pipeline['web_url']}"
46
52
  output.puts "#{divider}\n"
47
- end
53
+ end
48
54
  end
49
-
55
+
50
56
  spinner.stop
51
57
  end
52
58
  end
@@ -1,26 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../../command'
2
- require 'tty-table'
4
+ require_relative '../../utils/table'
3
5
 
4
6
  module Dri
5
7
  module Commands
6
8
  class Fetch
7
9
  class Triaged < Dri::Command
10
+ include Dri::Utils::Table
11
+ using Refinements
12
+
8
13
  def initialize(options)
9
14
  @options = options
10
15
  end
11
16
 
12
- def execute(input: $stdin, output: $stdout)
17
+ def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize
13
18
  verify_config_exists
14
19
 
15
20
  title = add_color('Title', :magenta)
16
21
  url = add_color('URL', :magenta)
17
22
  type_label = add_color('Type', :magenta)
18
23
 
19
- table_labels = [ title, url, type_label ]
24
+ table_labels = [title, url, type_label]
20
25
  failures_triaged = []
21
26
 
22
27
  logger.info "Fetching your triaged failures..."
23
- spinner.start
28
+ spinner.start
24
29
 
25
30
  response = api_client.fetch_triaged_failures(emoji: emoji, state: 'opened')
26
31
 
@@ -30,7 +35,7 @@ module Dri
30
35
  end
31
36
 
32
37
  response.each do |triaged|
33
- title = truncate(triaged["title"], 70)
38
+ title = triaged["title"].truncate(70)
34
39
  url = triaged["web_url"]
35
40
  labels = triaged["labels"]
36
41
  type = ""
@@ -39,21 +44,12 @@ module Dri
39
44
  type = label.gsub!('failure::', ' ').to_s if label.include? "failure::"
40
45
  end
41
46
 
42
- labels = triaged["labels"]
43
-
44
47
  failures_triaged << [title, url, type]
45
48
  end
46
49
 
47
50
  spinner.stop
48
51
 
49
- table = TTY::Table.new(table_labels,failures_triaged)
50
- puts table.render(:ascii, resize: true, alignments: [:center, :center, :center])
51
- end
52
-
53
- private
54
-
55
- def truncate(string, max)
56
- string.length > max ? "#{string[0...max]}..." : string
52
+ print_table(table_labels, failures_triaged)
57
53
  end
58
54
  end
59
55
  end
@@ -7,6 +7,18 @@ module Dri
7
7
  class Fetch < Thor
8
8
  namespace :fetch
9
9
 
10
+ desc 'featureflags', 'Display feature flag changes for today'
11
+ method_option :help, aliases: '-h', type: :boolean,
12
+ desc: 'Display usage information'
13
+ def featureflags(*)
14
+ if options[:help]
15
+ invoke :help, ['featureflags']
16
+ else
17
+ require_relative 'fetch/featureflags'
18
+ Dri::Commands::Fetch::FeatureFlags.new(options).execute
19
+ end
20
+ end
21
+
10
22
  desc 'triaged', 'Command description...'
11
23
  method_option :help, aliases: '-h', type: :boolean,
12
24
  desc: 'Display usage information'
@@ -23,7 +35,7 @@ module Dri
23
35
  method_option :help, aliases: '-h', type: :boolean,
24
36
  desc: 'Display usage information'
25
37
  method_option :filter_pipelines, type: :boolean,
26
- desc: 'Filter by pipeline'
38
+ desc: 'Filter by pipeline'
27
39
  def testcases(*)
28
40
  if options[:help]
29
41
  invoke :help, ['testcases']
@@ -37,7 +49,9 @@ module Dri
37
49
  method_option :help, aliases: '-h', type: :boolean,
38
50
  desc: 'Display usage information'
39
51
  method_option :urgent, type: :boolean,
40
- desc: 'Shows failures that quickly escalated'
52
+ desc: 'Shows failures that quickly escalated'
53
+ method_option :sort_by, type: :string,
54
+ desc: 'Shows failures in specified order'
41
55
  def failures(*)
42
56
  if options[:help]
43
57
  invoke :help, ['failures']
@@ -46,6 +60,28 @@ module Dri
46
60
  Dri::Commands::Fetch::Failures.new(options).execute
47
61
  end
48
62
  end
63
+
64
+ desc 'quarantines', 'Display open quarantine MRs'
65
+ method_option :help, aliases: '-h', type: :boolean,
66
+ desc: 'Display usage information'
67
+ def quarantines(*)
68
+ if options[:help]
69
+ invoke :help, ['quarantines']
70
+ else
71
+ require_relative 'fetch/quarantines'
72
+ Dri::Commands::Fetch::Quarantines.new(options, search: '[QUARANTINE]').execute
73
+ end
74
+ end
75
+
76
+ desc 'dequarantines', 'Display open dequarantine MRs'
77
+ method_option :help, aliases: '-h', type: :boolean,
78
+ desc: 'Display usage information'
79
+ def dequarantines(*)
80
+ return invoke :help, %w[quarantines] if options[:help]
81
+
82
+ require_relative 'fetch/quarantines'
83
+ Dri::Commands::Fetch::Quarantines.new(options, search: '[DEQUARANTINE]').execute
84
+ end
49
85
  end
50
86
  end
51
87
  end
@@ -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
+ exit 0
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
@@ -10,11 +10,11 @@ module Dri
10
10
  def initialize(options)
11
11
  @options = options
12
12
 
13
- font = TTY::Font.new(:doom)
13
+ font = TTY::Font.new(:doom)
14
14
  puts pastel.yellow(font.write("DRI"))
15
15
  end
16
16
 
17
- def execute(input: $stdin, output: $stdout)
17
+ def execute(input: $stdin, output: $stdout) # rubocop:disable Metrics/AbcSize
18
18
  output.puts "🤖 Welcome to DRI 🤖\n"
19
19
 
20
20
  logger.info "🔎 Scanning for existing configurations...\n"
@@ -22,14 +22,16 @@ module Dri
22
22
  if config.exist?
23
23
  overwrite = prompt.yes?("There is already a configuration initialized. Would you like to overwrite it?")
24
24
  unless overwrite
25
- output.puts "Using existing configuration. To view configuration in use try #{add_color('dri profile', :yellow)}."
25
+ output.puts(
26
+ "Using existing configuration. To view configuration in use try #{add_color('dri profile', :yellow)}."
27
+ )
26
28
  exit 0
27
29
  end
28
30
  end
29
31
 
30
32
  @username = prompt.ask("What is your GitLab username?")
31
33
  @token = prompt.mask("Please provide your GitLab personal access token:")
32
- @timezone = prompt.select("Choose your current timezone?", %w(EMEA AMER APAC))
34
+ @timezone = prompt.select("Choose your current timezone?", %w[EMEA AMER APAC])
33
35
  @emoji = prompt.ask("Have a triage emoji?")
34
36
 
35
37
  if (@emoji || @token || @username).nil?
@@ -43,7 +45,7 @@ module Dri
43
45
  config.set(:settings, :emoji, value: @emoji)
44
46
  config.write(force: true)
45
47
 
46
- logger.success "✅ We're ready to go 🚀"
48
+ logger.success "✅ We're ready to go 🚀"
47
49
  end
48
50
  end
49
51
  end
@@ -20,10 +20,15 @@ module Dri
20
20
  logger.info "🔎 Looking for profiles...\n"
21
21
 
22
22
  if config.exist?
23
- box = TTY::Box.frame(width: 30, height: 10, align: :center, padding: 1, title: {top_left: add_color('PROFILE:', :bright_cyan)}, border: :thick) do
24
- pretty_print_profile
25
- end
26
- print box
23
+ frame_args = {
24
+ width: 30,
25
+ height: 10,
26
+ align: :center,
27
+ padding: 1,
28
+ border: :thick,
29
+ title: { top_left: add_color('PROFILE:', :bright_cyan) }
30
+ }
31
+ output.print TTY::Box.frame(**frame_args) { pretty_print_profile.strip }
27
32
  output.puts "☝️ To modify this profile try passing #{add_color('dri profile --edit', :yellow)}.\n"
28
33
  else
29
34
  logger.error "Oops.. Profile not found. Try creating one using #{add_color('dri init', :yellow)}."
@@ -31,7 +36,11 @@ module Dri
31
36
  end
32
37
 
33
38
  def pretty_print_profile
34
- "#{add_color('User:', :bright_cyan)} #{username}\n #{add_color('Token:', :bright_cyan)} #{token}\n #{add_color('Timezone:', :bright_cyan)} #{timezone}\n #{add_color('Emoji:', :bright_cyan)} #{emoji}"
39
+ <<~PROFILE
40
+ #{add_color('User:', :bright_cyan)} #{username}\n #{add_color('Token:', :bright_cyan)} #{token}
41
+ #{add_color('Timezone:', :bright_cyan)} #{timezone}
42
+ #{add_color('Emoji:', :bright_cyan)} #{emoji}
43
+ PROFILE
35
44
  end
36
45
  end
37
46
  end