lazylead 0.6.0 → 0.7.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +7 -2
  3. data/.docker/Dockerfile +17 -6
  4. data/.rubocop.yml +102 -1
  5. data/.simplecov +1 -1
  6. data/Guardfile +1 -1
  7. data/Rakefile +6 -3
  8. data/bin/lazylead +3 -1
  9. data/lazylead.gemspec +31 -27
  10. data/lib/lazylead/cc.rb +21 -20
  11. data/lib/lazylead/cli/app.rb +8 -3
  12. data/lib/lazylead/confluence.rb +9 -2
  13. data/lib/lazylead/email.rb +0 -20
  14. data/lib/lazylead/exchange.rb +16 -28
  15. data/lib/lazylead/model.rb +31 -16
  16. data/lib/lazylead/opts.rb +64 -2
  17. data/lib/lazylead/postman.rb +13 -18
  18. data/lib/lazylead/salt.rb +1 -0
  19. data/lib/lazylead/system/jira.rb +9 -2
  20. data/lib/lazylead/task/accuracy/accuracy.rb +10 -14
  21. data/lib/lazylead/task/accuracy/attachment.rb +2 -6
  22. data/lib/lazylead/task/accuracy/logs.rb +9 -4
  23. data/lib/lazylead/task/accuracy/onlyll.rb +147 -0
  24. data/lib/lazylead/task/accuracy/records.rb +1 -1
  25. data/lib/lazylead/task/accuracy/servers.rb +16 -7
  26. data/lib/lazylead/task/accuracy/stacktrace.rb +1 -1
  27. data/lib/lazylead/task/accuracy/testcase.rb +8 -7
  28. data/lib/lazylead/task/fix_version.rb +4 -2
  29. data/lib/lazylead/task/propagate_down.rb +1 -1
  30. data/lib/lazylead/task/svn/diff.rb +13 -12
  31. data/lib/lazylead/task/svn/grep.rb +40 -12
  32. data/lib/lazylead/task/svn/touch.rb +2 -4
  33. data/lib/lazylead/version.rb +1 -1
  34. data/lib/messages/illegal_fixversion_change.erb +8 -2
  35. data/lib/messages/only_ll.erb +107 -0
  36. data/lib/messages/svn_diff.erb +9 -9
  37. data/lib/messages/svn_diff_attachment.erb +19 -9
  38. data/lib/messages/svn_grep.erb +1 -1
  39. data/test/lazylead/cc_test.rb +1 -0
  40. data/test/lazylead/model_test.rb +20 -0
  41. data/test/lazylead/opts_test.rb +47 -0
  42. data/test/lazylead/postman_test.rb +8 -5
  43. data/test/lazylead/smoke_test.rb +13 -0
  44. data/test/lazylead/smtp_test.rb +1 -4
  45. data/test/lazylead/task/accuracy/attachment_test.rb +1 -1
  46. data/test/lazylead/task/accuracy/logs_test.rb +12 -0
  47. data/test/lazylead/task/accuracy/onlyll_test.rb +138 -0
  48. data/test/lazylead/task/accuracy/servers_test.rb +2 -2
  49. data/test/lazylead/task/accuracy/testcase_test.rb +56 -0
  50. data/test/lazylead/task/duedate_test.rb +3 -10
  51. data/test/lazylead/task/fix_version_test.rb +1 -0
  52. data/test/lazylead/task/propagate_down_test.rb +4 -3
  53. data/test/lazylead/task/savepoint_test.rb +9 -6
  54. data/test/lazylead/task/svn/grep_test.rb +43 -1
  55. data/test/test.rb +7 -8
  56. data/upgrades/sqlite/999.testdata.sql +3 -1
  57. metadata +117 -57
@@ -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,38 +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 "#{__FILE__} sent '#{opts['subject']}' to '#{to}'."
67
62
  end
68
63
 
69
- def make_msg(to, opts)
70
- {
64
+ def make_msg(opts)
65
+ msg = {
71
66
  subject: opts["subject"],
72
- body: make_body(opts),
67
+ body: opts.msg_body,
73
68
  body_type: "HTML",
74
- to_recipients: to
69
+ to_recipients: opts.msg_to
75
70
  }
76
- end
77
-
78
- def add_attachments(msg, opts)
79
- return unless opts.key? "attachments"
80
- files = split("attachments", opts).map { |f| File.open(f, "r") }
81
- msg[:file_attachments] = files
82
- end
83
-
84
- def close_attachments(msg)
85
- return if msg[:file_attachments].nil? || msg[:file_attachments].empty?
86
- 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
87
75
  end
88
76
 
89
77
  private
@@ -68,9 +68,7 @@ module Lazylead
68
68
  opts.each_with_object({}) do |e, o|
69
69
  k = e[0]
70
70
  v = e[1]
71
- if v.respond_to? :start_with?
72
- v = ENV[v.slice(2, v.length - 3)] if v.start_with? "${"
73
- end
71
+ v = ENV[v.slice(2, v.length - 3)] if v.respond_to?(:start_with?) && v.start_with?("${")
74
72
  o[k] = v
75
73
  end
76
74
  end
@@ -80,6 +78,13 @@ module Lazylead
80
78
  JSON.parse(properties).to_h
81
79
  end
82
80
 
81
+ def to_h?
82
+ return true unless to_hash.nil?
83
+ false
84
+ rescue StandardError => _e
85
+ false
86
+ end
87
+
83
88
  def to_s
84
89
  attributes.map { |k, v| "#{k}='#{v}'" }.join(", ")
85
90
  end
@@ -99,6 +104,7 @@ module Lazylead
99
104
  sys = system.connect
100
105
  opts = props
101
106
  opts = detect_cc(sys) if opts.key? "cc"
107
+ opts["system"] = second_sys if opts.numeric? "system"
102
108
  action.constantize.new.run(sys, postman, opts)
103
109
  end
104
110
 
@@ -130,12 +136,12 @@ module Lazylead
130
136
 
131
137
  def props
132
138
  @props ||= begin
133
- if team.nil?
134
- Opts.new(env(to_hash))
135
- else
136
- Opts.new(env(team.to_hash.merge(to_hash)))
137
- end
138
- end
139
+ if team.nil?
140
+ Opts.new(env(to_hash))
141
+ else
142
+ Opts.new(env(team.to_hash.merge(to_hash)))
143
+ end
144
+ end
139
145
  end
140
146
 
141
147
  def postman
@@ -146,17 +152,21 @@ module Lazylead
146
152
  end
147
153
  end
148
154
 
155
+ def second_sys
156
+ sys = System.find(props["system"])
157
+ raise "ll-014: No ticketing system found for #{self}" if sys.nil?
158
+ sys.connect
159
+ end
160
+
149
161
  private
150
162
 
151
163
  # Parse scheduling #type and #unit
152
164
  def trigger
153
165
  @trigger ||= begin
154
- trg = schedule.split(":")
155
- unless trg.size == 2
156
- raise "ll-007: illegal schedule format '#{schedule}'"
157
- end
158
- trg.map(&:strip).map(&:chomp)
159
- end
166
+ trg = schedule.split(":")
167
+ raise "ll-007: illegal schedule format '#{schedule}'" unless trg.size == 2
168
+ trg.map(&:strip).map(&:chomp)
169
+ end
160
170
  end
161
171
  end
162
172
 
@@ -172,6 +182,10 @@ module Lazylead
172
182
  @log = log
173
183
  end
174
184
 
185
+ # @todo #/DEV Remove the suppression during next refactoring (or enhancements)
186
+ # for the method below
187
+ #
188
+ # rubocop:disable Metrics/AbcSize
175
189
  def exec
176
190
  Logging.mdc["tid"] = "task #{id}"
177
191
  @log.debug "'#{name}' is started."
@@ -182,12 +196,13 @@ module Lazylead
182
196
  rescue StandardError => e
183
197
  msg = <<~MSG
184
198
  ll-006: Task ##{id} #{e} (#{e.class}) at #{self}
185
- #{Backtrace.new(e) if ARGV.include? '--trace'}"
199
+ #{Backtrace.new(e) if ARGV.include? '--trace'}
186
200
  MSG
187
201
  @log.error msg
188
202
  ensure
189
203
  Logging.mdc["tid"] = ""
190
204
  end
205
+ # rubocop:enable Metrics/AbcSize
191
206
  end
192
207
 
193
208
  # Ticketing systems to monitor.
@@ -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
@@ -43,7 +44,13 @@ module Lazylead
43
44
  # Split text value by delimiter, trim all spaces and reject blank items
44
45
  def slice(key, delim)
45
46
  return [] unless to_h.key? key
46
- to_h[key].split(delim).map(&:chomp).map(&:strip).reject(&:blank?)
47
+ trim to_h[key].split(delim)
48
+ end
49
+
50
+ # Trim all spaces and reject blank items in array
51
+ def trim(arr)
52
+ return [] if arr.nil?
53
+ arr.map(&:chomp).map(&:strip).reject(&:blank?)
47
54
  end
48
55
 
49
56
  def blank?(key)
@@ -77,5 +84,60 @@ module Lazylead
77
84
  return Salt.new(sid).decrypt(text) if ENV.key? sid
78
85
  text
79
86
  end
87
+
88
+ def merge(args)
89
+ return self unless args.is_a? Hash
90
+ Opts.new @origin.merge(args)
91
+ end
92
+
93
+ # Construct html document from template and binds.
94
+ def msg_body(template = "template")
95
+ Email.new(
96
+ to_h[template],
97
+ to_h.merge(version: Lazylead::VERSION)
98
+ ).render
99
+ end
100
+
101
+ def msg_to(delim = ",")
102
+ sliced delim, :to, "to"
103
+ end
104
+
105
+ def msg_cc(delim = ",")
106
+ sliced delim, :cc, "cc"
107
+ end
108
+
109
+ def msg_from(delim = ",")
110
+ sliced delim, :from, "from"
111
+ end
112
+
113
+ def msg_attachments(delim = ",")
114
+ sliced(delim, :attachments, "attachments").select { |f| File.file? f }
115
+ end
116
+
117
+ #
118
+ # Find the option by key and split by delimiter
119
+ # Opts.new("key" => "a,b").sliced(",", "key") => [a, b]
120
+ # Opts.new(key: "a,b").sliced(",", :key) => [a, b]
121
+ # Opts.new(key: "a,b").sliced(",", "key", :key) => [a, b]
122
+ # Opts.new(key: "").sliced ",", :key) => []
123
+ #
124
+ def sliced(delim, *keys)
125
+ return [] if keys.empty?
126
+ key = keys.detect { |k| key? k }
127
+ val = to_h[key]
128
+ return [] if val.nil? || val.blank?
129
+ return val if val.is_a? Array
130
+ return [val] unless val.include? delim
131
+ slice key, delim
132
+ end
133
+
134
+ # Ensure that particular key from options is a positive numer
135
+ # Opts.new("key" => "1").numeric? "key" => true
136
+ # Opts.new("key" => "0").numeric? "key" => false
137
+ # Opts.new("key" => ".").numeric? "key" => false
138
+ # Opts.new("key" => "nil").numeric? "key" => false
139
+ def numeric?(key)
140
+ to_h[key].to_i.positive?
141
+ end
80
142
  end
81
143
  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,32 +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
- mail = make_email(opts)
57
- mail.deliver
58
- @log.debug "#{__FILE__} sent '#{mail.subject}' to '#{mail.to}'."
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
59
62
  end
60
63
 
61
64
  # Construct an email based on input arguments
62
65
  def make_email(opts)
63
66
  mail = Mail.new
64
- mail.to opts[:to] || opts["to"]
65
- mail.from opts["from"]
66
- 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"
67
70
  mail.subject opts["subject"]
68
- html = make_body(opts)
69
71
  mail.html_part do
70
72
  content_type "text/html; charset=UTF-8"
71
- body html
73
+ body opts.msg_body
72
74
  end
73
- add_attachments mail, opts
75
+ opts.msg_attachments.each { |f| mail.add_file f }
74
76
  mail
75
77
  end
76
-
77
- def add_attachments(mail, opts)
78
- return unless opts.key?("attachments") || opts.key?(:attachments)
79
- attach = opts["attachments"] || opts[:attachments]
80
- return if attach.nil?
81
- attach.select { |a| File.file? a }.each { |a| mail.add_file a }
82
- end
83
78
  end
84
79
  end
@@ -39,6 +39,7 @@ module Lazylead
39
39
  #
40
40
  class Salt
41
41
  attr_reader :id
42
+
42
43
  #
43
44
  # Each salt should be defined as a environment variable with id, like
44
45
  # salt1=E1F53135E559C253
@@ -177,7 +177,7 @@ module Lazylead
177
177
  end
178
178
 
179
179
  def url
180
- @issue.attrs["self"].split("/rest/api/").first + "/browse/" + key
180
+ "#{@issue.attrs['self'].split('/rest/api/').first}/browse/#{key}"
181
181
  end
182
182
 
183
183
  def duedate
@@ -257,13 +257,20 @@ module Lazylead
257
257
  lbl = [] if lbl.nil?
258
258
  lbl << label
259
259
  lbl += more if more.size.positive?
260
- save!("fields" => { "labels" => lbl.uniq })
260
+ labels! lbl
261
261
  end
262
262
 
263
+ # Get the labels for a particular issue
263
264
  def labels
264
265
  fields["labels"]
265
266
  end
266
267
 
268
+ # Update the labels for a particular issue
269
+ def labels!(lbl)
270
+ return if lbl.nil? || lbl.empty?
271
+ save!("fields" => { "labels" => lbl.uniq })
272
+ end
273
+
267
274
  def save!(opts)
268
275
  @issue.save(opts)
269
276
  end
@@ -70,10 +70,8 @@ module Lazylead
70
70
  # Estimate the ticket score and accuracy.
71
71
  # Accuracy is a percentage between current score and maximum possible value.
72
72
  def evaluate(digits = 2)
73
- @total = @rules.map(&:score).sum
74
- @score = @rules.select { |r| r.passed(@issue) }
75
- .map(&:score)
76
- .sum
73
+ @total = @rules.sum(&:score)
74
+ @score = @rules.select { |r| r.passed(@issue) }.sum(&:score)
77
75
  @accuracy = (score / @total * 100).round(digits)
78
76
  end
79
77
 
@@ -81,7 +79,7 @@ module Lazylead
81
79
  def post
82
80
  return if @opts.key? "silent"
83
81
  @issue.post comment
84
- @issue.add_label "LL.accuracy", "#{grade(@accuracy)}%"
82
+ @issue.add_label "LL.accuracy", "#{grade(@accuracy)}%", "#{@accuracy}%"
85
83
  end
86
84
 
87
85
  # The jira comment in markdown format
@@ -114,9 +112,7 @@ module Lazylead
114
112
  end
115
113
 
116
114
  def color
117
- if colors.nil? || !defined?(@score) || !@score.is_a?(Numeric)
118
- return "#061306"
119
- end
115
+ return "#061306" if colors.nil? || !defined?(@score) || !@score.is_a?(Numeric)
120
116
  colors.reverse_each do |color|
121
117
  return color.last if @accuracy >= color.first
122
118
  end
@@ -125,12 +121,12 @@ module Lazylead
125
121
 
126
122
  def colors
127
123
  @colors ||= begin
128
- JSON.parse(@opts["colors"])
129
- .to_h
130
- .to_a
131
- .each { |e| e[0] = e[0].to_i }
132
- .sort_by { |e| e[0] }
133
- end
124
+ JSON.parse(@opts["colors"])
125
+ .to_h
126
+ .to_a
127
+ .each { |e| e[0] = e[0].to_i }
128
+ .sort_by { |e| e[0] }
129
+ end
134
130
  end
135
131
 
136
132
  # Calculate grade for accuracy
@@ -27,17 +27,13 @@ require_relative "requirement"
27
27
  module Lazylead
28
28
  # Check that ticket has an attachment.
29
29
  class Attachment < Lazylead::Requirement
30
- def initialize(desc, score, field)
31
- super(desc, score, field)
32
- end
33
-
34
30
  def passed(issue)
35
- issue.attachments.any?(&method(:matching))
31
+ issue.attachments.any? { |a| matches?(a) }
36
32
  end
37
33
 
38
34
  # Check a single attachment from ticket.
39
35
  # Potential extension point for custom verification logic.
40
- def matching(attachment)
36
+ def matches?(attachment)
41
37
  !attachment.nil?
42
38
  end
43
39
  end
@@ -27,16 +27,21 @@ require_relative "attachment"
27
27
  module Lazylead
28
28
  # Check that ticket has log file(s) in attachment.
29
29
  class Logs < Lazylead::Attachment
30
- def initialize
30
+ def initialize(files = %w[log.zip logs.zip log.gz logs.gz log.tar.gz
31
+ logs.tar.gz log.7z logs.7z log.tar logs.tar])
31
32
  super("Log files", 2, "Attachments")
33
+ @files = files
32
34
  end
33
35
 
34
36
  # Ensure that ticket has a '*.log' file more '5KB'
35
- def matching(attachment)
37
+ def matches?(attachment)
36
38
  name = attachment.attrs["filename"].downcase
37
39
  return false unless attachment.attrs["size"].to_i > 5120
38
- return true if File.extname(name).start_with?(".log", ".txt")
39
- %w[.log.zip .log.gz .log.tar.gz].any? { |l| name.end_with? l }
40
+ return true if File.extname(name).start_with? ".log", ".txt", ".out"
41
+ return true if @files.any? { |l| name.end_with? l }
42
+ %w[.zip .7z .gz tar.gz].any? do |l|
43
+ name.end_with?(l) && name.split(/(?:[^a-zA-Z0-9](?<![^\x00-\x7F]))+/).include?("logs")
44
+ end
40
45
  end
41
46
  end
42
47
  end