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,91 @@
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_support"
26
+
27
+ module Lazylead
28
+ #
29
+ # A cryptography salt defined in environment variables.
30
+ #
31
+ # Salt is random data that is used as an additional input to a one-way
32
+ # function that hashes data, a password or passphrase. Salts are used to
33
+ # safeguard passwords in storage. Historically a password was stored in
34
+ # plaintext on a system, but over time additional safeguards developed to
35
+ # protect a user's password against being read from the system. A salt is one
36
+ # of those methods.
37
+ #
38
+ # Read more: https://en.wikipedia.org/wiki/Salt_(cryptography).
39
+ #
40
+ class Salt
41
+ attr_reader :id
42
+ #
43
+ # Each salt should be defined as a environment variable with id, like
44
+ # salt1=E1F53135E559C253
45
+ # salt2=84B03D034B409D4E
46
+ # ...
47
+ # saltN=xxxxxxxxx
48
+ #
49
+ def initialize(id, env = ENV.to_h)
50
+ @id = id
51
+ @env = env
52
+ end
53
+
54
+ def encrypt(password)
55
+ ActiveSupport::MessageEncryptor.new(@env[@id]).encrypt_and_sign password
56
+ end
57
+
58
+ def decrypt(password)
59
+ ActiveSupport::MessageEncryptor.new(@env[@id]).decrypt_and_verify password
60
+ end
61
+
62
+ def specified?
63
+ @env.key?(@id) && !@env[@id].blank?
64
+ end
65
+ end
66
+
67
+ #
68
+ # No cryptography salt defined within environment variables.
69
+ #
70
+ class NoSalt
71
+ def id
72
+ "No salt"
73
+ end
74
+
75
+ def encrypt(_)
76
+ raise "ll-003: Unsupported operation: 'encrypt'"
77
+ end
78
+
79
+ def decrypt(_)
80
+ raise "ll-004: Unsupported operation: 'decrypt'"
81
+ end
82
+
83
+ def specified?
84
+ false
85
+ end
86
+
87
+ def key
88
+ raise "ll-005: Unsupported operation: 'key'"
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,88 @@
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 "rufus-scheduler"
27
+ require_relative "log"
28
+ require_relative "model"
29
+
30
+ module Lazylead
31
+ # The tasks schedule
32
+ #
33
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
34
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
35
+ # License:: MIT
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::NOTHING, cling = true)
44
+ @log = log
45
+ @cling = cling
46
+ @trigger = Rufus::Scheduler.new
47
+ end
48
+
49
+ # @todo #/DEV error code is required for reach 'raise' statement within the
50
+ # application.
51
+ def register(task)
52
+ raise "ll-002: task can't be a null" if task.nil?
53
+ @trigger.cron task.cron do
54
+ task.exec @log
55
+ end
56
+ @log.debug "Task scheduled: #{task}"
57
+ end
58
+
59
+ # @todo #/DEV inspect the current execution status. This method should
60
+ # support several format for output, by default is `json`.
61
+ def ps; end
62
+
63
+ def join
64
+ @trigger.join if @cling
65
+ end
66
+
67
+ # @todo #/DEV stop the execution of current jobs (shutdown?).
68
+ # The test is required.
69
+ def stop
70
+ @trigger.shutdown(:kill)
71
+ end
72
+ end
73
+
74
+ # Fake application schedule for unit testing purposes
75
+ class NoSchedule
76
+ def initialize(log = Log::NOTHING)
77
+ @log = log
78
+ end
79
+
80
+ def register(task)
81
+ @log.debug("Task registered: #{task}")
82
+ end
83
+
84
+ def ps; end
85
+
86
+ def join; end
87
+ end
88
+ end
@@ -0,0 +1,82 @@
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 "mail"
26
+ require_relative "log"
27
+ require_relative "salt"
28
+
29
+ module Lazylead
30
+ #
31
+ # The emails configuration over SMTP protocol.
32
+ #
33
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
34
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
35
+ # License:: MIT
36
+ class Smtp
37
+ def initialize(log = Log::NOTHING, salt = NoSalt.new, opts = {})
38
+ @log = log
39
+ @salt = salt
40
+ @opts = opts
41
+ end
42
+
43
+ def enable
44
+ if @opts.empty? || @opts[:test_mode] || @opts[:smtp_host].blank?
45
+ Mail.defaults do
46
+ delivery_method :test
47
+ end
48
+ @log.warn "SMTP connection enabled in test mode."
49
+ else
50
+ setup_smtp
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def setup_smtp
57
+ host = @opts[:smtp_host]
58
+ port = @opts[:smtp_port]
59
+ user = decrypted(:smtp_user)
60
+ pass = decrypted(:smtp_pass)
61
+ Mail.defaults do
62
+ delivery_method :smtp,
63
+ address: host,
64
+ port: port,
65
+ user_name: user,
66
+ password: pass,
67
+ authentication: "plain",
68
+ enable_starttls_auto: true
69
+ end
70
+ @log.debug "SMTP connection established with #{host} as #{user}."
71
+ end
72
+
73
+ # Decrypt the value of configuration property using cryptography salt.
74
+ def decrypted(key)
75
+ if @salt.specified?
76
+ @salt.decrypt(@opts[key])
77
+ else
78
+ @opts[key]
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,36 @@
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
+ module Lazylead
25
+ #
26
+ # A empty ticketing system
27
+ #
28
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
29
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
30
+ # License:: MIT
31
+ class Empty
32
+ def issues(_)
33
+ []
34
+ end
35
+ end
36
+ end
@@ -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
+ #
27
+ # A fake ticketing system
28
+ #
29
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
30
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
31
+ # License:: MIT
32
+ class Fake
33
+ def initialize(issues = [])
34
+ @issues = issues
35
+ end
36
+
37
+ def issues(_)
38
+ @issues
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,249 @@
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 "jira-ruby"
26
+ require_relative "../salt"
27
+
28
+ module Lazylead
29
+ # Jira system for manipulation with issues.
30
+ #
31
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
32
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
33
+ # License:: MIT
34
+ class Jira
35
+ # @todo #57/DEV The debug method should be moved outside of ctor.
36
+ # This was moved here from 'client' method because Rubocop failed the build
37
+ # due to 'Metrics/AbcSize' violation.
38
+ def initialize(opts, salt = NoSalt.new, log = Log::NOTHING)
39
+ @opts = opts
40
+ @salt = salt
41
+ @log = log
42
+ @log.debug "Initiate a Jira client using following opts: " \
43
+ "#{@opts.except 'password', :password} " \
44
+ " and salt #{@salt.id} (found=#{@salt.specified?})"
45
+ end
46
+
47
+ def issues(jql, opts = {})
48
+ raw do |jira|
49
+ jira.Issue.jql(jql, opts).map { |i| Lazylead::Issue.new(i) }
50
+ end
51
+ end
52
+
53
+ # Execute request to the ticketing system using raw client.
54
+ # For Jira the raw client is 'jira-ruby' gem.
55
+ def raw
56
+ raise "ll-06: No block given to method" unless block_given?
57
+ yield client
58
+ end
59
+
60
+ private
61
+
62
+ def client
63
+ return @client if defined? @client
64
+ @opts[:auth_type] = :basic if @opts[:auth_type].nil?
65
+ @opts["username"] = @salt.decrypt(@opts["username"]) if @salt.specified?
66
+ @opts["password"] = @salt.decrypt(@opts["password"]) if @salt.specified?
67
+ cp("site", :site)
68
+ cp("username", :username)
69
+ cp("password", :password)
70
+ cp("context_path", :context_path)
71
+ @client = JIRA::Client.new(@opts)
72
+ end
73
+
74
+ # Copy the required/mandatory parameter(s) for Jira client which can't
75
+ # be specified/defined at database level.
76
+ #
77
+ # @todo #/DEV Jira.cp - find a way how to avoid this method.
78
+ # Potentially, hash with indifferent access might be used.
79
+ # http://jocellyn.cz/2014/05/03/hash-with-indifferent-access.html
80
+ # key.kind_of?(Symbol) ? key.to_s : key
81
+ def cp(act, exp)
82
+ @opts[exp] = @opts[act] if @opts.key? act
83
+ end
84
+ end
85
+
86
+ #
87
+ # An user from Jira which might be reporter, assignee, etc.
88
+ #
89
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
90
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
91
+ # License:: MIT
92
+ class User
93
+ def initialize(usr)
94
+ @usr = usr
95
+ end
96
+
97
+ def id
98
+ @usr["name"]
99
+ end
100
+
101
+ def name
102
+ @usr["displayName"]
103
+ end
104
+
105
+ def email
106
+ @usr["emailAddress"]
107
+ end
108
+
109
+ def ==(other)
110
+ if other.respond_to?(:id)
111
+ other.id == id
112
+ else
113
+ false
114
+ end
115
+ end
116
+
117
+ alias eql? ==
118
+
119
+ def hash
120
+ id.hash
121
+ end
122
+
123
+ def to_s
124
+ inspect
125
+ end
126
+
127
+ def inspect
128
+ "#{@opts['site']} (#{@opts['username']})"
129
+ end
130
+ end
131
+
132
+ #
133
+ # An issue from Jira
134
+ #
135
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
136
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
137
+ # License:: MIT
138
+ class Issue
139
+ def initialize(issue)
140
+ @issue = issue
141
+ end
142
+
143
+ def id
144
+ @issue.id
145
+ end
146
+
147
+ def key
148
+ @issue.key
149
+ end
150
+
151
+ def summary
152
+ fields["summary"]
153
+ end
154
+
155
+ def url
156
+ @issue.attrs["self"].split("/rest/api/").first + "/browse/" + key
157
+ end
158
+
159
+ def duedate
160
+ @issue.fields["duedate"]
161
+ end
162
+
163
+ def priority
164
+ fields["priority"]["name"]
165
+ end
166
+
167
+ def reporter
168
+ Lazylead::User.new(fields["reporter"])
169
+ end
170
+
171
+ def assignee
172
+ Lazylead::User.new(@issue.assignee.attrs)
173
+ end
174
+
175
+ def fields
176
+ @issue.fields
177
+ end
178
+
179
+ def history
180
+ return [] unless @issue.respond_to? :changelog
181
+ return [] if @issue.changelog == nil? || @issue.changelog.empty?
182
+ @issue.changelog["histories"]
183
+ end
184
+
185
+ def comments
186
+ @issue.comments
187
+ end
188
+
189
+ def to_s
190
+ "#{key} #{summary}"
191
+ end
192
+
193
+ def inspect
194
+ to_s
195
+ end
196
+ end
197
+
198
+ # The jira issue comments
199
+ #
200
+ # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
201
+ # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
202
+ # License:: MIT
203
+ class Comments
204
+ attr_reader :issue
205
+
206
+ def initialize(issue, sys)
207
+ @issue = issue
208
+ @sys = sys
209
+ end
210
+
211
+ def body?(text)
212
+ comments.any? { |c| c.attrs["body"].include? text }
213
+ end
214
+
215
+ def comments
216
+ @comments ||= @sys.raw do |conn|
217
+ conn.Issue.find(@issue.id, expand: "comments,changelog", fields: "")
218
+ .comments
219
+ end
220
+ end
221
+
222
+ def last(quantity)
223
+ comments.last(quantity).map { |c| c.attrs["body"] }
224
+ end
225
+ end
226
+
227
+ # Jira instance without authentication in order to access public filters
228
+ # or dashboards.
229
+ class NoAuthJira
230
+ def initialize(url, log = Log::NOTHING)
231
+ @jira = Jira.new(
232
+ { username: nil, password: nil, site: url, context_path: "" },
233
+ NoSalt.new,
234
+ log
235
+ )
236
+ end
237
+
238
+ def issues(jql, opts = {})
239
+ @jira.issues(jql, opts)
240
+ end
241
+
242
+ # Execute request to the ticketing system using raw client.
243
+ # For Jira the raw client is 'jira-ruby' gem.
244
+ def raw(&block)
245
+ raise "ll-07: No block given to method" unless block_given?
246
+ @jira.raw(&block)
247
+ end
248
+ end
249
+ end