lazylead 0.1.0

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 (83) hide show
  1. checksums.yaml +7 -0
  2. data/ .dockerignore +12 -0
  3. data/.0pdd.yml +5 -0
  4. data/.circleci/config.yml +50 -0
  5. data/.circleci/release_image.sh +13 -0
  6. data/.circleci/validate.sh +10 -0
  7. data/.docker/Dockerfile +31 -0
  8. data/.docker/build.sh +6 -0
  9. data/.docker/docker-compose.yml +23 -0
  10. data/.docker/readme.md +21 -0
  11. data/.gitattributes +9 -0
  12. data/.github/CODE_OF_CONDUCT.md +76 -0
  13. data/.github/CONTRIBUTING.md +9 -0
  14. data/.github/ISSUE_TEMPLATE.md +14 -0
  15. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  16. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  17. data/.github/PULL_REQUEST_TEMPLATE.md +11 -0
  18. data/.github/tasks.yml +24 -0
  19. data/.github/trello.md +18 -0
  20. data/.gitignore +12 -0
  21. data/.pdd +5 -0
  22. data/.rubocop.yml +87 -0
  23. data/.ruby-version +1 -0
  24. data/.rultor.yml +31 -0
  25. data/.simplecov +16 -0
  26. data/.travis.yml +16 -0
  27. data/Gemfile +27 -0
  28. data/Guardfile +33 -0
  29. data/Rakefile +93 -0
  30. data/appveyor.yml +50 -0
  31. data/bin/.ruby-version +1 -0
  32. data/bin/lazylead +99 -0
  33. data/build.sh +6 -0
  34. data/deploy.sh +16 -0
  35. data/lazylead.gemspec +106 -0
  36. data/lib/lazylead.rb +52 -0
  37. data/lib/lazylead/allocated.rb +56 -0
  38. data/lib/lazylead/cli/app.rb +87 -0
  39. data/lib/lazylead/confluence.rb +157 -0
  40. data/lib/lazylead/email.rb +74 -0
  41. data/lib/lazylead/exchange.rb +83 -0
  42. data/lib/lazylead/log.rb +71 -0
  43. data/lib/lazylead/model.rb +140 -0
  44. data/lib/lazylead/postman.rb +78 -0
  45. data/lib/lazylead/salt.rb +91 -0
  46. data/lib/lazylead/schedule.rb +88 -0
  47. data/lib/lazylead/smtp.rb +82 -0
  48. data/lib/lazylead/system/empty.rb +36 -0
  49. data/lib/lazylead/system/fake.rb +41 -0
  50. data/lib/lazylead/system/jira.rb +249 -0
  51. data/lib/lazylead/system/synced.rb +41 -0
  52. data/lib/lazylead/task/alert.rb +105 -0
  53. data/lib/lazylead/task/confluence_ref.rb +59 -0
  54. data/lib/lazylead/task/echo.rb +38 -0
  55. data/lib/lazylead/task/fix_version.rb +79 -0
  56. data/lib/lazylead/task/missing_comment.rb +53 -0
  57. data/lib/lazylead/version.rb +27 -0
  58. data/lib/messages/due_date_expired.erb +96 -0
  59. data/lib/messages/illegal_fixversion_change.erb +120 -0
  60. data/lib/messages/missing_comment.erb +128 -0
  61. data/license.txt +21 -0
  62. data/readme.md +160 -0
  63. data/test/lazylead/allocated_test.rb +51 -0
  64. data/test/lazylead/cli/app_test.rb +74 -0
  65. data/test/lazylead/confluence_test.rb +55 -0
  66. data/test/lazylead/exchange_test.rb +68 -0
  67. data/test/lazylead/model_test.rb +65 -0
  68. data/test/lazylead/salt_test.rb +42 -0
  69. data/test/lazylead/smoke_test.rb +38 -0
  70. data/test/lazylead/smtp_test.rb +65 -0
  71. data/test/lazylead/system/jira_test.rb +110 -0
  72. data/test/lazylead/task/confluence_ref_test.rb +66 -0
  73. data/test/lazylead/task/duedate_test.rb +126 -0
  74. data/test/lazylead/task/echo_test.rb +34 -0
  75. data/test/lazylead/task/fix_version_test.rb +52 -0
  76. data/test/lazylead/task/missing_comment_test.rb +56 -0
  77. data/test/lazylead/version_test.rb +36 -0
  78. data/test/sqlite_test.rb +80 -0
  79. data/test/test.rb +103 -0
  80. data/todo.yml +16 -0
  81. data/upgrades/sqlite/001-install-main-lazylead-tables.sql +63 -0
  82. data/upgrades/sqlite/999.testdata.sql +39 -0
  83. metadata +815 -0
@@ -0,0 +1,74 @@
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 "tilt"
26
+
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
+ # An email regarding tickets based on file with markup.
49
+ #
50
+ # The 'tilt' gem was used as a template engine.
51
+ # Read more about 'tilt':
52
+ # - https://github.com/rtomayko/tilt
53
+ # - https://github.com/rtomayko/tilt/blob/master/docs/TEMPLATES.md
54
+ # - https://www.rubyguides.com/2018/11/ruby-erb-haml-slim/
55
+ #
56
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
57
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
58
+ # License:: MIT
59
+ class Email
60
+ # :file :: the file with html template
61
+ # :binds :: the template variables for substitution.
62
+ def initialize(file, binds)
63
+ @file = file
64
+ @binds = binds
65
+ end
66
+
67
+ # Construct the email body from html template based on variables (binds).
68
+ def render
69
+ Tilt.new(@file)
70
+ .render(OpenStruct.new(@binds))
71
+ .delete!("\n")
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,83 @@
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 "viewpoint"
26
+ require_relative "log"
27
+ require_relative "salt"
28
+ require_relative "email"
29
+ require_relative "version"
30
+
31
+ module Lazylead
32
+ #
33
+ # A postman to send emails to the Microsoft Exchange server.
34
+ #
35
+ # @todo #/DEV Add support of symbols for options in order to use both
36
+ # notations like opts[:endpoint] or opts["endpoint"].
37
+ #
38
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
39
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
40
+ # License:: MIT
41
+ class Exchange
42
+ include Emailing
43
+
44
+ def initialize(
45
+ log = Log::NOTHING, salt = Salt.new("exchange_salt"), opts = ENV.to_h
46
+ )
47
+ @log = log
48
+ @salt = salt
49
+ @opts = opts
50
+ end
51
+
52
+ # Send an email.
53
+ # :opts :: the mail configuration like from, cc, subject, template.
54
+ def send(opts)
55
+ to = opts["to"] || opts[:to]
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
+ }
64
+ msg.update(cc_recipients: split("cc", opts)) if opts.key? "cc"
65
+ cli.send_message msg
66
+ @log.debug "Email was generated from #{opts} and send by #{__FILE__}. " \
67
+ "Here is the body: #{html}"
68
+ end
69
+
70
+ private
71
+
72
+ def cli
73
+ return @cli if defined? @cli
74
+ url = @opts["exchange_url"]
75
+ usr = @opts["exchange_user"]
76
+ psw = @opts["exchange_password"]
77
+ usr = @salt.decrypt(usr) if @salt.specified?
78
+ psw = @salt.decrypt(psw) if @salt.specified?
79
+ @log.debug "Connect to MS Exchange server #{url} as '#{usr}'"
80
+ @cli = Viewpoint::EWSClient.new url, usr, psw
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,71 @@
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
+ # Loggers.
29
+ #
30
+ # There are 3 colored loggers so far:
31
+ # NOTHING - for cases when logging isn't required
32
+ # VERBOSE - all logging levels including debug
33
+ # ERRORS - for errors only which are critical for app.
34
+ #
35
+ module Log
36
+ # Coloring configuration for appender(s).
37
+ Logging.color_scheme("bright",
38
+ levels: {
39
+ info: :green,
40
+ warn: :yellow,
41
+ error: :red,
42
+ fatal: %i[white on_red]
43
+ },
44
+ date: :blue,
45
+ logger: :cyan)
46
+ Logging.appenders.stdout(
47
+ "stdout",
48
+ layout: Logging.layouts.pattern(
49
+ pattern: "[%d] %-5l %m\n",
50
+ color_scheme: "bright"
51
+ )
52
+ )
53
+
54
+ # Nothing to log
55
+ NOTHING = Logging.logger["nothing"]
56
+ NOTHING.level = :off
57
+ NOTHING.freeze
58
+
59
+ # All levels including debug
60
+ VERBOSE = Logging.logger["verbose"]
61
+ VERBOSE.level = :debug
62
+ VERBOSE.add_appenders "stdout"
63
+ VERBOSE.freeze
64
+
65
+ # Alerts
66
+ ERRORS = Logging.logger["errors"]
67
+ ERRORS.level = :error
68
+ ERRORS.add_appenders "stdout"
69
+ ERRORS.freeze
70
+ end
71
+ end
@@ -0,0 +1,140 @@
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 "active_record"
26
+ require "require_all"
27
+ require_rel "task"
28
+ require_rel "system"
29
+ require_relative "log"
30
+ require_relative "postman"
31
+ require_relative "exchange"
32
+
33
+ #
34
+ # ORM domain model entities.
35
+ #
36
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
37
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
38
+ # License:: MIT
39
+ module Lazylead
40
+ # ORM-related methods
41
+ module ORM
42
+ # Tables from database may have configuration in json column.
43
+ # Some of those properties might be configured/overridden by environment
44
+ # variable using macros "${...}"
45
+ # {
46
+ # ...
47
+ # "user" = "${my_user}",
48
+ # "pass" = "${my_pass}"
49
+ # "url" = "https://url.com"
50
+ # ...
51
+ # }
52
+ # next, we need to configure following environment variables(ENV)
53
+ # my_user=XXXXX
54
+ # my_pass=YYYYY
55
+ # thus, the result of this method is
56
+ # {
57
+ # ...
58
+ # "user" = "XXXXXX",
59
+ # "pass" = "YYYYYY"
60
+ # "url" = "https://url.com"
61
+ # ...
62
+ # }
63
+ def env(opts)
64
+ opts.each_with_object({}) do |e, o|
65
+ k = e[0]
66
+ v = e[1]
67
+ v = ENV[v.slice(2, v.length - 3)] if v.start_with? "${"
68
+ o[k] = v
69
+ end
70
+ end
71
+
72
+ # Convert json-based database column 'properties' to hash.
73
+ def to_hash
74
+ JSON.parse(properties).to_h
75
+ end
76
+
77
+ def to_s
78
+ attributes.map { |k, v| "#{k}='#{v}'" }.join(", ")
79
+ end
80
+
81
+ def inspect
82
+ to_s
83
+ end
84
+
85
+ # General lazylead task.
86
+ class Task < ActiveRecord::Base
87
+ include ORM
88
+ belongs_to :team, foreign_key: "team_id"
89
+ belongs_to :system, foreign_key: "system"
90
+
91
+ def exec(log = Log::NOTHING)
92
+ log.debug("Task ##{id} '#{name}' is started")
93
+ action.constantize
94
+ .new(log)
95
+ .run(system.connect(log), postman(log), props)
96
+ log.debug("Task ##{id} '#{name}' is completed")
97
+ end
98
+
99
+ def props
100
+ return @prop if defined? @prop
101
+ @prop = team.to_hash.merge(to_hash)
102
+ end
103
+
104
+ def postman(log = Log::NOTHING)
105
+ if props.key? "postman"
106
+ props["postman"].constantize.new(log)
107
+ else
108
+ log.warn "No postman details provided, an local stub is used."
109
+ Postman.new
110
+ end
111
+ end
112
+ end
113
+
114
+ # Ticketing systems to monitor.
115
+ class System < ActiveRecord::Base
116
+ include ORM
117
+
118
+ # Make an instance of ticketing system for future interaction.
119
+ def connect(log = Log::NOTHING)
120
+ opts = to_hash
121
+ if opts["type"].empty?
122
+ log.warn "No task system details provided, an empty stub is used."
123
+ Empty.new
124
+ else
125
+ opts["type"].constantize.new(
126
+ env(opts.except("type", "salt")),
127
+ opts["salt"].blank? ? NoSalt.new : Salt.new(opts["salt"]),
128
+ log
129
+ )
130
+ end
131
+ end
132
+ end
133
+
134
+ # A team for lazylead task.
135
+ # Each team may have several tasks.
136
+ class Team < ActiveRecord::Base
137
+ include ORM
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,78 @@
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 "log"
26
+ require_relative "email"
27
+ require_relative "version"
28
+
29
+ module Lazylead
30
+ #
31
+ # A postman to send emails.
32
+ #
33
+ # @todo #/DEV Merge Smtp and Postman objects. There might be different
34
+ # postman's based on different mail servers, thus its better to keep together
35
+ # the instantiation and sending. For each type of mail servers we should have
36
+ # separate object.
37
+ #
38
+ # @todo #/DEV TestMail.deliveries -> store all emails to the files in
39
+ # /test/resources/testmailer/*.html. Right now after each test with email
40
+ # sending we taking the body and test it content. Quite ofter we need to
41
+ # check visual style in mails, etc, thus its better to store them on disk.
42
+ #
43
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
44
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
45
+ # License:: MIT
46
+ class Postman
47
+ include Emailing
48
+
49
+ def initialize(log = Log::NOTHING)
50
+ @log = log
51
+ end
52
+
53
+ # Send an email.
54
+ # :opts :: the mail configuration like to, from, cc, subject, template.
55
+ def send(opts)
56
+ html = make_body(opts)
57
+ cc = detect_cc(opts)
58
+ Mail.deliver do
59
+ to opts[:to] || opts["to"]
60
+ from opts["from"]
61
+ cc cc if opts.key? "cc"
62
+ subject opts["subject"]
63
+ html_part do
64
+ content_type "text/html; charset=UTF-8"
65
+ body html
66
+ end
67
+ end
68
+ @log.debug "Email was generated from #{opts} and send by #{__FILE__}. " \
69
+ "Here is the body: #{html}"
70
+ end
71
+
72
+ def detect_cc(opts)
73
+ cc = opts["cc"]
74
+ cc = split("cc", opts) if !cc.nil? && cc.include?(",")
75
+ cc
76
+ end
77
+ end
78
+ end