lazylead 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.docs/accuracy.md +2 -2
  3. data/.docs/duedate_expired.md +3 -3
  4. data/.docs/propagate_down.md +3 -3
  5. data/.gitattributes +1 -0
  6. data/Rakefile +2 -0
  7. data/bin/lazylead +1 -1
  8. data/lazylead.gemspec +2 -2
  9. data/lib/lazylead/exchange.rb +15 -8
  10. data/lib/lazylead/model.rb +35 -1
  11. data/lib/lazylead/opts.rb +12 -0
  12. data/lib/lazylead/schedule.rb +16 -15
  13. data/lib/lazylead/system/jira.rb +39 -0
  14. data/lib/lazylead/task/accuracy/accuracy.rb +4 -8
  15. data/lib/lazylead/task/accuracy/affected_build.rb +2 -6
  16. data/lib/lazylead/task/accuracy/attachment.rb +44 -0
  17. data/lib/lazylead/task/accuracy/environment.rb +39 -0
  18. data/lib/lazylead/task/accuracy/logs.rb +40 -0
  19. data/lib/lazylead/task/accuracy/records.rb +45 -0
  20. data/lib/lazylead/task/accuracy/requirement.rb +9 -0
  21. data/lib/lazylead/task/accuracy/servers.rb +50 -0
  22. data/lib/lazylead/task/accuracy/stacktrace.rb +63 -0
  23. data/lib/lazylead/task/accuracy/testcase.rb +75 -0
  24. data/lib/lazylead/task/accuracy/wiki.rb +41 -0
  25. data/lib/lazylead/task/echo.rb +18 -0
  26. data/lib/lazylead/task/fix_version.rb +9 -2
  27. data/lib/lazylead/task/touch.rb +23 -8
  28. data/lib/lazylead/version.rb +1 -1
  29. data/lib/messages/svn_log.erb +117 -0
  30. data/license.txt +1 -1
  31. data/readme.md +5 -5
  32. data/test/lazylead/cli/app_test.rb +11 -11
  33. data/test/lazylead/system/jira_test.rb +30 -0
  34. data/test/lazylead/task/accuracy/accuracy_test.rb +1 -1
  35. data/test/lazylead/task/accuracy/affected_build_test.rb +2 -2
  36. data/test/lazylead/task/accuracy/attachment_test.rb +50 -0
  37. data/test/lazylead/task/accuracy/environment_test.rb +42 -0
  38. data/test/lazylead/task/accuracy/logs_test.rb +78 -0
  39. data/test/lazylead/task/accuracy/records_test.rb +60 -0
  40. data/test/lazylead/task/accuracy/servers_test.rb +66 -0
  41. data/test/lazylead/task/accuracy/stacktrace_test.rb +113 -0
  42. data/test/lazylead/task/accuracy/testcase_test.rb +205 -0
  43. data/test/lazylead/task/accuracy/wiki_test.rb +40 -0
  44. data/test/lazylead/task/touch_test.rb +28 -1
  45. data/test/test.rb +16 -0
  46. data/upgrades/sqlite/001-install-main-lazylead-tables.sql +1 -5
  47. data/upgrades/sqlite/999.testdata.sql +12 -17
  48. metadata +28 -4
  49. data/.travis.yml +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd5d4e1e8d89064bfa868e1c088166d3a42b4c155fde98246979a262854d15b0
4
- data.tar.gz: fa0d3aefc186740945b1da68318aebe1d597d7b88f70b079644058f5e201126b
3
+ metadata.gz: a8358ec8da9d97621f9c5ec28ddfa3114699f260af8ae4fd9632a998263f795e
4
+ data.tar.gz: 4a11f6cbd5cffbc91a2b40a5b7fb6c82d608301d16ebba1d8b2155a256101f48
5
5
  SHA512:
6
- metadata.gz: 7b018d644048900e19d03bbf8b24847a0b617ba74721f213743687b90d1cc2078ca84f35af29c2660b73207e5fc5b86d4b9e45d3394d9c095645198400602dfe
7
- data.tar.gz: 842cf53e33f2376891d80dcd7f7802f2a27a37bf26ea42a55c88d4b53925b6a161b3962cbc7df0467c2c1049532db8029977c78776340e938cc20f757f5d89b3
6
+ metadata.gz: 31259a7b7305b519f610e68dbbb7649fee65257415aae4a0bfe1a77bdd88f62662279a4689bb18872d37eba4d8c0b6537ddf5f1a1c415762085e507fb39d05ae
7
+ data.tar.gz: 2c6bc24efc4696496c8a0c58c697af93377bc7c6d216141138ce8d18f3606104e7a945c715d3657ebc97ede3f97db586da3a6fe57c01f826eb86c61f024f91b7
@@ -66,7 +66,7 @@ For simplicity, we are using [docker-compose](https://docs.docker.com/compose/):
66
66
  values (1,'{"type":"Lazylead::Jira", "username":"${jira_user}", "password":"${jira_password}", "site":"${jira_url}", "context_path":""}');
67
67
  insert into tasks (name, cron, enabled, id, system, team_id, action, properties)
68
68
  values ('Post ticket score and accuracy to the tickets',
69
- '0 8 * * 1-5',
69
+ 'cron:0 8 * * 1-5',
70
70
  'true',
71
71
  1, 1, 1,
72
72
  'Lazylead::Task::Accuracy',
@@ -82,7 +82,7 @@ For simplicity, we are using [docker-compose](https://docs.docker.com/compose/):
82
82
  }
83
83
  ');
84
84
  ```
85
- Yes, for task scheduling we are using [cron](https://crontab.guru).
85
+ Yes, for task scheduling we are using [cron](https://crontab.guru) here, but you may use other scheduling types from [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler).
86
86
 
87
87
  4. Once you changed `./ll.db`, please restart the container using `docker-compose -f .github/tasks.yml restart`
88
88
  ```bash
@@ -68,16 +68,16 @@ For simplicity, we are using [docker-compose](https://docs.docker.com/compose/):
68
68
  values (1, 'Dream team with lazylead', '{}');
69
69
  insert into systems(id, properties)
70
70
  values (1,'{"type":"Lazylead::Jira", "username":"${jira_user}", "password":"${jira_password}", "site":"${jira_url}", "context_path":""}');
71
- insert into tasks (name, cron, enabled, id, system, team_id, action, properties)
71
+ insert into tasks (name, schedule, enabled, id, system, team_id, action, properties)
72
72
  values ('Expired due dates',
73
- '0 8 * * 1-5',
73
+ 'cron:0 8 * * 1-5',
74
74
  'true',
75
75
  1, 1, 1,
76
76
  'Lazylead::Task::AssigneeAlert',
77
77
  '{"sql":"filter=222", "cc":"<youremail.com>", "subject":"[LL] Expired due dates", "template":"lib/messages/due_date_expired.erb", "postman":"Lazylead::Exchange"}');
78
78
 
79
79
  ```
80
- Yes, for task scheduling we are using [cron](https://crontab.guru).
80
+ Yes, for task scheduling we are using [cron](https://crontab.guru) here, but you may use other scheduling types from [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler).
81
81
 
82
82
  4. Once you changed `./ll.db`, please restart the container using `docker-compose -f .github/tasks.yml restart`
83
83
  ```bash
@@ -58,16 +58,16 @@ For simplicity, we are using [docker-compose](https://docs.docker.com/compose/):
58
58
  values (1, 'Dream team with lazylead', '{}');
59
59
  insert into systems(id, properties)
60
60
  values (1,'{"type":"Lazylead::Jira", "username":"${jira_user}", "password":"${jira_password}", "site":"${jira_url}", "context_path":""}');
61
- insert into tasks (name, cron, enabled, id, system, team_id, action, properties)
61
+ insert into tasks (name, schedule, enabled, id, system, team_id, action, properties)
62
62
  values ('Propagate customfield_1 (External ID) to sub-tasks',
63
- '0 8 * * 1-5',
63
+ 'cron:0 8 * * 1-5',
64
64
  'true',
65
65
  1, 1, 1,
66
66
  'Lazylead::Task::PropagateDown',
67
67
  '{"jql":"filter=222", "propagate":"customfield_1"}');
68
68
 
69
69
  ```
70
- Yes, for task scheduling we are using [cron](https://crontab.guru).
70
+ Yes, for task scheduling we are using [cron](https://crontab.guru) here, but you may use other scheduling types from [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler).
71
71
 
72
72
  4. Once you changed `./ll.db`, please restart the container using `docker-compose -f .github/tasks.yml restart`
73
73
  ```bash
@@ -7,3 +7,4 @@
7
7
  *.xml ident
8
8
  *.png binary
9
9
  *.pdf binary
10
+ *.db binary
data/Rakefile CHANGED
@@ -127,4 +127,6 @@ task :docker do
127
127
  system "docker-compose -f .docker/docker-compose.yml build "\
128
128
  " --build-arg release_tags='latest 1.0'"\
129
129
  " --build-arg version=1.0"
130
+ system "docker-compose -f .docker/docker-compose.yml rm --force -s lazylead"
131
+ system "docker-compose -f .docker/docker-compose.yml up"
130
132
  end
@@ -82,7 +82,7 @@ log.debug("Memory footprint at start is #{Lazylead::Allocated.new}")
82
82
  cmd = lambda do
83
83
  Lazylead::CLI::App.new(
84
84
  log,
85
- Lazylead::Schedule.new(log),
85
+ Lazylead::Schedule.new(log: log),
86
86
  Lazylead::Smtp.new(
87
87
  log, Lazylead::Salt.new("smtp_salt"),
88
88
  smtp_host: ENV["smtp_host"],
@@ -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.4.3"
35
+ s.version = "0.5.0"
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.4.3!
48
+ s.post_install_message = "Thanks for installing Lazylead v0.5.0!
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
@@ -54,19 +54,26 @@ module Lazylead
54
54
  def send(opts)
55
55
  to = opts["to"] || opts[:to]
56
56
  to = [to] unless to.is_a? Array
57
- html = make_body(opts)
58
- msg = {
59
- subject: opts["subject"],
60
- body: html,
61
- body_type: "HTML",
62
- to_recipients: to
63
- }
57
+ if to.reject { |e| e.nil? || e.blank? }.empty?
58
+ @log.warn "Email can't be sent to '#{to}, more: '#{opts}'"
59
+ return
60
+ end
61
+ msg = make_msg(to, opts)
64
62
  msg.update(cc_recipients: opts["cc"]) if opts.key? "cc"
65
63
  add_attachments(msg, opts)
66
64
  cli.send_message msg
67
65
  close_attachments msg
68
66
  @log.debug "Email was generated from #{opts} and send by #{__FILE__}. " \
69
- "Here is the body: #{html}"
67
+ "Here is the body: '#{msg[:body]}'"
68
+ end
69
+
70
+ def make_msg(to, opts)
71
+ {
72
+ subject: opts["subject"],
73
+ body: make_body(opts),
74
+ body_type: "HTML",
75
+ to_recipients: to
76
+ }
70
77
  end
71
78
 
72
79
  def add_attachments(msg, opts)
@@ -94,6 +94,7 @@ module Lazylead
94
94
  belongs_to :team, foreign_key: "team_id"
95
95
  belongs_to :system, foreign_key: "system"
96
96
 
97
+ # Execute task
97
98
  def exec
98
99
  sys = system.connect
99
100
  opts = props
@@ -101,6 +102,25 @@ module Lazylead
101
102
  action.constantize.new.run(sys, postman, opts)
102
103
  end
103
104
 
105
+ # Scheduling type.
106
+ # Current implementation is based on 'rufus-scheduler' gem and supports
107
+ # the following types: 'cron', 'interval', 'in', 'at', 'every'
108
+ def type
109
+ trigger.first
110
+ end
111
+
112
+ # Scheduling unit.
113
+ # Current implementation is based on 'rufus-scheduler' gem thus each
114
+ # scheduling type has own arguments:
115
+ # 1. Scheduling type 'cron' has 'unit' = '00 09 * * *'
116
+ # 2. Scheduling type 'interval' has 'unit' = '2h'
117
+ # 3. Scheduling type 'every' has 'unit' = '3h'
118
+ # 4. Scheduling type 'in' has 'unit' = '10d'
119
+ # 5. Scheduling type 'at' has 'unit' = '2014/12/24 2000'
120
+ def unit
121
+ trigger.last
122
+ end
123
+
104
124
  def detect_cc(sys)
105
125
  opts = props
106
126
  opts["cc"] = CC.new.detect(opts["cc"], sys)
@@ -125,13 +145,27 @@ module Lazylead
125
145
  Postman.new
126
146
  end
127
147
  end
148
+
149
+ private
150
+
151
+ # Parse scheduling #type and #unit
152
+ def trigger
153
+ @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
160
+ end
128
161
  end
129
162
 
130
163
  # A task with extended logging
131
164
  # @see Lazylead::ORM::Task
132
165
  class VerboseTask
133
166
  extend Forwardable
134
- def_delegators :@orig, :id, :name, :team, :to_s, :inspect, :props
167
+ def_delegators :@orig, :id, :name, :team, :to_s, :inspect, :props, :type,
168
+ :unit
135
169
 
136
170
  def initialize(orig, log = Log.new)
137
171
  @orig = orig
@@ -23,6 +23,7 @@
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
25
  require "forwardable"
26
+ require_relative "salt"
26
27
 
27
28
  module Lazylead
28
29
  #
@@ -64,5 +65,16 @@ module Lazylead
64
65
  def jira_fields
65
66
  to_h.fetch("fields", "").split(",").map(&:to_sym)
66
67
  end
68
+
69
+ # Decrypt particular option using cryptography salt
70
+ # @param key option to be decrypted
71
+ # @param sid the name of the salt to be used for the description
72
+ # @see Lazylead::Salt
73
+ def decrypt(key, sid)
74
+ text = to_h[key]
75
+ return text if text.blank? || text.nil?
76
+ return Salt.new(sid).decrypt(text) if ENV.key? sid
77
+ text
78
+ end
67
79
  end
68
80
  end
@@ -34,23 +34,18 @@ module Lazylead
34
34
  # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
35
35
  # License:: MIT
36
36
  class Schedule
37
- # @todo #/DEV New scheduling types like 'at', 'once' is required.
38
- # The minimum time period for cron is 1 minute and it's not suitable for
39
- # unit testing, thus its better to introduce new types which allows to
40
- # schedule some task once or at particular time period like in next 200ms).
41
- # For cron expressions we should define separate test suite which will test
42
- # in parallel without blocking main CI process.
43
- def initialize(log = Log.new, cling = true)
37
+ def initialize(log: Log.new, cling: true, trigger: Rufus::Scheduler.new)
44
38
  @log = log
45
39
  @cling = cling
46
- @trigger = Rufus::Scheduler.new
40
+ @trigger = trigger
47
41
  end
48
42
 
49
- # @todo #/DEV error code is required for reach 'raise' statement within the
50
- # application.
43
+ # @todo #/DEV error code is required for each 'raise' statement within the
44
+ # application. Align the naming of existing one, the error code should be
45
+ # like ll-xxx.
51
46
  def register(task)
52
47
  raise "ll-002: task can't be a null" if task.nil?
53
- @trigger.cron task.cron do
48
+ @trigger.method(task.type).call(task.unit) do
54
49
  ActiveRecord::Base.connection_pool.with_connection do
55
50
  ORM::VerboseTask.new(task, @log).exec
56
51
  end
@@ -60,7 +55,9 @@ module Lazylead
60
55
 
61
56
  # @todo #/DEV inspect the current execution status. This method should
62
57
  # support several format for output, by default is `json`.
63
- def ps; end
58
+ def ps
59
+ @log.debug "#{self}#ps"
60
+ end
64
61
 
65
62
  def join
66
63
  @trigger.join if @cling
@@ -80,11 +77,15 @@ module Lazylead
80
77
  end
81
78
 
82
79
  def register(task)
83
- @log.debug("Task registered: #{task}")
80
+ @log.debug "Task registered: #{task}"
84
81
  end
85
82
 
86
- def ps; end
83
+ def ps
84
+ @log.debug "#{self}#ps"
85
+ end
87
86
 
88
- def join; end
87
+ def join
88
+ @log.debug "#{self}#join"
89
+ end
89
90
  end
90
91
  end
@@ -150,6 +150,11 @@ module Lazylead
150
150
  @issue.key
151
151
  end
152
152
 
153
+ def description
154
+ return "" if @issue.description.nil?
155
+ @issue.description
156
+ end
157
+
153
158
  def summary
154
159
  fields["summary"]
155
160
  end
@@ -175,9 +180,24 @@ module Lazylead
175
180
  end
176
181
 
177
182
  def fields
183
+ return {} if @issue.nil?
184
+ return {} unless @issue.respond_to? :fields
185
+ return {} if @issue.fields.nil?
186
+ return {} unless @issue.fields.respond_to? :[]
178
187
  @issue.fields
179
188
  end
180
189
 
190
+ def [](name)
191
+ return "" if fields[name].nil? || fields[name].blank?
192
+ fields[name]
193
+ end
194
+
195
+ def components
196
+ return [] unless @issue.respond_to? :components
197
+ return [] if @issue.components.nil?
198
+ @issue.components.map(&:name)
199
+ end
200
+
181
201
  def history
182
202
  return [] unless @issue.respond_to? :changelog
183
203
  return [] if @issue.changelog == nil? || @issue.changelog.empty?
@@ -206,6 +226,25 @@ module Lazylead
206
226
  def post(markdown)
207
227
  @issue.comments.build.save!(body: markdown)
208
228
  end
229
+
230
+ def remote_links
231
+ @issue.remotelink.all
232
+ end
233
+
234
+ def attachments
235
+ @issue.attachments
236
+ end
237
+
238
+ 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 })
243
+ end
244
+
245
+ def save!(opts)
246
+ @issue.save(opts)
247
+ end
209
248
  end
210
249
 
211
250
  # The jira issue comments
@@ -43,7 +43,7 @@ module Lazylead
43
43
  end
44
44
 
45
45
  def run(sys, postman, opts)
46
- require_rules
46
+ Dir[File.join(__dir__, "*.rb")].sort.each { |f| require f }
47
47
  rules = opts.slice("rules", ",")
48
48
  .map(&:constantize)
49
49
  .map(&:new)
@@ -53,12 +53,6 @@ module Lazylead
53
53
  .each(&:post)
54
54
  postman.send opts.merge(tickets: raised) unless raised.empty?
55
55
  end
56
-
57
- # Load all ticket accuracy rules for future verification
58
- def require_rules
59
- rules = File.dirname(__FILE__)
60
- $LOAD_PATH.unshift(rules) unless $LOAD_PATH.include?(rules)
61
- end
62
56
  end
63
57
  end
64
58
 
@@ -85,7 +79,9 @@ module Lazylead
85
79
 
86
80
  # Post the comment with score and accuracy to the ticket.
87
81
  def post
88
- @issue.post(comment) unless @opts.key? "silent"
82
+ return if @opts.key? "silent"
83
+ @issue.post comment
84
+ @issue.add_label "LL.accuracy", "#{(@accuracy / 10) * 10}%"
89
85
  end
90
86
 
91
87
  # The jira comment in markdown format
@@ -22,22 +22,18 @@
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_relative "../../log"
26
- require_relative "../../email"
27
- require_relative "../../version"
28
- require_relative "../../postman"
29
25
  require_relative "requirement"
30
26
 
31
27
  module Lazylead
32
28
  # A requirement that Jira field "Affects Version/s" provided by the reporter.
33
- class RequirementAffectedBuild < Requirement
29
+ class AffectedBuild < Requirement
34
30
  def initialize(score = 0.5)
35
31
  super "Affected build", score, "Affects Version/s"
36
32
  end
37
33
 
38
34
  # @return true if an issue has non-empty "Affects Version/s" field
39
35
  def passed(issue)
40
- !issue.fields["versions"].nil? && !issue.fields["versions"].empty?
36
+ non_blank? issue, "versions"
41
37
  end
42
38
  end
43
39
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The MIT License
4
+ #
5
+ # Copyright (c) 2019-2020 Yurii Dubinka
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"),
9
+ # to deal in the Software without restriction, including without limitation
10
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
11
+ # and/or sell copies of the Software, and to permit persons to whom
12
+ # the Software is furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included
15
+ # in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
23
+ # OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ require_relative "requirement"
26
+
27
+ module Lazylead
28
+ # Check that ticket has an attachment.
29
+ class Attachment < Lazylead::Requirement
30
+ def initialize(desc, score, field)
31
+ super(desc, score, field)
32
+ end
33
+
34
+ def passed(issue)
35
+ issue.attachments.any?(&method(:matching))
36
+ end
37
+
38
+ # Check a single attachment from ticket.
39
+ # Potential extension point for custom verification logic.
40
+ def matching(attachment)
41
+ !attachment.nil?
42
+ end
43
+ end
44
+ end