lazylead 0.8.0 → 0.9.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 422600b27880dd0aa96889f7fb8e2f55dc371ac8638dc7065d5e57bc8556aece
4
- data.tar.gz: 4c8fd293522b890072ef45af8fbefe5cce59594023a38e12ef275e5ef3453d90
3
+ metadata.gz: 2cbd092ef55bfdcf7887c4e0fcf5e5838a70b91eac82011893dc9423b22a0b74
4
+ data.tar.gz: 5d71b27626fbf5e9d60ea38cce1e06ab6637aa874daa057ff9b9f8451b095dbf
5
5
  SHA512:
6
- metadata.gz: 59932534b571e92e9c8877d365292de759081444ac629c7d59d9b09897519ca19043d6886afc45291df327af3e4eec2524e0019e159ad0564e270eeceb0cad8f
7
- data.tar.gz: cd6cdbe78ca38b738005fc91184e7fe4390276fcde446da0180f009d406d081cceb5e617ab62a76d9417559b071972d603a1b4adfbe4a70fba98b482d8f84ebf
6
+ metadata.gz: 748f97ab75655642143bd13d562aab38693df3f6496429d794e75f4c6bb9f0315537ad656027f639a9481ed0399345b3cef0f9ebcd83af680c69f5cf3be6380d
7
+ data.tar.gz: ea40e803198075657c05df31b251599aac4dbb55bf628a01e1d68bcffeedd09c713ca9d0858d153301ab88f518e5ab50a1486fbc58be3801e5a57264611d915c
@@ -20,4 +20,4 @@ services:
20
20
  volumes:
21
21
  - .local/dumps:/lazylead/dumps
22
22
  - .local/logs:/lazylead/logs
23
- entrypoint: bin/lazylead --trace --verbose
23
+ entrypoint: bin/lazylead --trace --verbose --log-file /lazylead/logs/ll{{.%Y-%m-%dT%H:%M:%S}}.log
data/.rubocop.yml CHANGED
@@ -3,6 +3,7 @@ require:
3
3
  - rubocop-performance
4
4
 
5
5
  AllCops:
6
+ NewCops: enable
6
7
  DisplayCopNames: true
7
8
  DisplayStyleGuide: true
8
9
  TargetRubyVersion: 2.6
@@ -14,6 +15,7 @@ Layout/LineLength:
14
15
  Exclude:
15
16
  - "*.gemspec"
16
17
  - "test/**/*"
18
+ - "bin/lazylead"
17
19
 
18
20
  Metrics/AbcSize:
19
21
  Max: 21
data/Rakefile CHANGED
@@ -45,7 +45,7 @@ def version
45
45
  Gem::Specification.load(Dir["*.gemspec"].first).version
46
46
  end
47
47
 
48
- task default: %i[clean test rubocop sqlint xcop copyright docker]
48
+ task default: %i[clean rubocop test sqlint xcop copyright docker]
49
49
 
50
50
  require "rake/testtask"
51
51
  desc "Run all unit tests"
data/bin/lazylead CHANGED
@@ -70,6 +70,9 @@ Available options:"
70
70
  o.bool "--testdata",
71
71
  "Apply the database VCS migration with test data",
72
72
  default: false
73
+ o.string "--log-file", "The path to the log file"
74
+ o.string "--rolling-age", "The maximum age (in seconds) of a log file before it is rolled. The age can also be given as 'daily', 'weekly', or 'monthly'"
75
+ o.string "--rolling-files", "The number of rolled log files to keep."
73
76
  o.on "--verbose", "Enable extra logging information" do
74
77
  log.verbose
75
78
  end
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.8.0"
35
+ s.version = "0.9.1"
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.8.0!
48
+ s.post_install_message = "Thanks for installing Lazylead v0.9.1!
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.3.0"
61
+ s.add_runtime_dependency "faraday", "1.4.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"
@@ -69,6 +69,7 @@ tasks instead of solving technical problems."
69
69
  s.add_runtime_dependency "openssl", "2.1.2"
70
70
  s.add_runtime_dependency "railties", "6.1.3"
71
71
  s.add_runtime_dependency "require_all", "3.0.0"
72
+ s.add_runtime_dependency "rubyzip", "2.3.0"
72
73
  s.add_runtime_dependency "rufus-scheduler", "3.7.0"
73
74
  s.add_runtime_dependency "slop", "4.8.2"
74
75
  s.add_runtime_dependency "sqlite3", "1.4.2"
@@ -78,7 +79,7 @@ tasks instead of solving technical problems."
78
79
  s.add_runtime_dependency "tzinfo-data", "1.2021.1"
79
80
  s.add_runtime_dependency "vcs4sql", "0.1.1"
80
81
  s.add_runtime_dependency "viewpoint", "1.1.1"
81
- s.add_development_dependency "codecov", "0.5.1"
82
+ s.add_development_dependency "codecov", "0.5.2"
82
83
  s.add_development_dependency "guard", "2.16.2"
83
84
  s.add_development_dependency "guard-minitest", "2.4.6"
84
85
  s.add_development_dependency "minitest", "5.14.4"
@@ -89,12 +90,12 @@ tasks instead of solving technical problems."
89
90
  s.add_development_dependency "rake", "13.0.3"
90
91
  s.add_development_dependency "random-port", "0.5.1"
91
92
  s.add_development_dependency "rdoc", "6.3.0"
92
- s.add_development_dependency "rubocop", "1.11.0"
93
- s.add_development_dependency "rubocop-minitest", "0.10.3"
94
- s.add_development_dependency "rubocop-performance", "1.10.1"
93
+ s.add_development_dependency "rubocop", "1.13.0"
94
+ s.add_development_dependency "rubocop-minitest", "0.12.1"
95
+ s.add_development_dependency "rubocop-performance", "1.11.0"
95
96
  s.add_development_dependency "rubocop-rake", "0.5.1"
96
- s.add_development_dependency "rubocop-rspec", "2.2.0"
97
- s.add_development_dependency "sqlint", "0.1.10"
97
+ s.add_development_dependency "rubocop-rspec", "2.3.0"
98
+ s.add_development_dependency "sqlint", "0.2.0"
98
99
  s.add_development_dependency "tempfile", "0.1.1"
99
100
  s.add_development_dependency "xcop", "0.6.2"
100
101
  end
data/lib/lazylead/cc.rb CHANGED
@@ -80,13 +80,11 @@ module Lazylead
80
80
  end
81
81
 
82
82
  def cc
83
- @cc ||= begin
84
- if @text.include? ","
85
- @text.split(",").map(&:strip).select { |e| e[@regxp] }
86
- elsif @text[@regxp]
87
- [@text.strip]
88
- end
89
- end
83
+ @cc ||= if @text.include? ","
84
+ @text.split(",").map(&:strip).select { |e| e[@regxp] }
85
+ elsif @text[@regxp]
86
+ [@text.strip]
87
+ end
90
88
  end
91
89
 
92
90
  def each(&block)
@@ -125,15 +123,13 @@ module Lazylead
125
123
  end
126
124
 
127
125
  def to_h
128
- @to_h ||= begin
129
- if @orig.is_a? Hash
130
- @orig.each_with_object({}) do |i, o|
131
- o[i.first] = Lazylead::PlainCC.new(i.last).cc
132
- end
133
- else
134
- {}
135
- end
136
- end
126
+ @to_h ||= if @orig.is_a? Hash
127
+ @orig.each_with_object({}) do |i, o|
128
+ o[i.first] = Lazylead::PlainCC.new(i.last).cc
129
+ end
130
+ else
131
+ {}
132
+ end
137
133
  end
138
134
  end
139
135
 
@@ -151,12 +147,10 @@ module Lazylead
151
147
  end
152
148
 
153
149
  def to_h
154
- @to_h ||= begin
155
- components.each_with_object({}) do |c, h|
156
- email = lead(c.attrs["id"])
157
- next if email.nil? || email.blank?
158
- h[c.attrs["name"]] = email
159
- end
150
+ @to_h ||= components.each_with_object({}) do |c, h|
151
+ email = lead(c.attrs["id"])
152
+ next if email.nil? || email.blank?
153
+ h[c.attrs["name"]] = email
160
154
  end
161
155
  end
162
156
 
data/lib/lazylead/log.rb CHANGED
@@ -74,6 +74,15 @@ module Lazylead
74
74
  )
75
75
  )
76
76
 
77
+ if ARGV.include? "--log-file"
78
+ name = ARGV[ARGV.find_index("--log-file") + 1]
79
+ age = "daily"
80
+ age = ARGV[ARGV.find_index("--rolling-age") + 1] if ARGV.include? "--rolling-age"
81
+ files = 7
82
+ files = ARGV[ARGV.find_index("--rolling-files") + 1] if ARGV.include? "--rolling-files"
83
+ FILE_APPENDER = Logging.appenders.rolling_file("file", filename: name, age: age, keep: files)
84
+ end
85
+
77
86
  # Nothing to log
78
87
  NOTHING = Logging.logger["nothing"]
79
88
  NOTHING.level = :off
@@ -83,12 +92,14 @@ module Lazylead
83
92
  DEBUG = Logging.logger["debug"]
84
93
  DEBUG.level = :debug
85
94
  DEBUG.add_appenders "stdout"
95
+ DEBUG.add_appenders(FILE_APPENDER) if ARGV.include? "--log-file"
86
96
  DEBUG.freeze
87
97
 
88
98
  # Alerts/errors
89
99
  ERRORS = Logging.logger["errors"]
90
100
  ERRORS.level = :error
91
101
  ERRORS.add_appenders "stdout"
102
+ ERRORS.add_appenders(FILE_APPENDER) if ARGV.include? "--log-file"
92
103
  ERRORS.freeze
93
104
  end
94
105
  end
@@ -135,13 +135,11 @@ module Lazylead
135
135
  end
136
136
 
137
137
  def props
138
- @props ||= begin
139
- if team.nil?
140
- Opts.new(env(to_hash))
141
- else
142
- Opts.new(env(team.to_hash.merge(to_hash)))
143
- end
144
- end
138
+ @props ||= if team.nil?
139
+ Opts.new(env(to_hash))
140
+ else
141
+ Opts.new(env(team.to_hash.merge(to_hash)))
142
+ end
145
143
  end
146
144
 
147
145
  def postman
@@ -26,6 +26,7 @@ require "jira-ruby"
26
26
  require "forwardable"
27
27
  require_relative "../salt"
28
28
  require_relative "../opts"
29
+ require_relative "../log"
29
30
 
30
31
  module Lazylead
31
32
  # Jira system for manipulation with issues.
@@ -128,11 +129,7 @@ module Lazylead
128
129
  end
129
130
 
130
131
  def to_s
131
- inspect
132
- end
133
-
134
- def inspect
135
- "#{@opts['site']} (#{@opts['username']})"
132
+ "#{name} (#{id})"
136
133
  end
137
134
  end
138
135
 
@@ -256,13 +253,20 @@ module Lazylead
256
253
 
257
254
  # Update the labels for a particular issue
258
255
  def labels!(lbl)
259
- return if lbl.nil? || lbl.empty?
260
- save!("fields" => { "labels" => lbl.uniq })
256
+ save!("fields" => { "labels" => lbl.uniq }) unless lbl.empty?
261
257
  end
262
258
 
263
259
  def save!(opts)
264
260
  @issue.save(opts)
265
261
  end
262
+
263
+ def sprint(field = "customfield_10480")
264
+ @sprint ||= if fields[field].nil? || fields[field].empty?
265
+ ""
266
+ else
267
+ fields[field].first.split(",").find { |text| text.starts_with? "name=" }[5..]
268
+ end
269
+ end
266
270
  end
267
271
 
268
272
  # The jira issue comments
@@ -47,7 +47,7 @@ module Lazylead
47
47
  Requires.new(__dir__).load
48
48
  opts[:rules] = opts.construct("rules")
49
49
  opts[:total] = opts[:rules].sum(&:score)
50
- opts[:tickets] = sys.issues(opts["jql"], opts.jira_defaults)
50
+ opts[:tickets] = sys.issues(opts["jql"], opts.jira_defaults.merge(expand: "changelog"))
51
51
  .map { |i| Score.new(i, opts) }
52
52
  .each(&:evaluate)
53
53
  .each(&:post)
@@ -58,7 +58,7 @@ module Lazylead
58
58
 
59
59
  # The ticket score based on fields content.
60
60
  class Score
61
- attr_reader :issue, :total, :score, :accuracy
61
+ attr_reader :issue, :score, :accuracy
62
62
 
63
63
  def initialize(issue, opts)
64
64
  @issue = issue
@@ -70,6 +70,7 @@ module Lazylead
70
70
  def evaluate(digits = 2)
71
71
  @score = @opts[:rules].select { |r| r.passed(@issue) }.sum(&:score)
72
72
  @accuracy = (score / @opts[:total] * 100).round(digits)
73
+ self
73
74
  end
74
75
 
75
76
  # Post the comment with score and accuracy to the ticket.
@@ -82,13 +83,13 @@ module Lazylead
82
83
  # The jira comment in markdown format
83
84
  def comment
84
85
  comment = [
85
- "Hi [~#{@issue.reporter.id}],",
86
+ "Hi [~#{reporter}],",
86
87
  "",
87
88
  "The triage accuracy is '{color:#{color}}#{@score}{color}'" \
88
89
  " (~{color:#{color}}#{@accuracy}%{color}), here are the reasons why:",
89
90
  "|| Ticket requirement || Status || Field ||"
90
91
  ]
91
- @rules.each do |r|
92
+ @opts[:rules].each do |r|
92
93
  comment << "|#{r.desc}|#{r.passed(@issue) ? '(/)' : '(-)'}|#{r.field}|"
93
94
  end
94
95
  comment << docs_link
@@ -117,13 +118,11 @@ module Lazylead
117
118
  end
118
119
 
119
120
  def colors
120
- @colors ||= begin
121
- JSON.parse(@opts["colors"])
122
- .to_h
123
- .to_a
124
- .each { |e| e[0] = e[0].to_i }
125
- .sort_by { |e| e[0] }
126
- end
121
+ @colors ||= JSON.parse(@opts["colors"])
122
+ .to_h
123
+ .to_a
124
+ .each { |e| e[0] = e[0].to_i }
125
+ .sort_by { |e| e[0] }
127
126
  end
128
127
 
129
128
  # Calculate grade for accuracy
@@ -134,5 +133,17 @@ module Lazylead
134
133
  def grade(value)
135
134
  (value / 10).floor * 10
136
135
  end
136
+
137
+ # Detect the ticket reporter.
138
+ #
139
+ # If ticket created by some automatic/admin user account then reporter is the first non-system
140
+ # user account who modified the ticket.
141
+ def reporter
142
+ sys = @opts.slice("system-users", ",")
143
+ return @issue.reporter.id if sys.empty? || sys.none? { |susr| susr.eql? @issue.reporter.id }
144
+ first = @issue.history.find { |h| sys.none? { |susr| susr.eql? h["author"]["key"] } }
145
+ return @issue.reporter.id if first.nil?
146
+ first["author"]["key"]
147
+ end
137
148
  end
138
149
  end
@@ -94,13 +94,11 @@ module Lazylead
94
94
 
95
95
  # Detect the percentage grid for tickets, by default its 0%, 10%, 20%, etc.
96
96
  def grid
97
- @grid ||= begin
98
- if @opts.key? "grid"
99
- @opts.slice("grid", ",")
100
- else
101
- %w[0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%]
102
- end
103
- end
97
+ @grid ||= if @opts.key? "grid"
98
+ @opts.slice("grid", ",")
99
+ else
100
+ %w[0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%]
101
+ end
104
102
  end
105
103
 
106
104
  # Remove score labels from the ticket.
@@ -0,0 +1,65 @@
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
+ #
29
+ # Check that ticket has screenshot(s).
30
+ # The screenshots should
31
+ # 1. present as attachments
32
+ # 2. has extension .jpg .jpeg .exif .tiff .tff .bmp .png .svg
33
+ # 3. mentioned in description with !<name>.<extension>|thumbnail! (read more https://bit.ly/3rusNgW)
34
+ #
35
+ class Screenshots < Lazylead::Requirement
36
+ # @param minimum The number of expected screenshots
37
+ def initialize(minimum: 1, score: 2, ext: %w[.jpg .jpeg .exif .tiff .tff .bmp .png .svg])
38
+ super "Screenshots", score, "Description,Attachments"
39
+ @minimum = minimum
40
+ @ext = ext
41
+ end
42
+
43
+ def passed(issue)
44
+ return false if issue.attachments.nil? || blank?(issue, "description")
45
+ ref = references(issue)
46
+ ref.size < @minimum ? false : ref.all? { |r| pictures(issue).any? { |file| r.include? file } }
47
+ end
48
+
49
+ # Detect all references in ticket description to attachments (including web links).
50
+ def references(issue)
51
+ issue.description
52
+ .to_enum(:scan, /!.+!/)
53
+ .map { Regexp.last_match }
54
+ .map(&:to_s)
55
+ .reject { |r| r.match?(%r{(http|https)://.*/images/icons/link_attachment.*.gif}) }
56
+ end
57
+
58
+ # Detect all pictures in ticket attachments and returns an array with file names.
59
+ def pictures(issue)
60
+ @pictures ||= issue.attachments
61
+ .select { |a| @ext.include? File.extname(a.filename).downcase }
62
+ .map(&:filename)
63
+ end
64
+ end
65
+ end
@@ -43,14 +43,12 @@ module Lazylead
43
43
  def run(sys, postman, opts)
44
44
  allowed = opts.slice "allowed", ","
45
45
  silent = opts.key? "silent"
46
- issues = sys.issues opts["jql"],
47
- opts.jira_defaults.merge(expand: "changelog")
48
- return if issues.empty?
49
- postman.send opts.merge(
50
- assignees: issues.map { |i| Assignee.new(i, allowed, silent) }
51
- .select(&:illegal?)
52
- .each(&:add_label)
53
- )
46
+ assignees = sys.issues(opts["jql"], opts.jira_defaults.merge(expand: "changelog"))
47
+ .map { |i| Assignee.new(i, allowed, silent) }
48
+ .select(&:illegal?)
49
+ .each(&:add_label)
50
+ return if assignees.empty?
51
+ postman.send opts.merge(assignees: assignees)
54
52
  end
55
53
  end
56
54
 
@@ -67,10 +65,8 @@ module Lazylead
67
65
  # Gives true when last change of "Assignee" field was done
68
66
  # by not authorized person.
69
67
  def illegal?
70
- @allowed.none? do |a|
71
- return false if last.nil?
72
- a == last["author"]["name"]
73
- end
68
+ return false if last.nil? || @issue.assignee.id.eql?(last["author"]["name"])
69
+ @allowed.none? { |a| a.eql? last["author"]["name"] }
74
70
  end
75
71
 
76
72
  # Detect details about last change of "Assignee" to non-null value
@@ -89,7 +85,7 @@ module Lazylead
89
85
 
90
86
  # The name of current assignee for ticket
91
87
  def to
92
- @issue.fields["assignee"]["name"]
88
+ @issue.fields["assignee"]["displayName"]
93
89
  end
94
90
  end
95
91
  end