lazylead 0.10.2 → 0.11.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.githooks/commit-msg +13 -0
  3. data/.rultor.yml +1 -0
  4. data/Rakefile +9 -1
  5. data/lazylead.gemspec +8 -7
  6. data/lib/lazylead/cli/app.rb +1 -1
  7. data/lib/lazylead/model.rb +24 -3
  8. data/lib/lazylead/opts.rb +11 -0
  9. data/lib/lazylead/schedule.rb +8 -1
  10. data/lib/lazylead/system/jira.rb +4 -3
  11. data/lib/lazylead/task/accuracy/accuracy.rb +32 -4
  12. data/lib/lazylead/task/accuracy/has_label.rb +48 -0
  13. data/lib/lazylead/task/accuracy/logs_link.rb +2 -2
  14. data/lib/lazylead/task/accuracy/memes.rb +77 -0
  15. data/lib/lazylead/task/accuracy/records.rb +3 -1
  16. data/lib/lazylead/task/accuracy/records_link.rb +49 -0
  17. data/lib/lazylead/task/accuracy/wiki_url.rb +46 -0
  18. data/lib/lazylead/version.rb +1 -1
  19. data/lib/messages/svn_grep.erb +1 -0
  20. data/test/lazylead/exchange_test.rb +21 -14
  21. data/test/lazylead/opts_test.rb +16 -0
  22. data/test/lazylead/retry_test.rb +85 -0
  23. data/test/lazylead/system/jira_test.rb +13 -5
  24. data/test/lazylead/task/accuracy/accuracy_test.rb +5 -0
  25. data/test/lazylead/task/accuracy/has_label_test.rb +54 -0
  26. data/test/lazylead/task/accuracy/memes_test.rb +77 -0
  27. data/test/lazylead/task/accuracy/records_llink_test.rb +55 -0
  28. data/test/lazylead/task/accuracy/records_test.rb +16 -0
  29. data/test/lazylead/task/accuracy/score_test.rb +7 -0
  30. data/test/lazylead/task/accuracy/screenshots_test.rb +2 -0
  31. data/test/lazylead/task/accuracy/wiki_url_test.rb +58 -0
  32. data/test/lazylead/task/svn/grep_test.rb +1 -2
  33. data/test/lazylead/task/svn/touch_test.rb +1 -1
  34. data/test/sqlite_test.rb +1 -1
  35. data/test/test.rb +47 -0
  36. metadata +42 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fa41cd073bf1df41fee51ab890e6fd72a379bc0f4fd3d6db118194bbbcad85f
4
- data.tar.gz: 310978fbc087cb95064a28c86e48f79cd03928217f03701df7976a74d8b0442b
3
+ metadata.gz: 25f9021629329ee9ec02db8d6d8d51a0daaffcfccdc6e63627e12644f5775aab
4
+ data.tar.gz: 5560dc06b0b2c67ce56ff918ebe3eeb1b28d0bf22457c3c539f0229cf7db8690
5
5
  SHA512:
6
- metadata.gz: bacaf9e2ba2a774e979e4957ca8e18623188a419b0a9f5363ea237692dc9977376e78411bbe0fcf0f575a8ac80352258aaa35236554c824302ffa99d4994ebf3
7
- data.tar.gz: 7df938335a3179983280f254194cb9f1e8e6b3b0de2cf890ed97a38b7056d5aa0a3ec073ec0a3e1c47a5480c7f3f003be1f07a328e5da90a5f4f278e8c5be2ba
6
+ metadata.gz: 1240d3399f5d17087f3e844dbebe1c838f152dac9ac11635dcb4697ba5ec9c391b69dc7ccf140966e8a83fa95f12607bb46e32f50b768187335f1c43ed9b1aed
7
+ data.tar.gz: 92da12e36cba95581298efc6a3899af2f24e61d0be558b40e919fa8a5a98f871dbb565c5f1e8b7d1ad5cc53768deec7e5cc0edec94a1510cc4663e8c0abf53b8
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ def check_message_format
4
+ regex = /^#\d+:\s\w+/
5
+ message_file = ARGV[0]
6
+ message = File.read(message_file)
7
+ unless regex.match(message)
8
+ puts "Your message is not formatted correctly"
9
+ puts "Message should have following format: '#issue_id: Commit message'"
10
+ exit 1
11
+ end
12
+ end
13
+ check_message_format
data/.rultor.yml CHANGED
@@ -25,6 +25,7 @@ release:
25
25
  curl -s -X POST --url "https://circleci.com/api/v2/project/gh/dgroup/lazylead/envvar" -H @../circleci.header -H "accept: application/json" -H "content-type: application/json" -d "{ \"name\": \"DOCKER_RELEASE_TAGS\", \"value\": \"${tag}\" }" -o /dev/null
26
26
  curl -s -X POST --url https://circleci.com/api/v2/project/gh/dgroup/lazylead/pipeline -H @../circleci.header -H 'accept: application/json' -H 'content-type: application/json' -H 'x-attribution-login: dgroup' -o /dev/null
27
27
  merge:
28
+ squash: true
28
29
  script: |-
29
30
  bundle install
30
31
  bundle exec rake test rubocop sqlint xcop
data/Rakefile CHANGED
@@ -28,6 +28,7 @@ require "date"
28
28
  require "rdoc"
29
29
  require "colorize"
30
30
  require "rake/clean"
31
+ require "concurrent"
31
32
 
32
33
  # @todo #/DEV Investigate the possibility of using migrations from active_record
33
34
  # - Rake tasks https://gist.github.com/schickling/6762581
@@ -45,11 +46,18 @@ def version
45
46
  Gem::Specification.load(Dir["*.gemspec"].first).version
46
47
  end
47
48
 
48
- task default: %i[clean rubocop test sqlint xcop copyright docker]
49
+ task default: %i[init_hooks clean rubocop test sqlint xcop copyright docker]
50
+
51
+ task :init_hooks do
52
+ next if File.file?(".git/hooks/commit-msg")
53
+ FileUtils.copy(".githooks/commit-msg", ".git/hooks/commit-msg")
54
+ FileUtils.chmod("+x", ".git/hooks/commit-msg")
55
+ end
49
56
 
50
57
  require "rake/testtask"
51
58
  desc "Run all unit tests"
52
59
  Rake::TestTask.new(:test) do |t|
60
+ ENV["MT_CPU"] = Concurrent.processor_count.to_s
53
61
  t.libs << "test"
54
62
  t.libs << "lib"
55
63
  t.test_files = FileList["test/**/*_test.rb"]
data/lazylead.gemspec CHANGED
@@ -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.10.2"
35
+ s.version = "0.11.0"
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.10.2!
48
+ s.post_install_message = "Thanks for installing Lazylead v0.11.0!
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
@@ -58,7 +58,7 @@ tasks instead of solving technical problems."
58
58
  s.add_runtime_dependency "activerecord", "6.1.3"
59
59
  s.add_runtime_dependency "backtrace", "0.3"
60
60
  s.add_runtime_dependency "colorize", "0.8.1"
61
- s.add_runtime_dependency "faraday", "1.5.1"
61
+ s.add_runtime_dependency "faraday", "1.7.1"
62
62
  s.add_runtime_dependency "get_process_mem", "0.2.7"
63
63
  s.add_runtime_dependency "inifile", "3.0.0"
64
64
  s.add_runtime_dependency "jira-ruby", "2.1.5"
@@ -66,6 +66,7 @@ tasks instead of solving technical problems."
66
66
  s.add_runtime_dependency "logging", "2.3.0"
67
67
  s.add_runtime_dependency "mail", "2.7.1"
68
68
  s.add_runtime_dependency "memory_profiler", "1.0.0"
69
+ s.add_runtime_dependency "nokogiri", "1.11.3"
69
70
  s.add_runtime_dependency "openssl", "2.1.2"
70
71
  s.add_runtime_dependency "railties", "6.1.3"
71
72
  s.add_runtime_dependency "require_all", "3.0.0"
@@ -79,7 +80,7 @@ tasks instead of solving technical problems."
79
80
  s.add_runtime_dependency "tzinfo-data", "1.2021.1"
80
81
  s.add_runtime_dependency "vcs4sql", "0.1.1"
81
82
  s.add_runtime_dependency "viewpoint", "1.1.1"
82
- s.add_development_dependency "codecov", "0.5.2"
83
+ s.add_development_dependency "codecov", "0.6.0"
83
84
  s.add_development_dependency "guard", "2.18.0"
84
85
  s.add_development_dependency "guard-minitest", "2.4.6"
85
86
  s.add_development_dependency "minitest", "5.14.4"
@@ -90,9 +91,9 @@ tasks instead of solving technical problems."
90
91
  s.add_development_dependency "rake", "13.0.6"
91
92
  s.add_development_dependency "random-port", "0.5.1"
92
93
  s.add_development_dependency "rdoc", "6.3.2"
93
- s.add_development_dependency "rubocop", "1.18.3"
94
- s.add_development_dependency "rubocop-minitest", "0.14.0"
95
- s.add_development_dependency "rubocop-performance", "1.11.4"
94
+ s.add_development_dependency "rubocop", "1.20.0"
95
+ s.add_development_dependency "rubocop-minitest", "0.15.0"
96
+ s.add_development_dependency "rubocop-performance", "1.11.5"
96
97
  s.add_development_dependency "rubocop-rake", "0.6.0"
97
98
  s.add_development_dependency "rubocop-rspec", "2.4.0"
98
99
  s.add_development_dependency "sqlint", "0.2.0"
@@ -70,7 +70,7 @@ module Lazylead
70
70
  ActiveRecord::Base.establish_connection(
71
71
  adapter: "sqlite3",
72
72
  database: @db,
73
- pool: opts[:max_connections]
73
+ pool: opts[:max_connections] || ENV["MT_CPU"]
74
74
  )
75
75
  @log.debug "Database connection established"
76
76
  end
@@ -170,10 +170,9 @@ module Lazylead
170
170
 
171
171
  # A task with extended logging
172
172
  # @see Lazylead::ORM::Task
173
- class VerboseTask
173
+ class Verbose
174
174
  extend Forwardable
175
- def_delegators :@orig, :id, :name, :team, :to_s, :inspect, :props, :type,
176
- :unit
175
+ def_delegators :@orig, :id, :name, :team, :to_s, :inspect, :props, :type, :unit
177
176
 
178
177
  def initialize(orig, log = Log.new)
179
178
  @orig = orig
@@ -203,6 +202,28 @@ module Lazylead
203
202
  # rubocop:enable Metrics/AbcSize
204
203
  end
205
204
 
205
+ # A task which support retry in case of failure.
206
+ # @see Lazylead::ORM::Task
207
+ class Retry
208
+ extend Forwardable
209
+ def_delegators :@orig, :id, :name, :team, :to_s, :inspect, :props, :type, :unit
210
+
211
+ def initialize(orig, log = Log.new)
212
+ @orig = orig
213
+ @log = log
214
+ end
215
+
216
+ def exec
217
+ retries ||= 0
218
+ @orig.exec
219
+ @orig
220
+ rescue StandardError
221
+ sleep(props.fetch("attempt_wait", 0).to_f) if props.key? "attempt_wait"
222
+ retry if (retries += 1) < props.fetch("attempt", 0).to_i
223
+ @orig
224
+ end
225
+ end
226
+
206
227
  # Ticketing systems to monitor.
207
228
  class System < ActiveRecord::Base
208
229
  include ORM
data/lib/lazylead/opts.rb CHANGED
@@ -142,5 +142,16 @@ module Lazylead
142
142
  def construct(field, delim: ",")
143
143
  slice(field, delim).map(&:constantize).map(&:new)
144
144
  end
145
+
146
+ # Get the value by key
147
+ # @param key
148
+ # The key to fetch value. The key might be a plain text or symbol
149
+ # @param default
150
+ # The default/alternative value if key not found
151
+ # @return The value by key or alternative/default value.
152
+ def find(key, default = nil)
153
+ return default if key.nil?
154
+ to_h[key.to_s] || to_h[key.to_sym] || default
155
+ end
145
156
  end
146
157
  end
@@ -47,7 +47,14 @@ module Lazylead
47
47
  raise "ll-002: task can't be a null" if task.nil?
48
48
  @trigger.method(task.type).call(task.unit) do
49
49
  ActiveRecord::Base.connection_pool.with_connection do
50
- ORM::VerboseTask.new(task, @log).exec
50
+ if task.props.key? "no_logs"
51
+ ORM::Retry.new(task, @log).exec
52
+ else
53
+ ORM::Verbose.new(
54
+ ORM::Retry.new(task, @log),
55
+ @log
56
+ ).exec
57
+ end
51
58
  end
52
59
  end
53
60
  @log.debug "Task scheduled: #{task}"
@@ -57,6 +57,7 @@ module Lazylead
57
57
  # :max_results => maximum number of tickets per one iteration.
58
58
  # :fields => ticket fields to be fetched like assignee, summary, etc.
59
59
  def issues(jql, opts = { max_results: 50, fields: nil, expand: nil })
60
+ opts = Opts.new(opts)
60
61
  raw do |jira|
61
62
  start = 0
62
63
  tickets = []
@@ -66,8 +67,8 @@ module Lazylead
66
67
  tickets.concat(jira.Issue.jql(jql, opts.merge(start_at: start))
67
68
  .map { |i| Lazylead::Issue.new(i, jira) })
68
69
  @log.debug "Fetched #{tickets.size}"
69
- start += opts.fetch(:max_results, 50).to_i
70
- break if start > total
70
+ start += opts.find(:max_results, 50)
71
+ break if (start > total) || (start >= opts.find(:limit, total))
71
72
  end
72
73
  tickets
73
74
  end
@@ -326,7 +327,7 @@ module Lazylead
326
327
  # or dashboards.
327
328
  class NoAuthJira
328
329
  extend Forwardable
329
- def_delegators :@jira, :issues, :raw
330
+ def_delegators :@jira, :issues, :raw, :max_results, :limit
330
331
 
331
332
  def initialize(url, path = "", log = Log.new)
332
333
  @jira = Jira.new(
@@ -22,6 +22,7 @@
22
22
  # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
+ require_relative "memes"
25
26
  require_relative "../../log"
26
27
  require_relative "../../opts"
27
28
  require_relative "../../email"
@@ -44,15 +45,21 @@ module Lazylead
44
45
  end
45
46
 
46
47
  def run(sys, postman, opts)
47
- Requires.new(__dir__).load
48
- opts[:rules] = opts.construct("rules")
49
- opts[:total] = opts[:rules].sum(&:score)
48
+ init_rules(opts)
50
49
  opts[:tickets] = sys.issues(opts["jql"], opts.jira_defaults.merge(expand: "changelog"))
51
50
  .map { |i| Score.new(i, opts) }
52
51
  .each(&:evaluate)
53
52
  .each(&:post)
54
53
  postman.send(opts) unless opts[:tickets].empty?
55
54
  end
55
+
56
+ # Initialize accuracy rules and configuration for tickets verification.
57
+ def init_rules(opts)
58
+ Requires.new(__dir__).load
59
+ opts[:rules] = opts.construct("rules")
60
+ opts[:total] = opts[:rules].sum(&:score)
61
+ opts[:memes] = Memes.new(opts["memes"])
62
+ end
56
63
  end
57
64
  end
58
65
 
@@ -93,7 +100,7 @@ module Lazylead
93
100
  comment << "|#{r.desc}|#{r.passed(@issue) ? '(/)' : '(-)'}|#{r.field}|"
94
101
  end
95
102
  comment << docs_link
96
- comment << ""
103
+ comment << reaction
97
104
  comment << "Posted by [lazylead v#{Lazylead::VERSION}|https://bit.ly/2NjdndS]."
98
105
  comment.join("\r\n")
99
106
  end
@@ -144,5 +151,26 @@ module Lazylead
144
151
  return @issue.reporter.id if first.nil?
145
152
  first["author"]["key"]
146
153
  end
154
+
155
+ # Add reaction meme to the ticket comment based on score.
156
+ # The meme details are represented as array, where each element is a separate line in future
157
+ # comment in jira.
158
+ #
159
+ # @todo #339/DEV Seems jira doesn't support the rendering of external images by url, thus so far
160
+ # we might have several options:
161
+ # - attach meme to ticket and make rendering using [^attach.jpg!thumbnail] option
162
+ # - have a link to meme (like it implemented now)
163
+ # The 1st option with attachment might generate multiple events in jira and spam ticket
164
+ # watchers, thus, some research & UX testing needed how to make it better.
165
+ def reaction
166
+ return [] if @opts[:memes].nil? && !@opts[:memes].enabled?
167
+ url = @opts[:memes].find(@accuracy)
168
+ return [] if url.blank?
169
+ [
170
+ "",
171
+ "Our reaction when we got the ticket with triage accuracy #{@accuracy}% is [here|#{url}].",
172
+ ""
173
+ ]
174
+ end
147
175
  end
148
176
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The MIT License
4
+ #
5
+ # Copyright (c) 2019-2021 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 "requirement"
26
+
27
+ module Lazylead
28
+ # Check that ticket has specified label.
29
+ class HasLabel < Lazylead::Requirement
30
+ #
31
+ # @param label which should be present in ticket.
32
+ #
33
+ def initialize(label, desc: "Has label", score: 0.5)
34
+ super(desc, score, "Labels")
35
+ @label = label
36
+ end
37
+
38
+ def passed(issue)
39
+ return false if issue.labels.empty?
40
+ issue.labels.any? { |l| match?(l) }
41
+ end
42
+
43
+ # Ensure that particular label matches expected label.
44
+ def match?(label)
45
+ @label.eql? label
46
+ end
47
+ end
48
+ end
@@ -30,7 +30,7 @@ module Lazylead
30
30
  # or attachment with plain log file
31
31
  class LogsLink < Lazylead::Logs
32
32
  def initialize(link)
33
- super
33
+ super()
34
34
  @link = link
35
35
  end
36
36
 
@@ -48,7 +48,7 @@ module Lazylead
48
48
  .flat_map(&:split)
49
49
  .reject(&:blank?)
50
50
  .any? do |word|
51
- @link.is_a?(Array) ? @link.any? { |l| word.start_with? l } : word.start_with?(@link)
51
+ @link.is_a?(Array) ? @link.any? { |l| word.include? l } : word.include?(@link)
52
52
  end
53
53
  end
54
54
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The MIT License
4
+ #
5
+ # Copyright (c) 2019-2021 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_relative "../../log"
27
+ require_relative "../../opts"
28
+
29
+ module Lazylead
30
+ # Meme generator based on tickets accuracy.
31
+ # For each range of scores there might be several available memes to be chosen randomly.
32
+ # If no meme found for score, empty url should be provided.
33
+ #
34
+ # @todo #339/DEV Prepare the meme list and attach them to the github, section .docs/accuracy/memes
35
+ # As url they will be required for configuration of the each task.
36
+ class Memes
37
+ def initialize(memes, log: Log.new)
38
+ @log = log
39
+ @memes = memes
40
+ end
41
+
42
+ def enabled?
43
+ !@memes.nil? && !range.empty?
44
+ end
45
+
46
+ # Detect meme url based on accuracy score.
47
+ # @param score
48
+ # Accuracy score in percentage
49
+ def find(score)
50
+ r = range.find { |m| score >= m[0] && score <= m[1] }
51
+ return "" if r.nil?
52
+ r.last.sample
53
+ end
54
+
55
+ # Parse json-based memes configuration.
56
+ # Expected format is
57
+ # "memes": {
58
+ # "0-9.9": "https://meme.com?id=awful1.gif,https://meme.com?id=awful2.gif",
59
+ # "70-89.9": "https://meme.com?id=nice.gif",
60
+ # "90-100": "https://meme.com?id=wow.gif"
61
+ # }
62
+ def range
63
+ @range ||= if @memes.nil?
64
+ []
65
+ else
66
+ rng = JSON.parse(@memes).to_h
67
+ return [] if rng.empty?
68
+ rng.map do |k, v|
69
+ next unless k.include? "-"
70
+ row = k.split("-").map(&:to_f)
71
+ row << v.split(",")
72
+ row
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end