lazylead 0.7.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.docker/docker-compose.yml +1 -1
  3. data/.docs/duedate_expired.md +1 -1
  4. data/.rubocop.yml +20 -3
  5. data/Gemfile +1 -1
  6. data/Rakefile +2 -2
  7. data/bin/lazylead +4 -1
  8. data/lazylead.gemspec +15 -14
  9. data/lib/lazylead.rb +2 -2
  10. data/lib/lazylead/allocated.rb +1 -1
  11. data/lib/lazylead/cc.rb +17 -23
  12. data/lib/lazylead/cli/app.rb +1 -1
  13. data/lib/lazylead/confluence.rb +1 -1
  14. data/lib/lazylead/email.rb +1 -1
  15. data/lib/lazylead/exchange.rb +1 -1
  16. data/lib/lazylead/home.rb +1 -1
  17. data/lib/lazylead/log.rb +12 -1
  18. data/lib/lazylead/model.rb +6 -8
  19. data/lib/lazylead/opts.rb +6 -3
  20. data/lib/lazylead/postman.rb +1 -1
  21. data/{Guardfile → lib/lazylead/requires.rb} +13 -8
  22. data/lib/lazylead/salt.rb +1 -1
  23. data/lib/lazylead/schedule.rb +1 -1
  24. data/lib/lazylead/smtp.rb +1 -1
  25. data/lib/lazylead/system/empty.rb +1 -1
  26. data/lib/lazylead/system/fake.rb +1 -1
  27. data/lib/lazylead/system/jira.rb +30 -25
  28. data/lib/lazylead/system/synced.rb +1 -1
  29. data/lib/lazylead/task/accuracy/accuracy.rb +36 -28
  30. data/lib/lazylead/task/accuracy/affected_build.rb +1 -1
  31. data/lib/lazylead/task/accuracy/attachment.rb +1 -1
  32. data/lib/lazylead/task/accuracy/environment.rb +1 -1
  33. data/lib/lazylead/task/accuracy/logs.rb +1 -1
  34. data/lib/lazylead/task/accuracy/onlyll.rb +6 -8
  35. data/lib/lazylead/task/accuracy/records.rb +1 -1
  36. data/lib/lazylead/task/accuracy/requirement.rb +1 -1
  37. data/lib/lazylead/task/accuracy/screenshots.rb +60 -0
  38. data/lib/lazylead/task/accuracy/servers.rb +1 -1
  39. data/lib/lazylead/task/accuracy/stacktrace.rb +1 -1
  40. data/lib/lazylead/task/accuracy/testcase.rb +1 -1
  41. data/lib/lazylead/task/accuracy/wiki.rb +1 -1
  42. data/lib/lazylead/task/{alert.rb → alert/alert.rb} +6 -6
  43. data/lib/lazylead/task/alert/alertif.rb +75 -0
  44. data/lib/lazylead/task/alert/changed_to.rb +58 -0
  45. data/lib/lazylead/task/assignment.rb +3 -5
  46. data/lib/lazylead/task/confluence_ref.rb +1 -1
  47. data/lib/lazylead/task/echo.rb +1 -1
  48. data/lib/lazylead/task/fix_version.rb +1 -1
  49. data/lib/lazylead/task/loading.rb +98 -0
  50. data/lib/lazylead/task/micromanager.rb +87 -0
  51. data/lib/lazylead/task/missing_comment.rb +1 -1
  52. data/lib/lazylead/task/propagate_down.rb +3 -3
  53. data/lib/lazylead/task/savepoint.rb +1 -1
  54. data/lib/lazylead/task/svn/diff.rb +29 -7
  55. data/lib/lazylead/task/svn/grep.rb +1 -1
  56. data/lib/lazylead/task/svn/touch.rb +1 -1
  57. data/lib/lazylead/version.rb +2 -2
  58. data/lib/messages/accuracy.erb +1 -1
  59. data/lib/messages/alertif.erb +134 -0
  60. data/lib/messages/created_recently.erb +110 -0
  61. data/lib/messages/illegal_duedate_change.erb +120 -0
  62. data/lib/messages/loading.erb +122 -0
  63. data/readme.md +26 -18
  64. data/test/lazylead/allocated_test.rb +1 -1
  65. data/test/lazylead/cc_test.rb +1 -1
  66. data/test/lazylead/cli/app_test.rb +2 -2
  67. data/test/lazylead/confluence_test.rb +1 -1
  68. data/test/lazylead/exchange_test.rb +1 -1
  69. data/test/lazylead/model_test.rb +1 -1
  70. data/test/lazylead/opts_test.rb +1 -1
  71. data/test/lazylead/postman_test.rb +1 -1
  72. data/test/lazylead/salt_test.rb +1 -1
  73. data/test/lazylead/smoke_test.rb +1 -1
  74. data/test/lazylead/smtp_test.rb +1 -1
  75. data/test/lazylead/system/jira_test.rb +10 -1
  76. data/test/lazylead/task/accuracy/accuracy_test.rb +1 -1
  77. data/test/lazylead/task/accuracy/affected_build_test.rb +1 -1
  78. data/test/lazylead/task/accuracy/attachment_test.rb +1 -1
  79. data/test/lazylead/task/accuracy/environment_test.rb +1 -1
  80. data/test/lazylead/task/accuracy/logs_test.rb +1 -1
  81. data/test/lazylead/task/accuracy/onlyll_test.rb +1 -1
  82. data/test/lazylead/task/accuracy/records_test.rb +1 -1
  83. data/test/lazylead/task/accuracy/score_test.rb +40 -2
  84. data/test/lazylead/task/accuracy/screenshots_test.rb +126 -0
  85. data/test/lazylead/task/accuracy/servers_test.rb +1 -1
  86. data/test/lazylead/task/accuracy/stacktrace_test.rb +1 -1
  87. data/test/lazylead/task/accuracy/testcase_test.rb +1 -1
  88. data/test/lazylead/task/accuracy/wiki_test.rb +1 -1
  89. data/test/lazylead/task/alert/alertif_test.rb +54 -0
  90. data/test/lazylead/task/{assignee_alert_test.rb → alert/assignee_alert_test.rb} +10 -10
  91. data/test/lazylead/task/alert/changed_to_test.rb +42 -0
  92. data/test/lazylead/task/assignment_test.rb +1 -1
  93. data/test/lazylead/task/confluence_ref_test.rb +1 -1
  94. data/test/lazylead/task/created_recently_test.rb +53 -0
  95. data/test/lazylead/task/duedate_test.rb +2 -2
  96. data/test/lazylead/task/echo_test.rb +1 -1
  97. data/test/lazylead/task/fix_version_test.rb +1 -1
  98. data/test/lazylead/task/loading_test.rb +55 -0
  99. data/test/lazylead/task/micromanager_test.rb +54 -0
  100. data/test/lazylead/task/missing_comment_test.rb +1 -1
  101. data/test/lazylead/task/propagate_down_test.rb +1 -1
  102. data/test/lazylead/task/savepoint_test.rb +1 -1
  103. data/test/lazylead/task/svn/diff_test.rb +8 -4
  104. data/test/lazylead/task/svn/grep_test.rb +1 -1
  105. data/test/lazylead/task/svn/touch_test.rb +1 -1
  106. data/test/lazylead/version_test.rb +1 -1
  107. data/test/sqlite_test.rb +1 -1
  108. data/test/test.rb +13 -1
  109. data/upgrades/sqlite/001-install-main-lazylead-tables.sql +1 -1
  110. data/upgrades/sqlite/999.testdata.sql +1 -1
  111. metadata +64 -29
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -22,12 +22,17 @@
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
- # Guardfile for Lazylead
26
- guard :minitest, all_after_pass: false, all_on_start: false do
27
- # with Minitest::Unit
28
- watch(%r{^test/(.*)/?test_(.*)\.rb$})
29
- watch(%r{^test/test\.rb$}) { "test" }
30
- watch(%r{^lib/lazylead/(.*/)?([^/]+)\.rb$}) do |m|
31
- "test/#{m[1]}test_#{m[2]}.rb"
25
+ module Lazylead
26
+ #
27
+ # Allows to load all ruby files within particular dir.
28
+ #
29
+ class Requires
30
+ def initialize(dir)
31
+ @dir = dir
32
+ end
33
+
34
+ def load
35
+ Dir[File.join(@dir, "*.rb")].sort.each { |f| require f }
36
+ end
32
37
  end
33
38
  end
data/lib/lazylead/salt.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
data/lib/lazylead/smtp.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -26,10 +26,15 @@ 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.
32
33
  #
34
+ # Jira official documentation:
35
+ # - https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue
36
+ # - https://developer.atlassian.com/server/jira/platform/rest-apis/
37
+ #
33
38
  # Author:: Yurii Dubinka (yurii.dubinka@gmail.com)
34
39
  # Copyright:: Copyright (c) 2019-2020 Yurii Dubinka
35
40
  # License:: MIT
@@ -82,22 +87,7 @@ module Lazylead
82
87
  @opts[:auth_type] = :basic if @opts[:auth_type].nil?
83
88
  @opts["username"] = @salt.decrypt(@opts["username"]) if @salt.specified?
84
89
  @opts["password"] = @salt.decrypt(@opts["password"]) if @salt.specified?
85
- cp("site", :site)
86
- cp("username", :username)
87
- cp("password", :password)
88
- cp("context_path", :context_path)
89
- @client = JIRA::Client.new(@opts)
90
- end
91
-
92
- # Copy the required/mandatory parameter(s) for Jira client which can't
93
- # be specified/defined at database level.
94
- #
95
- # @todo #/DEV Jira.cp - find a way how to avoid this method.
96
- # Potentially, hash with indifferent access might be used.
97
- # http://jocellyn.cz/2014/05/03/hash-with-indifferent-access.html
98
- # key.kind_of?(Symbol) ? key.to_s : key
99
- def cp(act, exp)
100
- @opts[exp] = @opts[act] if @opts.key? act
90
+ @client = JIRA::Client.new(@opts.symbolize_keys)
101
91
  end
102
92
  end
103
93
 
@@ -139,11 +129,7 @@ module Lazylead
139
129
  end
140
130
 
141
131
  def to_s
142
- inspect
143
- end
144
-
145
- def inspect
146
- "#{@opts['site']} (#{@opts['username']})"
132
+ "#{name} (#{id})"
147
133
  end
148
134
  end
149
135
 
@@ -267,13 +253,20 @@ module Lazylead
267
253
 
268
254
  # Update the labels for a particular issue
269
255
  def labels!(lbl)
270
- return if lbl.nil? || lbl.empty?
271
- save!("fields" => { "labels" => lbl.uniq })
256
+ save!("fields" => { "labels" => lbl.uniq }) unless lbl.empty?
272
257
  end
273
258
 
274
259
  def save!(opts)
275
260
  @issue.save(opts)
276
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
277
270
  end
278
271
 
279
272
  # The jira issue comments
@@ -313,7 +306,19 @@ module Lazylead
313
306
 
314
307
  # Check that comment has expected text
315
308
  def include?(text)
316
- @comment.attrs["body"].include? text
309
+ to_s.include? text
310
+ end
311
+
312
+ def to_s
313
+ @comment.attrs["body"]
314
+ end
315
+
316
+ def lines
317
+ to_s.split("\n").reject(&:blank?)
318
+ end
319
+
320
+ def author
321
+ @comment.attrs["author"]["displayName"]
317
322
  end
318
323
  end
319
324
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -27,6 +27,7 @@ require_relative "../../opts"
27
27
  require_relative "../../email"
28
28
  require_relative "../../version"
29
29
  require_relative "../../postman"
30
+ require_relative "../../requires"
30
31
 
31
32
  module Lazylead
32
33
  module Task
@@ -43,36 +44,33 @@ module Lazylead
43
44
  end
44
45
 
45
46
  def run(sys, postman, opts)
46
- Dir[File.join(__dir__, "*.rb")].sort.each { |f| require f }
47
- rules = opts.slice("rules", ",")
48
- .map(&:constantize)
49
- .map(&:new)
50
- raised = sys.issues(opts["jql"], opts.jira_defaults)
51
- .map { |i| Score.new(i, opts, rules) }
52
- .each(&:evaluate)
53
- .each(&:post)
54
- postman.send opts.merge(tickets: raised) unless raised.empty?
47
+ Requires.new(__dir__).load
48
+ opts[:rules] = opts.construct("rules")
49
+ opts[:total] = opts[:rules].sum(&:score)
50
+ opts[:tickets] = sys.issues(opts["jql"], opts.jira_defaults.merge(expand: "changelog"))
51
+ .map { |i| Score.new(i, opts) }
52
+ .each(&:evaluate)
53
+ .each(&:post)
54
+ postman.send(opts) unless opts[:tickets].empty?
55
55
  end
56
56
  end
57
57
  end
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
- def initialize(issue, opts, rules)
63
+ def initialize(issue, opts)
64
64
  @issue = issue
65
- @link = opts["docs"]
66
65
  @opts = opts
67
- @rules = rules
68
66
  end
69
67
 
70
68
  # Estimate the ticket score and accuracy.
71
69
  # Accuracy is a percentage between current score and maximum possible value.
72
70
  def evaluate(digits = 2)
73
- @total = @rules.sum(&:score)
74
- @score = @rules.select { |r| r.passed(@issue) }.sum(&:score)
75
- @accuracy = (score / @total * 100).round(digits)
71
+ @score = @opts[:rules].select { |r| r.passed(@issue) }.sum(&:score)
72
+ @accuracy = (score / @opts[:total] * 100).round(digits)
73
+ self
76
74
  end
77
75
 
78
76
  # Post the comment with score and accuracy to the ticket.
@@ -85,13 +83,13 @@ module Lazylead
85
83
  # The jira comment in markdown format
86
84
  def comment
87
85
  comment = [
88
- "Hi [~#{@issue.reporter.id}],",
86
+ "Hi [~#{reporter}],",
89
87
  "",
90
88
  "The triage accuracy is '{color:#{color}}#{@score}{color}'" \
91
89
  " (~{color:#{color}}#{@accuracy}%{color}), here are the reasons why:",
92
90
  "|| Ticket requirement || Status || Field ||"
93
91
  ]
94
- @rules.each do |r|
92
+ @opts[:rules].each do |r|
95
93
  comment << "|#{r.desc}|#{r.passed(@issue) ? '(/)' : '(-)'}|#{r.field}|"
96
94
  end
97
95
  comment << docs_link
@@ -103,11 +101,11 @@ module Lazylead
103
101
 
104
102
  # Link to ticket formatting rules
105
103
  def docs_link
106
- if @link.nil? || @link.blank?
104
+ if @opts["docs"].nil? || @opts["docs"].blank?
107
105
  ""
108
106
  else
109
107
  "The requirements/examples of ticket formatting rules you may find " \
110
- "[here|#{@link}]."
108
+ "[here|#{@opts['docs']}]."
111
109
  end
112
110
  end
113
111
 
@@ -120,13 +118,11 @@ module Lazylead
120
118
  end
121
119
 
122
120
  def colors
123
- @colors ||= begin
124
- JSON.parse(@opts["colors"])
125
- .to_h
126
- .to_a
127
- .each { |e| e[0] = e[0].to_i }
128
- .sort_by { |e| e[0] }
129
- 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] }
130
126
  end
131
127
 
132
128
  # Calculate grade for accuracy
@@ -137,5 +133,17 @@ module Lazylead
137
133
  def grade(value)
138
134
  (value / 10).floor * 10
139
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
140
148
  end
141
149
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The MIT License
4
4
  #
5
- # Copyright (c) 2019-2020 Yurii Dubinka
5
+ # Copyright (c) 2019-2021 Yurii Dubinka
6
6
  #
7
7
  # Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  # of this software and associated documentation files (the "Software"),
@@ -0,0 +1,60 @@
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
+ references = issue.description
46
+ .to_enum(:scan, /!.+!/)
47
+ .map { Regexp.last_match }
48
+ .map(&:to_s)
49
+ return false if references.size < @minimum
50
+ references.all? { |ref| pictures(issue).any? { |file| ref.include? file } }
51
+ end
52
+
53
+ # Detect all pictures in ticket attachments and returns an array with file names.
54
+ def pictures(issue)
55
+ @pictures ||= issue.attachments
56
+ .select { |a| @ext.include? File.extname(a.filename).downcase }
57
+ .map(&:filename)
58
+ end
59
+ end
60
+ end