lazylead 0.5.2 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 -26
- 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 +65 -2
- data/lib/lazylead/postman.rb +13 -17
- data/lib/lazylead/salt.rb +1 -0
- data/lib/lazylead/system/jira.rb +30 -6
- 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 -5
- 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 +50 -10
- data/lib/lazylead/task/accuracy/testcase.rb +16 -9
- data/lib/lazylead/task/assignment.rb +96 -0
- data/lib/lazylead/task/fix_version.rb +6 -0
- data/lib/lazylead/task/propagate_down.rb +1 -1
- data/lib/lazylead/task/svn/diff.rb +77 -0
- data/lib/lazylead/task/svn/grep.rb +139 -0
- data/lib/lazylead/task/svn/touch.rb +99 -0
- data/lib/lazylead/version.rb +1 -1
- data/lib/messages/illegal_assignee_change.erb +123 -0
- data/lib/messages/illegal_fixversion_change.erb +8 -0
- data/lib/messages/only_ll.erb +107 -0
- data/lib/messages/svn_diff.erb +110 -0
- data/lib/messages/{svn_log.erb → svn_diff_attachment.erb} +19 -9
- data/lib/messages/svn_grep.erb +114 -0
- 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/system/jira_test.rb +6 -7
- data/test/lazylead/task/accuracy/attachment_test.rb +1 -1
- data/test/lazylead/task/accuracy/logs_test.rb +62 -2
- 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/stacktrace_test.rb +227 -0
- data/test/lazylead/task/accuracy/testcase_test.rb +39 -0
- data/test/lazylead/task/assignment_test.rb +53 -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/diff_test.rb +97 -0
- data/test/lazylead/task/svn/grep_test.rb +103 -0
- data/test/lazylead/task/{touch_test.rb → svn/touch_test.rb} +7 -34
- data/test/test.rb +7 -8
- data/upgrades/sqlite/999.testdata.sql +3 -1
- metadata +141 -55
- data/lib/lazylead/task/touch.rb +0 -119
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
|
@@ -42,7 +43,14 @@ module Lazylead
|
|
42
43
|
|
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
|
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?)
|
46
54
|
end
|
47
55
|
|
48
56
|
def blank?(key)
|
@@ -76,5 +84,60 @@ module Lazylead
|
|
76
84
|
return Salt.new(sid).decrypt(text) if ENV.key? sid
|
77
85
|
text
|
78
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
|
79
142
|
end
|
80
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,31 +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"
|
79
|
-
opts["attachments"].select { |a| File.file? a }
|
80
|
-
.each { |a| mail.add_file a }
|
81
|
-
end
|
82
78
|
end
|
83
79
|
end
|
data/lib/lazylead/salt.rb
CHANGED
data/lib/lazylead/system/jira.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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-
|
74
|
+
raise "ll-009: No block given to method" unless block_given?
|
58
75
|
yield client
|
59
76
|
end
|
60
77
|
|
@@ -160,7 +177,7 @@ module Lazylead
|
|
160
177
|
end
|
161
178
|
|
162
179
|
def url
|
163
|
-
@issue.attrs[
|
180
|
+
"#{@issue.attrs['self'].split('/rest/api/').first}/browse/#{key}"
|
164
181
|
end
|
165
182
|
|
166
183
|
def duedate
|
@@ -240,13 +257,20 @@ module Lazylead
|
|
240
257
|
lbl = [] if lbl.nil?
|
241
258
|
lbl << label
|
242
259
|
lbl += more if more.size.positive?
|
243
|
-
|
260
|
+
labels! lbl
|
244
261
|
end
|
245
262
|
|
263
|
+
# Get the labels for a particular issue
|
246
264
|
def labels
|
247
265
|
fields["labels"]
|
248
266
|
end
|
249
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
|
+
|
250
274
|
def save!(opts)
|
251
275
|
@issue.save(opts)
|
252
276
|
end
|
@@ -325,7 +349,7 @@ module Lazylead
|
|
325
349
|
|
326
350
|
# Execute request to the ticketing system using raw client.
|
327
351
|
def raw
|
328
|
-
raise "ll-
|
352
|
+
raise "ll-008: No block given to method" unless block_given?
|
329
353
|
yield(OpenStruct.new(Issue: self))
|
330
354
|
end
|
331
355
|
|
@@ -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)}%"
|
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
|