lazylead 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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