lazylead 0.1.2 → 0.4.1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.0pdd.yml +4 -1
  3. data/.circleci/config.yml +14 -4
  4. data/.docker/Dockerfile +5 -4
  5. data/.docker/vcs.dockerfile +10 -0
  6. data/.docs/accuracy.md +107 -0
  7. data/.docs/accuracy_email.jpg +0 -0
  8. data/.docs/accuracy_jira_comment.jpg +0 -0
  9. data/.docs/duedate_expired.md +92 -0
  10. data/.docs/propagate_down.md +89 -0
  11. data/.pdd +1 -1
  12. data/.rubocop.yml +7 -1
  13. data/.rultor.yml +2 -2
  14. data/.simplecov +0 -6
  15. data/bin/lazylead +13 -5
  16. data/lazylead.gemspec +4 -16
  17. data/lib/lazylead/cc.rb +180 -0
  18. data/lib/lazylead/cli/app.rb +4 -3
  19. data/lib/lazylead/exchange.rb +15 -2
  20. data/lib/lazylead/home.rb +38 -0
  21. data/lib/lazylead/log.rb +30 -8
  22. data/lib/lazylead/model.rb +60 -16
  23. data/lib/lazylead/opts.rb +68 -0
  24. data/lib/lazylead/postman.rb +15 -15
  25. data/lib/lazylead/schedule.rb +6 -4
  26. data/lib/lazylead/smtp.rb +1 -1
  27. data/lib/lazylead/system/fake.rb +1 -1
  28. data/lib/lazylead/system/jira.rb +55 -12
  29. data/lib/lazylead/system/synced.rb +2 -1
  30. data/lib/lazylead/task/accuracy/accuracy.rb +140 -0
  31. data/lib/lazylead/task/accuracy/affected_build.rb +43 -0
  32. data/lib/lazylead/task/accuracy/requirement.rb +40 -0
  33. data/lib/lazylead/task/alert.rb +8 -6
  34. data/lib/lazylead/task/confluence_ref.rb +4 -3
  35. data/lib/lazylead/task/echo.rb +4 -0
  36. data/lib/lazylead/task/fix_version.rb +10 -6
  37. data/lib/lazylead/task/missing_comment.rb +7 -5
  38. data/lib/lazylead/task/propagate_down.rb +126 -0
  39. data/lib/lazylead/task/savepoint.rb +58 -0
  40. data/lib/lazylead/task/touch.rb +102 -0
  41. data/lib/lazylead/version.rb +1 -1
  42. data/lib/messages/accuracy.erb +118 -0
  43. data/lib/messages/due_date_expired.erb +8 -7
  44. data/lib/messages/illegal_fixversion_change.erb +9 -8
  45. data/lib/messages/missing_comment.erb +10 -9
  46. data/lib/messages/savepoint.erb +43 -0
  47. data/lib/messages/svn_touch.erb +147 -0
  48. data/readme.md +37 -34
  49. data/test/lazylead/cc_test.rb +153 -0
  50. data/test/lazylead/cli/app_test.rb +3 -4
  51. data/test/lazylead/exchange_test.rb +22 -2
  52. data/test/lazylead/model_test.rb +14 -3
  53. data/test/lazylead/opts_test.rb +66 -0
  54. data/test/lazylead/postman_test.rb +57 -0
  55. data/test/lazylead/smtp_test.rb +1 -1
  56. data/test/lazylead/system/jira_test.rb +43 -1
  57. data/test/lazylead/task/accuracy/accuracy_test.rb +73 -0
  58. data/test/lazylead/task/accuracy/affected_build_test.rb +42 -0
  59. data/test/lazylead/task/assignee_alert_test.rb +47 -0
  60. data/test/lazylead/task/duedate_test.rb +52 -30
  61. data/test/lazylead/task/fix_version_test.rb +11 -10
  62. data/test/lazylead/task/missing_comment_test.rb +13 -13
  63. data/test/lazylead/task/propagate_down_test.rb +88 -0
  64. data/test/lazylead/task/savepoint_test.rb +51 -0
  65. data/test/lazylead/task/touch_test.rb +63 -0
  66. data/test/test.rb +11 -0
  67. data/upgrades/sqlite/001-install-main-lazylead-tables.sql +0 -1
  68. data/upgrades/sqlite/999.testdata.sql +8 -2
  69. metadata +43 -176
  70. data/todo.yml +0 -6
@@ -34,15 +34,19 @@ require "logging"
34
34
  require "backtrace"
35
35
  require "memory_profiler"
36
36
  require_relative "../lib/lazylead/log"
37
+ require_relative "../lib/lazylead/home"
37
38
  require_relative "../lib/lazylead/schedule"
38
39
  require_relative "../lib/lazylead/allocated"
39
40
  require_relative "../lib/lazylead/cli/app"
40
41
 
41
- log = Lazylead::Log::ERRORS
42
+ log = Lazylead::Log.new
42
43
  Thread.current.name = "main"
44
+ Logging.mdc["tid"] = Thread.current.name
43
45
  Encoding.default_external = Encoding::UTF_8
44
46
  Encoding.default_internal = Encoding::UTF_8
45
47
 
48
+ # @todo #/DEV Decorate ARGV with custom methods in order to avoid code
49
+ # duplication, like { ARGV.include? "--trace" }
46
50
  opts = Slop.parse(ARGV, strict: false, suppress_errors: true) do |o|
47
51
  o.banner = "Usage: lazylead [options]
48
52
  Available options:"
@@ -51,25 +55,29 @@ Available options:"
51
55
  o.bool "--memory-dump", "Dump memory snapshot afterwards, to the console",
52
56
  default: false
53
57
  o.string "--home",
54
- "Home directory (default: #{Dir.pwd})",
55
- default: ENV["LL_HOME"].nil? ? Dir.pwd : ENV["LL_HOME"]
58
+ "Home directory (default #{Lazylead::Home.new.dir})",
59
+ default: Lazylead::Home.new.dir
56
60
  o.string "--sqlite",
57
61
  "SQLite database file name",
58
62
  default: "lazylead.db"
59
63
  o.string "--vcs4sql",
60
64
  "Home directory with database version control(vcs) scripts",
61
65
  default: "upgrades/sqlite"
66
+ o.integer "--max-connections",
67
+ "SQL connections pool size",
68
+ default: 5
62
69
  o.bool "--testdata",
63
70
  "Apply the database VCS migration with test data",
64
71
  default: false
65
72
  o.on "--verbose", "Enable extra logging information" do
66
- log = Lazylead::Log::VERBOSE
73
+ log.verbose
67
74
  end
68
75
  o.on "-v", "--version", "Show current version" do
69
76
  log.debug Lazylead::VERSION
70
77
  exit
71
78
  end
72
79
  end
80
+ log.debug("Version: #{Lazylead::VERSION}")
73
81
  log.debug("Memory footprint at start is #{Lazylead::Allocated.new}")
74
82
  cmd = lambda do
75
83
  Lazylead::CLI::App.new(
@@ -86,7 +94,7 @@ cmd = lambda do
86
94
  return 0
87
95
  rescue StandardError => e
88
96
  log.error("#{e.message} (#{e.class.name})")
89
- log.error(Backtrace.new(e)) if opts["trace"]
97
+ log.error(Backtrace.new(e)) if ARGV.include? "--trace"
90
98
  return -1
91
99
  end
92
100
  code = 0
@@ -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.1.2"
35
+ s.version = "0.4.1"
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.1.2!
48
+ s.post_install_message = "Thanks for installing Lazylead v0.4.1!
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,14 +57,10 @@ 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 "concurrent-ruby", "1.1.5"
61
- s.add_runtime_dependency "diffy", "3.3.0"
62
60
  s.add_runtime_dependency "faraday", "1.0.1"
63
- s.add_runtime_dependency "futex", "0.8.5"
64
61
  s.add_runtime_dependency "get_process_mem", "0.2.5"
65
- s.add_runtime_dependency "haml", "5.0.4"
66
62
  s.add_runtime_dependency "jira-ruby", "1.7.1"
67
- s.add_runtime_dependency "json", "2.2.0"
63
+ s.add_runtime_dependency "json", "2.3.0"
68
64
  s.add_runtime_dependency "logging", "2.2.2"
69
65
  s.add_runtime_dependency "mail", "2.7.1"
70
66
  s.add_runtime_dependency "memory_profiler", "0.9.13"
@@ -72,22 +68,14 @@ tasks instead of solving technical problems."
72
68
  s.add_runtime_dependency "rainbow", "3.0.0"
73
69
  s.add_runtime_dependency "require_all", "3.0.0"
74
70
  s.add_runtime_dependency "rufus-scheduler", "3.6.0"
75
- s.add_runtime_dependency "semantic", "1.6.1"
76
- s.add_runtime_dependency "sinatra", "2.0.5"
77
71
  s.add_runtime_dependency "slop", "4.4"
78
72
  s.add_runtime_dependency "sqlite3", "1.4.2"
79
- s.add_runtime_dependency "sys-proctable", "1.2.1"
80
- s.add_runtime_dependency "threads", "0.3"
81
73
  s.add_runtime_dependency "tilt", "2.0.10"
82
- s.add_runtime_dependency "total", "0.3"
83
- s.add_runtime_dependency "typhoeus", "1.3.1"
84
74
  s.add_runtime_dependency "tzinfo", "1.1"
85
75
  s.add_runtime_dependency "tzinfo-data", "1.2019.3"
86
- s.add_runtime_dependency "usagewatch_ext", "0.2.1"
87
76
  s.add_runtime_dependency "vcs4sql", "0.1.0"
88
77
  s.add_runtime_dependency "viewpoint", "1.1.0"
89
- s.add_runtime_dependency "zache", "0.12"
90
- s.add_development_dependency "codecov", "0.1.14"
78
+ s.add_development_dependency "codecov", "0.2.3"
91
79
  s.add_development_dependency "guard", "2.15.0"
92
80
  s.add_development_dependency "guard-minitest", "2.4.6"
93
81
  s.add_development_dependency "minitest", "5.11.3"
@@ -0,0 +1,180 @@
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
+ require "forwardable"
25
+
26
+ module Lazylead
27
+ # Entry point for email CC detection.
28
+ # The email may need CC email addresses, thus, there are various strategies
29
+ # how it can be done.
30
+ class CC
31
+ # Build an CC in order to detect email addresses by different conditions.
32
+ #
33
+ # Supported conditions(types):
34
+ # - PlainCC
35
+ # - PredefinedCC
36
+ # - ComponentCC
37
+ # - Empty
38
+ #
39
+ def detect(emails, sys)
40
+ return emails if recognized?(emails)
41
+ return PlainCC.new(emails) if plain?(emails)
42
+ return EmptyCC.new if undefined?(emails)
43
+ type = emails["type"].constantize
44
+ return ComponentCC.new(emails["project"], sys) if type.is_a? ComponentCC
45
+ type.new(emails["opts"])
46
+ end
47
+
48
+ # Detect that raw CC is a string which may has plain email addresses
49
+ def plain?(text)
50
+ (text.is_a? String) && text.to_s.include?("@")
51
+ end
52
+
53
+ def recognized?(emails)
54
+ [EmptyCC, PlainCC, ComponentCC, PredefinedCC].member? emails.class
55
+ end
56
+
57
+ def undefined?(emails)
58
+ return true unless emails.key? "type"
59
+ emails["type"].nil? || emails["type"].blank?
60
+ end
61
+ end
62
+
63
+ # Array of CC addresses from text for email notification.
64
+ #
65
+ # PlainCC.new("a@f.com, , -,b@f.com").cc # ==> ["a@f.com", "b@f.com"]
66
+ #
67
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
68
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
69
+ # License:: MIT
70
+ class PlainCC
71
+ include Enumerable
72
+ extend Forwardable
73
+ def_delegators :@cc, :each
74
+
75
+ # The regexp expression for email notification is very simple, here is the
76
+ # reason why https://bit.ly/38iLKeo
77
+ def initialize(text, regxp = /[^\s]@[^\s]/)
78
+ @text = text
79
+ @regxp = regxp
80
+ end
81
+
82
+ def cc
83
+ @cc ||= begin
84
+ if @text.include? ","
85
+ @text.split(",").map(&:strip).select { |e| e[@regxp] }
86
+ elsif @text[@regxp]
87
+ [@text.strip]
88
+ end
89
+ end
90
+ end
91
+
92
+ def each(&block)
93
+ cc.each(&block)
94
+ end
95
+ end
96
+
97
+ # Empty CC email addresses.
98
+ class EmptyCC
99
+ def cc
100
+ []
101
+ end
102
+ end
103
+
104
+ # Predefined CC addresses for email notification.
105
+ # You may define a hash where
106
+ # - key is Jira ticket component
107
+ # - value is CC email address(es)
108
+ #
109
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
110
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
111
+ # License:: MIT
112
+ class PredefinedCC
113
+ def initialize(orig)
114
+ @orig = orig
115
+ end
116
+
117
+ def [](key)
118
+ to_h[key]
119
+ end
120
+
121
+ def cc(*names)
122
+ return to_h.values.flatten.uniq.compact if names.count.zero?
123
+ return self[names.first] if names.count == 1
124
+ to_h.values_at(names.first, *names.drop(1)).flatten.uniq.compact
125
+ end
126
+
127
+ def to_h
128
+ @to_h ||= begin
129
+ if @orig.is_a? Hash
130
+ @orig.each_with_object({}) do |i, o|
131
+ o[i.first] = Lazylead::PlainCC.new(i.last).cc
132
+ end
133
+ else
134
+ {}
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ # CC addresses based on Jira component owners for email notification.
141
+ # Allows to detect the CC for particular ticket based on its component.
142
+ #
143
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
144
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
145
+ # License:: MIT
146
+ class ComponentCC < Lazylead::PredefinedCC
147
+ def initialize(prj, jira)
148
+ @prj = prj
149
+ @jira = jira
150
+ end
151
+
152
+ def to_h
153
+ @to_h ||= begin
154
+ components.each_with_object({}) do |c, h|
155
+ email = lead(c.attrs["id"])
156
+ next if email.nil? || email.blank?
157
+ h[c.attrs["name"]] = email
158
+ end
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ def lead(component_id)
165
+ @jira.raw do |j|
166
+ lead = j.Component
167
+ .find(component_id, expand: "", fields: "")
168
+ .attrs["lead"]
169
+ next if lead.nil? || lead.empty?
170
+ j.User.find(lead["key"]).attrs["emailAddress"]
171
+ end
172
+ end
173
+
174
+ def components
175
+ @jira.raw do |j|
176
+ j.Project.find(@prj, expand: "components", fields: "").components
177
+ end
178
+ end
179
+ end
180
+ end
@@ -48,7 +48,7 @@ module Lazylead
48
48
 
49
49
  def run(opts)
50
50
  apply_vcs_migration opts
51
- enable_active_record
51
+ enable_active_record opts
52
52
  @smtp.enable
53
53
  schedule_tasks
54
54
  end
@@ -63,10 +63,11 @@ module Lazylead
63
63
  @log.debug "Migration applied to #{@db} from #{vcs}"
64
64
  end
65
65
 
66
- def enable_active_record
66
+ def enable_active_record(opts)
67
67
  ActiveRecord::Base.establish_connection(
68
68
  adapter: "sqlite3",
69
- database: @db
69
+ database: @db,
70
+ pool: opts[:max_connections]
70
71
  )
71
72
  @log.debug "Database connection established"
72
73
  end
@@ -42,7 +42,7 @@ module Lazylead
42
42
  include Emailing
43
43
 
44
44
  def initialize(
45
- log = Log::NOTHING, salt = Salt.new("exchange_salt"), opts = ENV.to_h
45
+ log = Log.new, salt = Salt.new("exchange_salt"), opts = ENV.to_h
46
46
  )
47
47
  @log = log
48
48
  @salt = salt
@@ -61,12 +61,25 @@ module Lazylead
61
61
  body_type: "HTML",
62
62
  to_recipients: to
63
63
  }
64
- msg.update(cc_recipients: split("cc", opts)) if opts.key? "cc"
64
+ msg.update(cc_recipients: opts["cc"]) if opts.key? "cc"
65
+ add_attachments(msg, opts)
65
66
  cli.send_message msg
67
+ close_attachments msg
66
68
  @log.debug "Email was generated from #{opts} and send by #{__FILE__}. " \
67
69
  "Here is the body: #{html}"
68
70
  end
69
71
 
72
+ def add_attachments(msg, opts)
73
+ return unless opts.key? "attachments"
74
+ files = split("attachments", opts).map { |f| File.open(f, "r") }
75
+ msg[:file_attachments] = files
76
+ end
77
+
78
+ def close_attachments(msg)
79
+ return if msg[:file_attachments].nil? || msg[:file_attachments].empty?
80
+ msg[:file_attachments].each(&:close)
81
+ end
82
+
70
83
  private
71
84
 
72
85
  def cli
@@ -0,0 +1,38 @@
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 "logging"
26
+
27
+ module Lazylead
28
+ # Home directory for lazylead
29
+ #
30
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
31
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
32
+ # License:: MIT
33
+ class Home
34
+ def dir
35
+ ENV["LL_HOME"].nil? ? Dir.pwd : ENV["LL_HOME"]
36
+ end
37
+ end
38
+ end
@@ -23,16 +23,38 @@
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
25
  require "logging"
26
+ require "forwardable"
26
27
 
27
28
  module Lazylead
28
- # Loggers.
29
+ # The main application logger
30
+ class Log
31
+ extend Forwardable
32
+ def_delegators :@log, :debug, :info, :warn, :error
33
+
34
+ def initialize(log = Lazylead::Level::ERRORS)
35
+ @log = log
36
+ @log = Lazylead::Level::DEBUG if ARGV.include? "--trace"
37
+ end
38
+
39
+ def nothing
40
+ @log = Lazylead::Level::NOTHING
41
+ self
42
+ end
43
+
44
+ def verbose
45
+ @log = Lazylead::Level::DEBUG
46
+ self
47
+ end
48
+ end
49
+
50
+ # Predefined logging levels.
29
51
  #
30
52
  # There are 3 colored loggers so far:
31
53
  # NOTHING - for cases when logging isn't required
32
54
  # VERBOSE - all logging levels including debug
33
55
  # ERRORS - for errors only which are critical for app.
34
56
  #
35
- module Log
57
+ module Level
36
58
  # Coloring configuration for appender(s).
37
59
  Logging.color_scheme("bright",
38
60
  levels: {
@@ -46,7 +68,7 @@ module Lazylead
46
68
  Logging.appenders.stdout(
47
69
  "stdout",
48
70
  layout: Logging.layouts.pattern(
49
- pattern: "[%d] %-5l %m\n",
71
+ pattern: "[%d] %-5l [%X{tid}] %m\n",
50
72
  color_scheme: "bright"
51
73
  )
52
74
  )
@@ -57,12 +79,12 @@ module Lazylead
57
79
  NOTHING.freeze
58
80
 
59
81
  # All levels including debug
60
- VERBOSE = Logging.logger["verbose"]
61
- VERBOSE.level = :debug
62
- VERBOSE.add_appenders "stdout"
63
- VERBOSE.freeze
82
+ DEBUG = Logging.logger["debug"]
83
+ DEBUG.level = :debug
84
+ DEBUG.add_appenders "stdout"
85
+ DEBUG.freeze
64
86
 
65
- # Alerts
87
+ # Alerts/errors
66
88
  ERRORS = Logging.logger["errors"]
67
89
  ERRORS.level = :error
68
90
  ERRORS.add_appenders "stdout"