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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +7 -2
- data/.docker/Dockerfile +17 -6
- data/.rubocop.yml +102 -1
- data/.simplecov +1 -1
- data/Guardfile +1 -1
- data/Rakefile +6 -3
- data/bin/lazylead +3 -1
- data/lazylead.gemspec +31 -27
- data/lib/lazylead/cc.rb +21 -20
- data/lib/lazylead/cli/app.rb +8 -3
- data/lib/lazylead/confluence.rb +9 -2
- data/lib/lazylead/email.rb +0 -20
- data/lib/lazylead/exchange.rb +16 -28
- data/lib/lazylead/model.rb +31 -16
- data/lib/lazylead/opts.rb +64 -2
- data/lib/lazylead/postman.rb +13 -18
- data/lib/lazylead/salt.rb +1 -0
- data/lib/lazylead/system/jira.rb +9 -2
- data/lib/lazylead/task/accuracy/accuracy.rb +10 -14
- data/lib/lazylead/task/accuracy/attachment.rb +2 -6
- data/lib/lazylead/task/accuracy/logs.rb +9 -4
- data/lib/lazylead/task/accuracy/onlyll.rb +147 -0
- data/lib/lazylead/task/accuracy/records.rb +1 -1
- data/lib/lazylead/task/accuracy/servers.rb +16 -7
- data/lib/lazylead/task/accuracy/stacktrace.rb +1 -1
- data/lib/lazylead/task/accuracy/testcase.rb +8 -7
- data/lib/lazylead/task/fix_version.rb +4 -2
- data/lib/lazylead/task/propagate_down.rb +1 -1
- data/lib/lazylead/task/svn/diff.rb +13 -12
- data/lib/lazylead/task/svn/grep.rb +40 -12
- data/lib/lazylead/task/svn/touch.rb +2 -4
- data/lib/lazylead/version.rb +1 -1
- data/lib/messages/illegal_fixversion_change.erb +8 -2
- data/lib/messages/only_ll.erb +107 -0
- data/lib/messages/svn_diff.erb +9 -9
- data/lib/messages/svn_diff_attachment.erb +19 -9
- data/lib/messages/svn_grep.erb +1 -1
- data/test/lazylead/cc_test.rb +1 -0
- data/test/lazylead/model_test.rb +20 -0
- data/test/lazylead/opts_test.rb +47 -0
- data/test/lazylead/postman_test.rb +8 -5
- data/test/lazylead/smoke_test.rb +13 -0
- data/test/lazylead/smtp_test.rb +1 -4
- data/test/lazylead/task/accuracy/attachment_test.rb +1 -1
- data/test/lazylead/task/accuracy/logs_test.rb +12 -0
- data/test/lazylead/task/accuracy/onlyll_test.rb +138 -0
- data/test/lazylead/task/accuracy/servers_test.rb +2 -2
- data/test/lazylead/task/accuracy/testcase_test.rb +56 -0
- data/test/lazylead/task/duedate_test.rb +3 -10
- data/test/lazylead/task/fix_version_test.rb +1 -0
- data/test/lazylead/task/propagate_down_test.rb +4 -3
- data/test/lazylead/task/savepoint_test.rb +9 -6
- data/test/lazylead/task/svn/grep_test.rb +43 -1
- data/test/test.rb +7 -8
- data/upgrades/sqlite/999.testdata.sql +3 -1
- metadata +117 -57
data/lib/lazylead/email.rb
CHANGED
@@ -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.
|
data/lib/lazylead/exchange.rb
CHANGED
@@ -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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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(
|
70
|
-
{
|
64
|
+
def make_msg(opts)
|
65
|
+
msg = {
|
71
66
|
subject: opts["subject"],
|
72
|
-
body:
|
67
|
+
body: opts.msg_body,
|
73
68
|
body_type: "HTML",
|
74
|
-
to_recipients:
|
69
|
+
to_recipients: opts.msg_to
|
75
70
|
}
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
data/lib/lazylead/model.rb
CHANGED
@@ -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?
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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.
|
data/lib/lazylead/opts.rb
CHANGED
@@ -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, :
|
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)
|
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
|
data/lib/lazylead/postman.rb
CHANGED
@@ -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
|
-
|
57
|
-
|
58
|
-
|
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
|
65
|
-
mail.from opts
|
66
|
-
mail.cc opts
|
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
|
73
|
+
body opts.msg_body
|
72
74
|
end
|
73
|
-
|
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
|
data/lib/lazylead/salt.rb
CHANGED
data/lib/lazylead/system/jira.rb
CHANGED
@@ -177,7 +177,7 @@ module Lazylead
|
|
177
177
|
end
|
178
178
|
|
179
179
|
def url
|
180
|
-
@issue.attrs[
|
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
|
-
|
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.
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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?(
|
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
|
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
|
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?
|
39
|
-
|
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
|