dri 0.1.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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