dri 0.1.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 +7 -0
- data/.editorconfig +9 -0
- data/.gitignore +14 -0
- data/.gitlab-ci.yml +28 -0
- data/.rspec +3 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +107 -0
- data/LICENSE.txt +20 -0
- data/README.md +132 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dri.gemspec +41 -0
- data/exe/dri +18 -0
- data/lib/dri/api_client.rb +125 -0
- data/lib/dri/cli.rb +74 -0
- data/lib/dri/command.rb +127 -0
- data/lib/dri/commands/fetch/failures.rb +69 -0
- data/lib/dri/commands/fetch/testcases.rb +51 -0
- data/lib/dri/commands/fetch.rb +37 -0
- data/lib/dri/commands/init.rb +49 -0
- data/lib/dri/commands/profile.rb +38 -0
- data/lib/dri/commands/publish/report.rb +86 -0
- data/lib/dri/commands/publish.rb +26 -0
- data/lib/dri/commands/rm/emoji.rb +51 -0
- data/lib/dri/commands/rm.rb +24 -0
- data/lib/dri/report.rb +121 -0
- data/lib/dri/utils/markdown_lists.rb +20 -0
- data/lib/dri/version.rb +3 -0
- data/lib/dri.rb +4 -0
- metadata +311 -0
data/lib/dri/command.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'command'
|
3
|
+
require_relative 'api_client'
|
4
|
+
|
5
|
+
require "tty-config"
|
6
|
+
require "pastel"
|
7
|
+
require 'forwardable'
|
8
|
+
|
9
|
+
module Dri
|
10
|
+
class Command
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def_delegators :command, :run
|
14
|
+
attr_reader :config, :emoji, :token, :username, :timezone, :profile
|
15
|
+
|
16
|
+
def pastel(**options)
|
17
|
+
Pastel.new(**options)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Main configuration
|
21
|
+
def config
|
22
|
+
@config ||= begin
|
23
|
+
config = TTY::Config.new
|
24
|
+
config.filename = ".dri_profile"
|
25
|
+
config.extname = ".yml"
|
26
|
+
config.append_path Dir.pwd
|
27
|
+
config.append_path Dir.home
|
28
|
+
config
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def api_client
|
33
|
+
ApiClient.new(config)
|
34
|
+
end
|
35
|
+
|
36
|
+
def profile
|
37
|
+
@profile ||= config.read
|
38
|
+
end
|
39
|
+
|
40
|
+
def emoji
|
41
|
+
@emoji ||= profile["settings"]["emoji"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def username
|
45
|
+
@username ||= profile["settings"]["user"]
|
46
|
+
end
|
47
|
+
|
48
|
+
def token
|
49
|
+
@token ||= profile["settings"]["token"]
|
50
|
+
end
|
51
|
+
|
52
|
+
def timezone
|
53
|
+
@timezone ||= profile["settings"]["timezone"]
|
54
|
+
end
|
55
|
+
|
56
|
+
def verify_config_exists
|
57
|
+
if !config.exist?
|
58
|
+
logger.error "Oops, could not find a configuration. Try using #{add_color('dri init', :yellow)} first."
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_color(str, *color)
|
64
|
+
@options[:no_color] ? str : pastel.decorate(str, *color)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Execute this command
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def execute(*)
|
71
|
+
raise(
|
72
|
+
NotImplementedError,
|
73
|
+
"#{self.class}##{__method__} must be implemented"
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def logger
|
78
|
+
require 'tty-logger'
|
79
|
+
TTY::Logger.new
|
80
|
+
end
|
81
|
+
|
82
|
+
def spinner
|
83
|
+
require 'tty-spinner'
|
84
|
+
TTY::Spinner.new("[:spinner] ⏳", format: :classic)
|
85
|
+
end
|
86
|
+
|
87
|
+
# The external commands runner
|
88
|
+
#
|
89
|
+
# @see http://www.rubydoc.info/gems/tty-command
|
90
|
+
#
|
91
|
+
# @api public
|
92
|
+
def command(**options)
|
93
|
+
require 'tty-command'
|
94
|
+
TTY::Command.new(options)
|
95
|
+
end
|
96
|
+
|
97
|
+
# The cursor movement
|
98
|
+
#
|
99
|
+
# @see http://www.rubydoc.info/gems/tty-cursor
|
100
|
+
#
|
101
|
+
# @api public
|
102
|
+
def cursor
|
103
|
+
require 'tty-cursor'
|
104
|
+
TTY::Cursor
|
105
|
+
end
|
106
|
+
|
107
|
+
# Open a file or text in the user's preferred editor
|
108
|
+
#
|
109
|
+
# @see http://www.rubydoc.info/gems/tty-editor
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
def editor
|
113
|
+
require 'tty-editor'
|
114
|
+
TTY::Editor
|
115
|
+
end
|
116
|
+
|
117
|
+
# The interactive prompt
|
118
|
+
#
|
119
|
+
# @see http://www.rubydoc.info/gems/tty-prompt
|
120
|
+
#
|
121
|
+
# @api public
|
122
|
+
def prompt(**options)
|
123
|
+
require 'tty-prompt'
|
124
|
+
TTY::Prompt.new(options)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
|
5
|
+
require 'tty-table'
|
6
|
+
|
7
|
+
module Dri
|
8
|
+
module Commands
|
9
|
+
class Fetch
|
10
|
+
class Failures < Dri::Command
|
11
|
+
def initialize(options)
|
12
|
+
@options = options
|
13
|
+
@today_iso_format = Time.now.strftime('%Y-%m-%dT00:00:00Z')
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
verify_config_exists
|
18
|
+
|
19
|
+
title = add_color('Title', :bright_yellow)
|
20
|
+
triaged = add_color('Triaged?', :bright_yellow)
|
21
|
+
author = add_color('Author', :bright_yellow)
|
22
|
+
url = add_color('URL', :bright_yellow)
|
23
|
+
|
24
|
+
failures = []
|
25
|
+
labels = [ title, triaged, author, url ]
|
26
|
+
triaged_counter = 0
|
27
|
+
|
28
|
+
logger.info "Fetching today\'s failures..."
|
29
|
+
|
30
|
+
spinner.run do
|
31
|
+
|
32
|
+
response = api_client.fetch_failures(date: @today_iso_format, state: 'opened')
|
33
|
+
|
34
|
+
if response.nil?
|
35
|
+
logger.info "Life is great, there are no new failures today!"
|
36
|
+
exit 0
|
37
|
+
end
|
38
|
+
|
39
|
+
response.each do |failure|
|
40
|
+
title = truncate(failure["title"], 60)
|
41
|
+
author = failure["author"]["username"]
|
42
|
+
url = failure["web_url"]
|
43
|
+
award_emoji_url = failure["_links"]["award_emoji"]
|
44
|
+
triaged = add_color('x', :red)
|
45
|
+
|
46
|
+
emoji_awards = api_client.fetch_awarded_emojis(award_emoji_url)
|
47
|
+
|
48
|
+
triaged = add_color('✓', :green) && triaged_counter += 1 if emoji_awards.find do |e|
|
49
|
+
e['name'] == emoji && e['user']['username'] == @username
|
50
|
+
end
|
51
|
+
|
52
|
+
failures << [title, triaged, author, url]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
table = TTY::Table.new(labels, failures)
|
57
|
+
puts table.render(:ascii, resize: true, alignments: [:center, :center, :center, :center])
|
58
|
+
output.puts "\nFound: #{failures.size} failures, of these #{triaged_counter} have been triaged with a #{emoji}."
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def truncate(string, max)
|
64
|
+
string.length > max ? "#{string[0...max]}..." : string
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
|
5
|
+
require 'pastel'
|
6
|
+
|
7
|
+
module Dri
|
8
|
+
module Commands
|
9
|
+
class Fetch
|
10
|
+
class Testcases < Dri::Command
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
@options = options
|
14
|
+
@available_pipelines = %w(main canary master nightly production staging-canary staging-orchestrated staging-ref staging)
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute(input: $stdin, output: $stdout)
|
18
|
+
verify_config_exists
|
19
|
+
|
20
|
+
if @options[:filter_pipelines]
|
21
|
+
filtered_pipelines = prompt.multi_select("Select pipelines:", @available_pipelines)
|
22
|
+
end
|
23
|
+
|
24
|
+
logger.info "Fetching currently failing testcases..."
|
25
|
+
|
26
|
+
title_label = add_color('Title:', :bright_yellow)
|
27
|
+
url_label = add_color('URL:', :bright_yellow)
|
28
|
+
divider = add_color('---', :cyan)
|
29
|
+
|
30
|
+
spinner.start
|
31
|
+
|
32
|
+
pipelines = @options[:filter_pipelines] ? filtered_pipelines : @available_pipelines
|
33
|
+
|
34
|
+
pipelines.each do |pipeline|
|
35
|
+
logger.info "Fetching failing testcases in #{pipeline}\n"
|
36
|
+
response = api_client.fetch_failing_testcases(pipeline, state: 'opened')
|
37
|
+
|
38
|
+
output.puts "♦♦♦♦♦ #{add_color(pipeline, :black, :on_white)}♦♦♦♦♦\n\n"
|
39
|
+
|
40
|
+
response.each do |pipeline|
|
41
|
+
output.puts "#{title_label} #{pipeline["title"]}\n#{url_label} #{pipeline["web_url"]}"
|
42
|
+
output.puts "#{divider}\n"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
spinner.stop
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Dri
|
6
|
+
module Commands
|
7
|
+
class Fetch < Thor
|
8
|
+
namespace :fetch
|
9
|
+
|
10
|
+
desc 'testcases', 'Display failing testcases'
|
11
|
+
method_option :help, aliases: '-h', type: :boolean,
|
12
|
+
desc: 'Display usage information'
|
13
|
+
method_option :filter_pipelines, type: :boolean,
|
14
|
+
desc: 'Filter by pipeline'
|
15
|
+
def testcases(*)
|
16
|
+
if options[:help]
|
17
|
+
invoke :help, ['testcases']
|
18
|
+
else
|
19
|
+
require_relative 'fetch/testcases'
|
20
|
+
Dri::Commands::Fetch::Testcases.new(options).execute
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'failures', 'Display failures opened today'
|
25
|
+
method_option :help, aliases: '-h', type: :boolean,
|
26
|
+
desc: 'Display usage information'
|
27
|
+
def failures(*)
|
28
|
+
if options[:help]
|
29
|
+
invoke :help, ['failures']
|
30
|
+
else
|
31
|
+
require_relative 'fetch/failures'
|
32
|
+
Dri::Commands::Fetch::Failures.new(options).execute
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
require "tty-font"
|
6
|
+
require "pastel"
|
7
|
+
|
8
|
+
module Dri
|
9
|
+
module Commands
|
10
|
+
class Init < Dri::Command
|
11
|
+
def initialize(options)
|
12
|
+
font = TTY::Font.new(:doom)
|
13
|
+
puts pastel.yellow(font.write("DRI"))
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
output.puts "🤖 Welcome to DRI 🤖\n"
|
18
|
+
|
19
|
+
logger.info "🔎 Scanning for existing configurations...\n"
|
20
|
+
|
21
|
+
if config.exist?
|
22
|
+
overwrite = prompt.yes?("There is already a configuration initialized. Would you like to overwrite it?")
|
23
|
+
unless overwrite
|
24
|
+
output.puts "Using existing configuration. To view configuration in use try #{add_color('dri profile', :yellow)}."
|
25
|
+
exit 0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
@username = prompt.ask("What is your GitLab username?")
|
30
|
+
@token = prompt.mask("Please provide your GitLab personal access token:")
|
31
|
+
@timezone = prompt.select("Choose your current timezone?", %w(EMEA AMER APAC))
|
32
|
+
@emoji = prompt.ask("Have a triage emoji?")
|
33
|
+
|
34
|
+
if (@emoji || @token || @username).nil?
|
35
|
+
logger.error "Please provide a username, token, timezone and emoji used for triage."
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
config.set(:settings, :user, value: @username)
|
40
|
+
config.set(:settings, :token, value: @token)
|
41
|
+
config.set(:settings, :timezone, value: @timezone)
|
42
|
+
config.set(:settings, :emoji, value: @emoji)
|
43
|
+
config.write(force: true)
|
44
|
+
|
45
|
+
logger.success "✅ We're ready to go 🚀"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
require 'tty-box'
|
6
|
+
|
7
|
+
module Dri
|
8
|
+
module Commands
|
9
|
+
class Profile < Dri::Command
|
10
|
+
def initialize(options)
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(input: $stdin, output: $stdout)
|
15
|
+
if config.exist? && @options["edit"]
|
16
|
+
editor.open(config.source_file)
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
logger.info "🔎 Looking for profiles...\n"
|
21
|
+
|
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
|
27
|
+
output.puts "☝️ To modify this profile try passing #{add_color('dri profile --edit', :yellow)}.\n"
|
28
|
+
else
|
29
|
+
logger.error "Oops.. Profile not found. Try creating one using #{add_color('dri init', :yellow)}."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
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}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative '../../command'
|
2
|
+
require_relative '../../utils/markdown_lists'
|
3
|
+
require_relative "../../report"
|
4
|
+
|
5
|
+
require 'markdown-tables'
|
6
|
+
require 'fileutils'
|
7
|
+
require "uri"
|
8
|
+
|
9
|
+
module Dri
|
10
|
+
module Commands
|
11
|
+
class Publish
|
12
|
+
class Report < Dri::Command
|
13
|
+
def initialize(options)
|
14
|
+
@options = options
|
15
|
+
|
16
|
+
@date = Date.today
|
17
|
+
@time = Time.now.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute(input: $stdin, output: $stdout)
|
21
|
+
verify_config_exists
|
22
|
+
report = Dri::Report.new(config)
|
23
|
+
|
24
|
+
logger.info "Fetching triaged failures with award emoji #{emoji}..."
|
25
|
+
|
26
|
+
spinner.start
|
27
|
+
issues = api_client.fetch_triaged_failures(emoji: emoji, state: 'opened')
|
28
|
+
spinner.stop
|
29
|
+
|
30
|
+
if issues.empty?
|
31
|
+
logger.warn "Found no issues associated with \"#{emoji}\" emoji. Will exit. Bye 👋"
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
|
35
|
+
logger.info "Assembling the report... "
|
36
|
+
# sets each failure on the table
|
37
|
+
spinner.start
|
38
|
+
issues.each do |issue|
|
39
|
+
report.add_failure(issue)
|
40
|
+
end
|
41
|
+
|
42
|
+
if @options[:format] == 'list'
|
43
|
+
# generates markdown list with failures
|
44
|
+
format_style = Utils::MarkdownLists.make_list(report.labels, report.failures) unless report.failures.empty?
|
45
|
+
else
|
46
|
+
# generates markdown table with rows as failures
|
47
|
+
format_style = MarkdownTables.make_table(report.labels, report.failures, is_rows: true, align: %w[c l c l l]) unless report.failures.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
report.set_header(timezone, username)
|
51
|
+
note = "#{report.header}\n\n#{format_style}"
|
52
|
+
|
53
|
+
spinner.stop
|
54
|
+
|
55
|
+
# creates an .md file with the report locally in /handover_reports
|
56
|
+
if @options[:dry_run]
|
57
|
+
logger.info "Downloading the report... "
|
58
|
+
|
59
|
+
spinner.start
|
60
|
+
|
61
|
+
FileUtils.mkdir_p("#{Dir.pwd}/handover_reports")
|
62
|
+
report_path = "handover_reports/report-#{@date}-#{@time}.md"
|
63
|
+
|
64
|
+
File.open(report_path, 'a') do |out_file|
|
65
|
+
out_file.puts note
|
66
|
+
end
|
67
|
+
|
68
|
+
spinner.stop
|
69
|
+
|
70
|
+
output.puts "Done! ✅\n"
|
71
|
+
logger.success "Report is ready at: #{report_path}"
|
72
|
+
exit 0
|
73
|
+
end
|
74
|
+
|
75
|
+
# sends note to the weekly triage report
|
76
|
+
issues = api_client.fetch_current_triage_issue
|
77
|
+
current_issue_iid = issues[0]["iid"]
|
78
|
+
response = api_client.post_triage_report_note(iid: current_issue_iid, body: note)
|
79
|
+
|
80
|
+
output.puts "Done! ✅\n"
|
81
|
+
logger.success "Thanks @#{username}, your report was posted at https://gitlab.com/gitlab-org/quality/dri/-/issues/#{current_issue_iid} 🎉"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Dri
|
6
|
+
module Commands
|
7
|
+
class Publish < Thor
|
8
|
+
|
9
|
+
namespace :publish
|
10
|
+
|
11
|
+
desc 'report', 'Generate a report'
|
12
|
+
method_option :dry_run, type: :boolean,
|
13
|
+
desc: 'Generates a report locally'
|
14
|
+
method_option :format, aliases: '-f', type: :string, :default => "table",
|
15
|
+
desc: 'Formats the report'
|
16
|
+
def report(*)
|
17
|
+
if options[:help]
|
18
|
+
invoke :help, ['report']
|
19
|
+
else
|
20
|
+
require_relative 'publish/report'
|
21
|
+
Dri::Commands::Publish::Report.new(options).execute
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
|
5
|
+
module Dri
|
6
|
+
module Commands
|
7
|
+
class Rm
|
8
|
+
class Emoji < Dri::Command
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(input: $stdin, output: $stdout)
|
14
|
+
verify_config_exists
|
15
|
+
|
16
|
+
remove = prompt.yes? "Are you sure you want to remove all #{emoji} award emojis from issues?"
|
17
|
+
|
18
|
+
unless remove
|
19
|
+
logger.info "Emojis kept in place 👍"
|
20
|
+
exit 0
|
21
|
+
end
|
22
|
+
|
23
|
+
logger.info "Removing #{emoji} from issues..."
|
24
|
+
|
25
|
+
spinner.start
|
26
|
+
|
27
|
+
issues_with_award_emoji = api_client.fetch_triaged_failures(emoji: emoji, state: 'opened')
|
28
|
+
|
29
|
+
spinner.stop
|
30
|
+
|
31
|
+
issues_with_award_emoji.each do |issue|
|
32
|
+
logger.info "Removing #{emoji} from #{issue["web_url"]}..."
|
33
|
+
|
34
|
+
award_emoji_url = issue["_links"]["award_emoji"]
|
35
|
+
|
36
|
+
response = api_client.fetch_awarded_emojis(award_emoji_url)
|
37
|
+
|
38
|
+
emoji_found = response.find { |e| e['name'] == emoji && e['user']['username'] == username }
|
39
|
+
|
40
|
+
if !emoji_found.nil?
|
41
|
+
url = "#{award_emoji_url}/#{emoji_found["id"]}"
|
42
|
+
api_client.delete_award_emoji(url)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
output.puts "Done! ✅"
|
46
|
+
logger.success "Removed #{emoji} from #{issues_with_award_emoji.size} issue(s)."
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Dri
|
6
|
+
module Commands
|
7
|
+
class Rm < Thor
|
8
|
+
|
9
|
+
namespace :rm
|
10
|
+
|
11
|
+
desc 'emoji', 'Remove triage emoji from all failures'
|
12
|
+
method_option :help, aliases: '-h', type: :boolean,
|
13
|
+
desc: 'Display usage information'
|
14
|
+
def emoji(*)
|
15
|
+
if options[:help]
|
16
|
+
invoke :help, ['emoji']
|
17
|
+
else
|
18
|
+
require_relative 'rm/emoji'
|
19
|
+
Dri::Commands::Rm::Emoji.new(options).execute
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/dri/report.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
module Dri
|
2
|
+
class Report
|
3
|
+
attr_reader :header, :failures, :labels
|
4
|
+
|
5
|
+
def initialize(config)
|
6
|
+
@labels = ['Title', 'Issue', 'Pipelines', 'Stack Trace', 'Actions']
|
7
|
+
@failures = []
|
8
|
+
@date = Date.today
|
9
|
+
@today = Date.today.strftime("%Y-%m-%d")
|
10
|
+
@weekday = Date.today.strftime("%A")
|
11
|
+
@header = nil
|
12
|
+
|
13
|
+
@api_client = ApiClient.new(config)
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_header(timezone, username)
|
17
|
+
@header = "# #{timezone}, #{@weekday} - #{@date}\n posted by: @#{username}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_failure(failure)
|
21
|
+
iid = failure["iid"]
|
22
|
+
title = failure["title"]
|
23
|
+
link = failure["web_url"]
|
24
|
+
labels = failure["labels"]
|
25
|
+
created_at = failure["created_at"]
|
26
|
+
assignees = failure["assignees"]
|
27
|
+
award_emoji_url = failure["_links"]["award_emoji"]
|
28
|
+
description = failure["description"]
|
29
|
+
|
30
|
+
related_mrs = @api_client.fetch_related_mrs(issue_iid: iid)
|
31
|
+
emoji = classify_failure_emoji(created_at)
|
32
|
+
emojified_link = "#{emoji} #{link}"
|
33
|
+
|
34
|
+
stack_blob = description.empty? ? "No stack trace found" : description.split("### Stack trace").last.gsub(/\n|`|!|\[|\]/, '').squeeze(" ")[0...250]
|
35
|
+
stack_trace = ":link:[`#{stack_blob}...`](#{link + '#stack-trace'})"
|
36
|
+
|
37
|
+
failure_type = filter_failure_type_labels(labels)
|
38
|
+
assigned_status = assigned?(assignees)
|
39
|
+
pipelines = filter_pipeline_labels(labels)
|
40
|
+
|
41
|
+
linked_pipelines = link_pipelines(iid, pipelines)
|
42
|
+
|
43
|
+
actions = ""
|
44
|
+
actions.concat actions_status_template(failure_type, assigned_status)
|
45
|
+
actions.concat actions_fixes_template(related_mrs)
|
46
|
+
|
47
|
+
@failures << [title, emojified_link, linked_pipelines, stack_trace, actions]
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def link_pipelines(iid, pipelines)
|
53
|
+
failure_notes = @api_client.fetch_failure_notes(issue_iid: iid)
|
54
|
+
|
55
|
+
linked = ""
|
56
|
+
|
57
|
+
pipelines.each do |pipeline|
|
58
|
+
failure_notes.each do |note|
|
59
|
+
if note["body"].include? pipeline
|
60
|
+
pipeline_link = URI.extract(note["body"], %w(https))
|
61
|
+
pipeline_link_sanitized = pipeline_link.join.strip
|
62
|
+
pipeline = "[#{pipeline}](#{pipeline_link_sanitized})"
|
63
|
+
break
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
linked << pipeline
|
68
|
+
end
|
69
|
+
linked
|
70
|
+
end
|
71
|
+
|
72
|
+
def actions_status_template(failure_type, assigned_status)
|
73
|
+
"<i>Status:</i><ul><li>#{failure_type}</li><li>#{assigned_status}</li><li>[ ] notified SET</li><li>[ ] quarantined</li></ul>"
|
74
|
+
end
|
75
|
+
|
76
|
+
def actions_fixes_template(related_mrs)
|
77
|
+
actions_fixes_template = '<ul><i>Potential fixes:</i><br>'
|
78
|
+
related_mrs.each do |mr|
|
79
|
+
actions_fixes_template.concat "<li>[#{mr["title"]}](#{mr["web_url"]})</li>"
|
80
|
+
end
|
81
|
+
actions_fixes_template.concat '</ul>'
|
82
|
+
actions_fixes_template
|
83
|
+
end
|
84
|
+
|
85
|
+
def assigned?(assignees)
|
86
|
+
assignees.empty? ? 'Assigned :x:' : 'Assigned :white_check_mark:'
|
87
|
+
end
|
88
|
+
|
89
|
+
def filter_pipeline_labels(labels)
|
90
|
+
pipelines = []
|
91
|
+
|
92
|
+
labels.each do |label|
|
93
|
+
matchers = { 'found:' => ' ', '.gitlab.com' => ' ' }
|
94
|
+
|
95
|
+
if label.include? "found:"
|
96
|
+
pipeline = label.gsub(/found:|.gitlab.com/) { |match| matchers[match] }
|
97
|
+
pipelines << pipeline.strip
|
98
|
+
end
|
99
|
+
end
|
100
|
+
pipelines
|
101
|
+
end
|
102
|
+
|
103
|
+
def filter_failure_type_labels(labels)
|
104
|
+
labels.each do |label|
|
105
|
+
@type = label.gsub!('failure::', ' ').to_s if label.include? "failure::"
|
106
|
+
end
|
107
|
+
@type
|
108
|
+
end
|
109
|
+
|
110
|
+
def classify_failure_emoji(created_at)
|
111
|
+
new_failure_emoji = ':boom:'
|
112
|
+
known_failure_emoji = ':fire_engine:'
|
113
|
+
|
114
|
+
if created_at.include? @today
|
115
|
+
new_failure_emoji
|
116
|
+
else
|
117
|
+
known_failure_emoji
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|