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,41 @@
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
+ module Lazylead
26
+ # Thread-save ticketing system.
27
+ class Synced
28
+ def initialize(sys)
29
+ @mutex = Mutex.new
30
+ @sys = sys
31
+ end
32
+
33
+ # @todo #/DEV Unit tests for 'issues' function
34
+ #
35
+ def issues(jql)
36
+ @mutex.synchronize do
37
+ @sys.issues jql
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,105 @@
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
+ require_relative "../postman"
29
+
30
+ module Lazylead
31
+ #
32
+ # Email alerts about particular issue(s).
33
+ #
34
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
35
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
36
+ # License:: MIT
37
+ module Task
38
+ #
39
+ # Notify particular person about tickets based on pre-defined html template.
40
+ #
41
+ # The task supports the following features:
42
+ # - fetch issues from remote ticketing system by query
43
+ # - prepare email based on predefined template (*.erb)
44
+ # - send the required notifications pre-defined "addressee".
45
+ class Alert
46
+ def initialize(log = Log::NOTHING)
47
+ @log = log
48
+ end
49
+
50
+ def run(sys, postman, opts)
51
+ postman.send opts.merge(tickets: sys.issues(opts["sql"]))
52
+ end
53
+ end
54
+
55
+ #
56
+ # A task that sends notifications about issues to their assignees.
57
+ #
58
+ # The task supports the following features:
59
+ # - fetch issues from remote ticketing system by query
60
+ # - group all issues by assignee
61
+ # - prepare email based on predefined template (*.erb)
62
+ # - send the required notifications to each assignee
63
+ #
64
+ # The email message is sending to the assignee regarding all his/her issues,
65
+ # not like one email per each issue.
66
+ class AssigneeAlert
67
+ def initialize(log = Log::NOTHING)
68
+ @log = log
69
+ end
70
+
71
+ def run(sys, postman, opts)
72
+ sys.issues(opts["sql"])
73
+ .group_by(&:assignee)
74
+ .each do |a, t|
75
+ postman.send opts.merge(to: a.email, addressee: a.name, tickets: t)
76
+ end
77
+ end
78
+ end
79
+
80
+ #
81
+ # A task that sends notifications about issues to their reporters.
82
+ #
83
+ # The task supports the following features:
84
+ # - fetch issues from remote ticketing system by query
85
+ # - group all issues by reporter
86
+ # - prepare email based on predefined template (*.erb)
87
+ # - send the required notifications to each reporter
88
+ #
89
+ # The email message is sending to the assignee regarding all his/her issues,
90
+ # not like one email per each issue.
91
+ class ReporterAlert
92
+ def initialize(log = Log::NOTHING)
93
+ @log = log
94
+ end
95
+
96
+ def run(sys, postman, opts)
97
+ sys.issues(opts["sql"])
98
+ .group_by(&:reporter)
99
+ .each do |a, t|
100
+ postman.send opts.merge(to: a.email, addressee: a.name, tickets: t)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,59 @@
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 "json"
26
+ require "faraday"
27
+ require_relative "../system/jira"
28
+ require_relative "../log"
29
+ require_relative "../confluence"
30
+
31
+ module Lazylead
32
+ module Task
33
+ # The lazylead task which adds reference to Confluence page
34
+ # in case if Confluence page was mentioned in issue comments.
35
+ # @todo #/DEV Support sub-task for link search. Potentially, the issue
36
+ # might have sub-tasks where discussion ongoing.
37
+ class ConfluenceRef
38
+ def initialize(log = Log::NOTHING)
39
+ @log = log
40
+ end
41
+
42
+ def run(sys, _, opts)
43
+ confluences = confluences(opts)
44
+ return if confluences.empty?
45
+ sys.issues(opts["jql"])
46
+ .map { |i| Link.new(i, sys, confluences) }
47
+ .each(&:fetch_links)
48
+ .select(&:need_link?)
49
+ .each(&:add_link)
50
+ end
51
+
52
+ def confluences(opts)
53
+ return [] if opts["confluences"].nil? || opts["confluences"].blank?
54
+ JSON.parse(opts["confluences"], object_class: OpenStruct)
55
+ .map { |c| Confluence.new(c) }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -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
+ module Lazylead
26
+ module Task
27
+ # Lazylead task which prints to STDOUT the current class name and team.
28
+ #
29
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
30
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
31
+ # License:: MIT
32
+ class Echo
33
+ def run(_, _, _)
34
+ self.class.to_s
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,79 @@
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 "date"
26
+ require_relative "../system/jira"
27
+ require_relative "../log"
28
+
29
+ module Lazylead
30
+ module Task
31
+ # @todo #/DEV Each task should verify input arguments.
32
+ # The common API should be provided for each task.
33
+ class FixVersion
34
+ def initialize(log = Log::NOTHING)
35
+ @log = log
36
+ end
37
+
38
+ def run(sys, postman, opts)
39
+ allowed = opts["allowed"].split(",").map(&:strip).reject(&:blank?)
40
+ postman.send opts.merge(
41
+ versions: sys.issues(opts["jql"], expand: "changelog")
42
+ .map { |i| Version.new(i, allowed) }
43
+ .select(&:changed?)
44
+ )
45
+ end
46
+ end
47
+
48
+ # Instance of "Fix Version" field for the particular task.
49
+ class Version
50
+ attr_reader :issue
51
+
52
+ def initialize(issue, allowed)
53
+ @issue = issue
54
+ @allowed = allowed
55
+ end
56
+
57
+ # Gives true when last change of "Fix Version" field was done
58
+ # by not authorized person.
59
+ def changed?
60
+ @allowed.none? do |a|
61
+ return false if last.nil?
62
+ a == last["author"]["name"]
63
+ end
64
+ end
65
+
66
+ # Detect details about last change of "Fix Version" to non-null value
67
+ def last
68
+ return @last if defined? @last
69
+ @last = issue.history
70
+ .reverse
71
+ .find do |h|
72
+ h["items"].any? do |i|
73
+ i["field"] == "Fix Version" && !i["to"].nil?
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,53 @@
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 "../postman"
27
+
28
+ module Lazylead
29
+ module Task
30
+ # A task which fetch issues by particular ticketing system query language
31
+ # and find issues where comments has no expected text.
32
+ #
33
+ # Example:
34
+ # you have a sub-task for demo session for your story;
35
+ # demo session is recorded, placed to ftp;
36
+ # nobody mentioned in comment the ftp location for recorded session.
37
+ # Such cases needs to be reported.
38
+ class MissingComment
39
+ def initialize(log = Log::NOTHING)
40
+ @log = log
41
+ end
42
+
43
+ def run(sys, postman, opts)
44
+ opts["details"] = "text '#{opts['text']}'" if opts["details"].blank?
45
+ postman.send opts.merge(
46
+ comments: sys.issues(opts["jql"])
47
+ .map { |i| Comments.new(i, sys) }
48
+ .reject { |c| c.body? opts["text"] }
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,27 @@
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
+ module Lazylead
26
+ VERSION = "0.1.0"
27
+ end
@@ -0,0 +1,96 @@
1
+ <html>
2
+ <head>
3
+ <style>
4
+ /* CSS styles taken from https://github.com/yegor256/tacit */
5
+ th {
6
+ font-weight: 600
7
+ }
8
+
9
+ table tr {
10
+ border-bottom-width: 2.16px
11
+ }
12
+
13
+ table tr th {
14
+ border-bottom-width: 2.16px
15
+ }
16
+
17
+ table tr td, table tr th {
18
+ overflow: hidden;
19
+ padding: 5.4px 3.6px
20
+ }
21
+
22
+ a {
23
+ color: #275a90;
24
+ text-decoration: none
25
+ }
26
+
27
+ a:hover {
28
+ text-decoration: underline
29
+ }
30
+
31
+ * {
32
+ border: 0;
33
+ border-collapse: separate;
34
+ border-spacing: 0;
35
+ box-sizing: border-box;
36
+ margin: 0;
37
+ max-width: 100%;
38
+ padding: 0;
39
+ vertical-align: baseline;
40
+ font-family: system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif;
41
+ font-size: 13px;
42
+ font-stretch: normal;
43
+ font-style: normal;
44
+ font-weight: 400;
45
+ line-height: 29.7px
46
+ }
47
+
48
+ html, body {
49
+ width: 100%
50
+ }
51
+
52
+ html {
53
+ height: 100%
54
+ }
55
+
56
+ body {
57
+ background: #fff;
58
+ color: #1a1919;
59
+ padding: 36px
60
+ }
61
+ </style>
62
+ <title>Expired due date</title>
63
+ </head>
64
+ <body>
65
+ <p>Hi <%= addressee %>,</p>
66
+
67
+ <p>The due date for these tasks is expired or seems to expire soon. Please plan
68
+ them accordingly or actualize the
69
+ <span style="font-weight:bold">"Due date"</span> field for the following
70
+ ticket(s):</p>
71
+ <table>
72
+ <tr>
73
+ <th>Key</th>
74
+ <th>Due date</th>
75
+ <th>Priority</th>
76
+ <th>Reporter</th>
77
+ <th>Summary</th>
78
+ </tr>
79
+ <% tickets.each do |ticket| %>
80
+ <tr>
81
+ <td><a href="<%= ticket.url %>"><%= ticket.key %></a></td>
82
+ <td><%= ticket.duedate %></td>
83
+ <td><%= ticket.priority %></td>
84
+ <td><%= ticket.reporter.name %></td>
85
+ <td><%= ticket.summary %></td>
86
+ </tr>
87
+ <% end %>
88
+ </table>
89
+ <p>In case you have no idea what date should be set - remove obsolete date or
90
+ ask your lead to fill it.</p>
91
+
92
+ <p>Posted by
93
+ <a href="https://github.com/dgroup/lazylead">lazylead v<%= version %></a>.
94
+ </p>
95
+ </body>
96
+ </html>