lazylead 0.5.0 → 0.6.2

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +4 -3
  3. data/bin/lazylead +6 -4
  4. data/lazylead.gemspec +4 -3
  5. data/lib/lazylead/cli/app.rb +11 -3
  6. data/lib/lazylead/email.rb +0 -20
  7. data/lib/lazylead/exchange.rb +16 -29
  8. data/lib/lazylead/log.rb +2 -1
  9. data/lib/lazylead/model.rb +7 -0
  10. data/lib/lazylead/opts.rb +49 -1
  11. data/lib/lazylead/postman.rb +18 -17
  12. data/lib/lazylead/smtp.rb +3 -1
  13. data/lib/lazylead/system/jira.rb +30 -8
  14. data/lib/lazylead/task/accuracy/accuracy.rb +10 -1
  15. data/lib/lazylead/task/accuracy/logs.rb +7 -3
  16. data/lib/lazylead/task/accuracy/records.rb +1 -1
  17. data/lib/lazylead/task/accuracy/servers.rb +1 -1
  18. data/lib/lazylead/task/accuracy/stacktrace.rb +50 -10
  19. data/lib/lazylead/task/accuracy/testcase.rb +23 -6
  20. data/lib/lazylead/task/assignment.rb +96 -0
  21. data/lib/lazylead/task/fix_version.rb +6 -0
  22. data/lib/lazylead/task/svn/diff.rb +77 -0
  23. data/lib/lazylead/task/svn/grep.rb +132 -0
  24. data/lib/lazylead/task/svn/touch.rb +101 -0
  25. data/lib/lazylead/version.rb +1 -1
  26. data/lib/messages/illegal_assignee_change.erb +123 -0
  27. data/lib/messages/illegal_fixversion_change.erb +8 -0
  28. data/lib/messages/svn_diff.erb +110 -0
  29. data/lib/messages/{svn_log.erb → svn_diff_attachment.erb} +19 -9
  30. data/lib/messages/svn_grep.erb +114 -0
  31. data/test/lazylead/model_test.rb +10 -0
  32. data/test/lazylead/opts_test.rb +35 -0
  33. data/test/lazylead/postman_test.rb +8 -5
  34. data/test/lazylead/system/jira_test.rb +14 -7
  35. data/test/lazylead/task/accuracy/logs_test.rb +62 -2
  36. data/test/lazylead/task/accuracy/score_test.rb +46 -0
  37. data/test/lazylead/task/accuracy/stacktrace_test.rb +227 -0
  38. data/test/lazylead/task/accuracy/testcase_test.rb +49 -0
  39. data/test/lazylead/task/assignment_test.rb +53 -0
  40. data/test/lazylead/task/fix_version_test.rb +1 -0
  41. data/test/lazylead/task/savepoint_test.rb +7 -4
  42. data/test/lazylead/task/svn/diff_test.rb +97 -0
  43. data/test/lazylead/task/svn/grep_test.rb +103 -0
  44. data/test/lazylead/task/{touch_test.rb → svn/touch_test.rb} +7 -34
  45. data/upgrades/sqlite/999.testdata.sql +2 -1
  46. metadata +48 -20
  47. data/lib/lazylead/task/touch.rb +0 -119
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8358ec8da9d97621f9c5ec28ddfa3114699f260af8ae4fd9632a998263f795e
4
- data.tar.gz: 4a11f6cbd5cffbc91a2b40a5b7fb6c82d608301d16ebba1d8b2155a256101f48
3
+ metadata.gz: 669da4d2f993894e78fcd42508429667cb29afe9082012c8dadee88e77dc0518
4
+ data.tar.gz: '0976452e6d5aaf5e282f468f05620915e73ee5cb2566ff9e4441d094d4f3dc3f'
5
5
  SHA512:
6
- metadata.gz: 31259a7b7305b519f610e68dbbb7649fee65257415aae4a0bfe1a77bdd88f62662279a4689bb18872d37eba4d8c0b6537ddf5f1a1c415762085e507fb39d05ae
7
- data.tar.gz: 2c6bc24efc4696496c8a0c58c697af93377bc7c6d216141138ce8d18f3606104e7a945c715d3657ebc97ede3f97db586da3a6fe57c01f826eb86c61f024f91b7
6
+ metadata.gz: ec9719d37253c81db0d59d6d3f216e8d677a8d3a4543776409782236c01428fe97a07e3c58e95150c031506d894a8188b2ce551a2ffa79c4b364edf60774ad8a
7
+ data.tar.gz: 57662e5ba79d877e4b7310f0ff20fa767e6b4246ba46f7ffd30afeee86215f311dffeb2e947a86c867b3554ac20b1711b1d69edbd493e971c01d170fccd85aed
data/Rakefile CHANGED
@@ -26,7 +26,7 @@ require "rubygems"
26
26
  require "rake"
27
27
  require "date"
28
28
  require "rdoc"
29
- require "rainbow"
29
+ require "colorize"
30
30
  require "rake/clean"
31
31
 
32
32
  # @todo #/DEV Investigate the possibility of using migrations from active_record
@@ -112,9 +112,10 @@ task :sqlint do
112
112
  total += violations.count { |lint| lint.type == :error }
113
113
  end
114
114
  if total.positive?
115
- abort "#{Rainbow(total).red} SQL violations found."
115
+ abort "#{total.colorize(:red)} SQL violations found."
116
116
  else
117
- puts "#{src.size} files inspected, #{Rainbow('no offenses').green} detected"
117
+ puts "#{src.size} files inspected, #{'no offenses'.colorize(:green)} " \
118
+ "detected"
118
119
  end
119
120
  end
120
121
 
@@ -29,7 +29,7 @@ STDOUT.sync = true
29
29
 
30
30
  require "slop"
31
31
  require "sqlite3"
32
- require "rainbow"
32
+ require "colorize"
33
33
  require "logging"
34
34
  require "backtrace"
35
35
  require "memory_profiler"
@@ -77,8 +77,9 @@ Available options:"
77
77
  exit
78
78
  end
79
79
  end
80
- log.debug("Version: #{Lazylead::VERSION}")
81
- log.debug("Memory footprint at start is #{Lazylead::Allocated.new}")
80
+ log.debug "Version: #{Lazylead::VERSION.colorize(:green)}"
81
+ log.debug "Memory footprint at start is " \
82
+ "#{Lazylead::Allocated.new.to_s.colorize(:light_blue)}."
82
83
  cmd = lambda do
83
84
  Lazylead::CLI::App.new(
84
85
  log,
@@ -103,5 +104,6 @@ if opts["memory-dump"]
103
104
  else
104
105
  code = cmd.call
105
106
  end
106
- log.debug("Memory footprint at the end is #{Lazylead::Allocated.new}")
107
+ log.debug "Memory footprint at the end is " \
108
+ "#{Lazylead::Allocated.new.to_s.colorize(:light_blue)}."
107
109
  exit(code) unless code.zero?
@@ -32,7 +32,7 @@ Gem::Specification.new do |s|
32
32
  s.rubygems_version = "2.2"
33
33
  s.required_ruby_version = ">=2.6.5"
34
34
  s.name = "lazylead"
35
- s.version = "0.5.0"
35
+ s.version = "0.6.2"
36
36
  s.license = "MIT"
37
37
  s.summary = "Eliminate the annoying work within bug-trackers."
38
38
  s.description = "Ticketing systems (Github, Jira, etc.) are strongly
@@ -45,7 +45,7 @@ tasks instead of solving technical problems."
45
45
  s.authors = ["Yurii Dubinka"]
46
46
  s.email = "yurii.dubinka@gmail.com"
47
47
  s.homepage = "http://github.com/dgroup/lazylead"
48
- s.post_install_message = "Thanks for installing Lazylead v0.5.0!
48
+ s.post_install_message = "Thanks for installing Lazylead v0.6.2!
49
49
  Read our blog posts: https://lazylead.org
50
50
  Stay in touch with the community in Telegram: https://t.me/lazylead
51
51
  Follow us on Twitter: https://twitter.com/lazylead
@@ -57,6 +57,7 @@ tasks instead of solving technical problems."
57
57
  s.extra_rdoc_files = %w[readme.md license.txt]
58
58
  s.add_runtime_dependency "activerecord", "6.0.3"
59
59
  s.add_runtime_dependency "backtrace", "0.3"
60
+ s.add_runtime_dependency "colorize", "0.8.1"
60
61
  s.add_runtime_dependency "faraday", "1.0.1"
61
62
  s.add_runtime_dependency "get_process_mem", "0.2.5"
62
63
  s.add_runtime_dependency "jira-ruby", "1.7.1"
@@ -65,11 +66,11 @@ tasks instead of solving technical problems."
65
66
  s.add_runtime_dependency "mail", "2.7.1"
66
67
  s.add_runtime_dependency "memory_profiler", "0.9.13"
67
68
  s.add_runtime_dependency "openssl", "2.1.2"
68
- s.add_runtime_dependency "rainbow", "3.0.0"
69
69
  s.add_runtime_dependency "require_all", "3.0.0"
70
70
  s.add_runtime_dependency "rufus-scheduler", "3.6.0"
71
71
  s.add_runtime_dependency "slop", "4.4"
72
72
  s.add_runtime_dependency "sqlite3", "1.4.2"
73
+ s.add_runtime_dependency "tempfile", "0.1.0"
73
74
  s.add_runtime_dependency "tilt", "2.0.10"
74
75
  s.add_runtime_dependency "tzinfo", "1.1"
75
76
  s.add_runtime_dependency "tzinfo-data", "1.2019.3"
@@ -22,6 +22,7 @@
22
22
  # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
+ require "colorize"
25
26
  require "vcs4sql/sqlite/migration"
26
27
  require_relative "../smtp"
27
28
  require_relative "../schedule"
@@ -58,9 +59,11 @@ module Lazylead
58
59
  def apply_vcs_migration(opts)
59
60
  @db = File.expand_path(opts[:home]) + "/" + opts[:sqlite]
60
61
  vcs = File.expand_path(opts[:home]) + "/" + opts[:vcs4sql]
61
- @log.debug "Database: '#{@db}', sql migration dir: '#{vcs}'"
62
+ @log.debug "Database: '#{@db.colorize(:light_blue)}', "\
63
+ "sql migration dir: '#{vcs.colorize(:light_blue)}'"
62
64
  Vcs4sql::Sqlite::Migration.new(@db).upgrade vcs, opts[:testdata]
63
- @log.debug "Migration applied to #{@db} from #{vcs}"
65
+ @log.debug "Migration applied to '#{@db.colorize(:light_blue)}' from " \
66
+ "'#{vcs.colorize(:light_blue)}'"
64
67
  end
65
68
 
66
69
  def enable_active_record(opts)
@@ -78,7 +81,12 @@ module Lazylead
78
81
  @log.warn "ll-001: No tasks found."
79
82
  else
80
83
  todo.find_each do |task|
81
- @schedule.register task
84
+ if task.to_h?
85
+ @schedule.register task
86
+ else
87
+ @log.warn "ll-011: Scheduling skipped due to configuration " \
88
+ "mistake in #{task}"
89
+ end
82
90
  end
83
91
  @schedule.join
84
92
  end
@@ -25,26 +25,6 @@
25
25
  require "tilt"
26
26
 
27
27
  module Lazylead
28
- # Email notifications utilities.
29
- module Emailing
30
- # Construct html document from template and binds.
31
- def make_body(opts)
32
- Email.new(
33
- opts["template"],
34
- opts.merge(version: Lazylead::VERSION)
35
- ).render
36
- end
37
-
38
- # Split text with email addresses by ',' and trim all elements if needed.
39
- def split(type, opts)
40
- if opts[type].include? ","
41
- opts[type].split(",").map(&:strip).reject(&:empty?)
42
- else
43
- [opts[type]]
44
- end
45
- end
46
- end
47
-
48
28
  # An email regarding tickets based on file with markup.
49
29
  #
50
30
  # The 'tilt' gem was used as a template engine.
@@ -39,8 +39,6 @@ module Lazylead
39
39
  # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
40
40
  # License:: MIT
41
41
  class Exchange
42
- include Emailing
43
-
44
42
  def initialize(
45
43
  log = Log.new, salt = Salt.new("exchange_salt"), opts = ENV.to_h
46
44
  )
@@ -52,39 +50,28 @@ module Lazylead
52
50
  # Send an email.
53
51
  # :opts :: the mail configuration like from, cc, subject, template.
54
52
  def send(opts)
55
- to = opts["to"] || opts[:to]
56
- to = [to] unless to.is_a? Array
57
- if to.reject { |e| e.nil? || e.blank? }.empty?
58
- @log.warn "Email can't be sent to '#{to}, more: '#{opts}'"
59
- return
53
+ if opts.msg_to.empty?
54
+ @log.warn "ll-012: Email can't be sent to '#{opts.msg_to}," \
55
+ " more: '#{opts}'"
56
+ else
57
+ msg = make_msg opts
58
+ cli.send_message msg
59
+ msg[:file_attachments].each(&:close) unless opts.msg_attachments.empty?
60
+ @log.debug "#{__FILE__} sent '#{opts['subject']}' to '#{opts.msg_to}'."
60
61
  end
61
- msg = make_msg(to, opts)
62
- msg.update(cc_recipients: opts["cc"]) if opts.key? "cc"
63
- add_attachments(msg, opts)
64
- cli.send_message msg
65
- close_attachments msg
66
- @log.debug "Email was generated from #{opts} and send by #{__FILE__}. " \
67
- "Here is the body: '#{msg[:body]}'"
68
62
  end
69
63
 
70
- def make_msg(to, opts)
71
- {
64
+ def make_msg(opts)
65
+ msg = {
72
66
  subject: opts["subject"],
73
- body: make_body(opts),
67
+ body: opts.msg_body,
74
68
  body_type: "HTML",
75
- to_recipients: to
69
+ to_recipients: opts.msg_to
76
70
  }
77
- end
78
-
79
- def add_attachments(msg, opts)
80
- return unless opts.key? "attachments"
81
- files = split("attachments", opts).map { |f| File.open(f, "r") }
82
- msg[:file_attachments] = files
83
- end
84
-
85
- def close_attachments(msg)
86
- return if msg[:file_attachments].nil? || msg[:file_attachments].empty?
87
- msg[:file_attachments].each(&:close)
71
+ files = opts.msg_attachments.map { |f| File.open(f, "r") }
72
+ msg[:file_attachments] = files unless files.empty?
73
+ msg[:cc_recipients] = opts["cc"] if opts.key? "cc"
74
+ msg
88
75
  end
89
76
 
90
77
  private
@@ -23,6 +23,7 @@
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
25
  require "logging"
26
+ require "colorize"
26
27
  require "forwardable"
27
28
 
28
29
  module Lazylead
@@ -68,7 +69,7 @@ module Lazylead
68
69
  Logging.appenders.stdout(
69
70
  "stdout",
70
71
  layout: Logging.layouts.pattern(
71
- pattern: "[%d] %-5l [%X{tid}] %m\n",
72
+ pattern: "[%d] %-5l #{'[%X{tid}]'.colorize(:light_green)} %m\n",
72
73
  color_scheme: "bright"
73
74
  )
74
75
  )
@@ -80,6 +80,13 @@ module Lazylead
80
80
  JSON.parse(properties).to_h
81
81
  end
82
82
 
83
+ def to_h?
84
+ return true unless to_hash.nil?
85
+ false
86
+ rescue StandardError => _e
87
+ false
88
+ end
89
+
83
90
  def to_s
84
91
  attributes.map { |k, v| "#{k}='#{v}'" }.join(", ")
85
92
  end
@@ -34,7 +34,8 @@ module Lazylead
34
34
  # License:: MIT
35
35
  class Opts
36
36
  extend Forwardable
37
- def_delegators :@origin, :[], :[]=, :to_s, :key?, :fetch, :merge, :except
37
+ def_delegators :@origin, :[], :[]=, :to_s, :key?, :fetch, :except, :each,
38
+ :each_pair, :sort_by
38
39
 
39
40
  def initialize(origin = {})
40
41
  @origin = origin
@@ -42,6 +43,7 @@ module Lazylead
42
43
 
43
44
  # Split text value by delimiter, trim all spaces and reject blank items
44
45
  def slice(key, delim)
46
+ return [] unless to_h.key? key
45
47
  to_h[key].split(delim).map(&:chomp).map(&:strip).reject(&:blank?)
46
48
  end
47
49
 
@@ -76,5 +78,51 @@ module Lazylead
76
78
  return Salt.new(sid).decrypt(text) if ENV.key? sid
77
79
  text
78
80
  end
81
+
82
+ def merge(args)
83
+ return self unless args.is_a? Hash
84
+ Opts.new @origin.merge(args)
85
+ end
86
+
87
+ # Construct html document from template and binds.
88
+ def msg_body(template = "template")
89
+ Email.new(
90
+ to_h[template],
91
+ to_h.merge(version: Lazylead::VERSION)
92
+ ).render
93
+ end
94
+
95
+ def msg_to(delim = ",")
96
+ sliced delim, :to, "to"
97
+ end
98
+
99
+ def msg_cc(delim = ",")
100
+ sliced delim, :cc, "cc"
101
+ end
102
+
103
+ def msg_from(delim = ",")
104
+ sliced delim, :from, "from"
105
+ end
106
+
107
+ def msg_attachments(delim = ",")
108
+ sliced(delim, :attachments, "attachments").select { |f| File.file? f }
109
+ end
110
+
111
+ #
112
+ # Find the option by key and split by delimiter
113
+ # Opts.new("key" => "a,b").sliced(",", "key") => [a, b]
114
+ # Opts.new(key: "a,b").sliced(",", :key) => [a, b]
115
+ # Opts.new(key: "a,b").sliced(",", "key", :key) => [a, b]
116
+ # Opts.new(key: "").sliced ",", :key) => []
117
+ #
118
+ def sliced(delim, *keys)
119
+ return [] if keys.empty?
120
+ key = keys.detect { |k| key? k }
121
+ val = to_h[key]
122
+ return [] if val.nil? || val.blank?
123
+ return val if val.is_a? Array
124
+ return [val] unless val.include? delim
125
+ slice key, delim
126
+ end
79
127
  end
80
128
  end
@@ -44,8 +44,6 @@ module Lazylead
44
44
  # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
45
45
  # License:: MIT
46
46
  class Postman
47
- include Emailing
48
-
49
47
  def initialize(log = Log.new)
50
48
  @log = log
51
49
  end
@@ -53,26 +51,29 @@ module Lazylead
53
51
  # Send an email.
54
52
  # :opts :: the mail configuration like to, from, cc, subject, template.
55
53
  def send(opts)
56
- html = make_body(opts)
54
+ if opts.msg_to.empty?
55
+ @log.warn "ll-013: Email can't be sent to '#{opts.msg_to}," \
56
+ " more: '#{opts}'"
57
+ else
58
+ mail = make_email(opts)
59
+ mail.deliver
60
+ @log.debug "#{__FILE__} sent '#{mail.subject}' to '#{mail.to}'."
61
+ end
62
+ end
63
+
64
+ # Construct an email based on input arguments
65
+ def make_email(opts)
57
66
  mail = Mail.new
58
- mail.to opts[:to] || opts["to"]
59
- mail.from opts["from"]
60
- mail.cc opts["cc"] if opts.key? "cc"
67
+ mail.to opts.msg_to
68
+ mail.from opts.msg_from
69
+ mail.cc opts.msg_cc if opts.key? "cc"
61
70
  mail.subject opts["subject"]
62
71
  mail.html_part do
63
72
  content_type "text/html; charset=UTF-8"
64
- body html
73
+ body opts.msg_body
65
74
  end
66
- add_attachments mail, opts
67
- mail.deliver
68
- @log.debug "Email was generated from #{opts} and send by #{__FILE__}. " \
69
- "Here is the body: #{html}"
70
- end
71
-
72
- def add_attachments(mail, opts)
73
- return unless opts.key? "attachments"
74
- opts["attachments"].select { |a| File.file? a }
75
- .each { |a| mail.add_file a }
75
+ opts.msg_attachments.each { |f| mail.add_file f }
76
+ mail
76
77
  end
77
78
  end
78
79
  end
@@ -23,6 +23,7 @@
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
25
  require "mail"
26
+ require "colorize"
26
27
  require_relative "log"
27
28
  require_relative "salt"
28
29
 
@@ -45,7 +46,8 @@ module Lazylead
45
46
  Mail.defaults do
46
47
  delivery_method :test
47
48
  end
48
- @log.warn "SMTP connection enabled in test mode."
49
+ @log.warn "SMTP connection enabled in " \
50
+ "#{'test'.colorize(:light_yellow)} mode."
49
51
  else
50
52
  setup_smtp
51
53
  end
@@ -25,6 +25,7 @@
25
25
  require "jira-ruby"
26
26
  require "forwardable"
27
27
  require_relative "../salt"
28
+ require_relative "../opts"
28
29
 
29
30
  module Lazylead
30
31
  # Jira system for manipulation with issues.
@@ -45,16 +46,32 @@ module Lazylead
45
46
  " and salt #{@salt.id} (found=#{@salt.specified?})"
46
47
  end
47
48
 
48
- def issues(jql, opts = {})
49
+ # Find the jira issues by 'JQL'
50
+ # @param 'jql' - Jira search query
51
+ # @param 'opts' - Parameters for Jira search query.
52
+ # :max_results => maximum number of tickets per one iteration.
53
+ # :fields => ticket fields to be fetched like assignee, summary, etc.
54
+ def issues(jql, opts = { max_results: 50, fields: nil, expand: nil })
49
55
  raw do |jira|
50
- jira.Issue.jql(jql, opts).map { |i| Lazylead::Issue.new(i, jira) }
56
+ start = 0
57
+ tickets = []
58
+ total = jira.Issue.jql(jql, max_results: 0)
59
+ @log.debug "Found #{total} ticket(s) in '#{jql}'"
60
+ loop do
61
+ tickets.concat(jira.Issue.jql(jql, opts.merge(start_at: start))
62
+ .map { |i| Lazylead::Issue.new(i, jira) })
63
+ @log.debug "Fetched #{tickets.size}"
64
+ start += opts.fetch(:max_results, 50).to_i
65
+ break if start > total
66
+ end
67
+ tickets
51
68
  end
52
69
  end
53
70
 
54
71
  # Execute request to the ticketing system using raw client.
55
72
  # For Jira the raw client is 'jira-ruby' gem.
56
73
  def raw
57
- raise "ll-06: No block given to method" unless block_given?
74
+ raise "ll-009: No block given to method" unless block_given?
58
75
  yield client
59
76
  end
60
77
 
@@ -236,10 +253,15 @@ module Lazylead
236
253
  end
237
254
 
238
255
  def add_label(label, *more)
239
- labels = @issue.labels
240
- labels << label
241
- labels += more if more.size.positive?
242
- save!("fields" => { "labels" => labels.uniq })
256
+ lbl = labels
257
+ lbl = [] if lbl.nil?
258
+ lbl << label
259
+ lbl += more if more.size.positive?
260
+ save!("fields" => { "labels" => lbl.uniq })
261
+ end
262
+
263
+ def labels
264
+ fields["labels"]
243
265
  end
244
266
 
245
267
  def save!(opts)
@@ -320,7 +342,7 @@ module Lazylead
320
342
 
321
343
  # Execute request to the ticketing system using raw client.
322
344
  def raw
323
- raise "ll-08: No block given to method" unless block_given?
345
+ raise "ll-008: No block given to method" unless block_given?
324
346
  yield(OpenStruct.new(Issue: self))
325
347
  end
326
348