lazylead 0.8.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.docker/docker-compose.yml +1 -1
  3. data/.rubocop.yml +1 -0
  4. data/Rakefile +1 -1
  5. data/bin/lazylead +3 -0
  6. data/lazylead.gemspec +11 -11
  7. data/lib/lazylead/log.rb +11 -0
  8. data/lib/lazylead/os.rb +55 -0
  9. data/lib/lazylead/system/jira.rb +10 -7
  10. data/lib/lazylead/task/accuracy/accuracy.rb +14 -2
  11. data/lib/lazylead/task/accuracy/screenshots.rb +20 -7
  12. data/lib/lazylead/task/accuracy/testcase.rb +4 -4
  13. data/lib/lazylead/task/assignment.rb +9 -13
  14. data/lib/lazylead/task/fix_version.rb +5 -8
  15. data/lib/lazylead/task/loading.rb +6 -1
  16. data/lib/lazylead/task/micromanager.rb +111 -0
  17. data/lib/lazylead/task/svn/diff.rb +11 -11
  18. data/lib/lazylead/task/svn/grep.rb +8 -71
  19. data/lib/lazylead/task/svn/svn.rb +107 -0
  20. data/lib/lazylead/task/svn/touch.rb +5 -7
  21. data/lib/lazylead/version.rb +1 -1
  22. data/lib/messages/illegal_assignee_change.erb +1 -2
  23. data/lib/messages/illegal_duedate_change.erb +122 -0
  24. data/lib/messages/loading.erb +9 -8
  25. data/lib/messages/svn_diff.erb +29 -29
  26. data/lib/messages/svn_diff_attachment.erb +5 -7
  27. data/readme.md +9 -5
  28. data/test/lazylead/system/jira_test.rb +8 -0
  29. data/test/lazylead/task/accuracy/accuracy_test.rb +1 -1
  30. data/test/lazylead/task/accuracy/onlyll_test.rb +1 -1
  31. data/test/lazylead/task/accuracy/score_test.rb +11 -0
  32. data/test/lazylead/task/accuracy/screenshots_test.rb +61 -7
  33. data/test/lazylead/task/accuracy/testcase_test.rb +18 -0
  34. data/test/lazylead/task/alert/alertif_test.rb +2 -1
  35. data/test/lazylead/task/assignment_test.rb +2 -1
  36. data/test/lazylead/task/created_recently_test.rb +2 -1
  37. data/test/lazylead/task/duedate_test.rb +2 -2
  38. data/test/lazylead/task/fix_version_test.rb +2 -1
  39. data/test/lazylead/task/loading_test.rb +9 -4
  40. data/test/lazylead/task/micromanager_test.rb +61 -0
  41. data/test/lazylead/task/missing_comment_test.rb +1 -1
  42. data/test/lazylead/task/svn/diff_test.rb +1 -1
  43. data/test/lazylead/task/svn/grep_test.rb +2 -1
  44. data/test/test.rb +4 -3
  45. metadata +27 -21
@@ -27,8 +27,10 @@ require "tempfile"
27
27
  require "nokogiri"
28
28
  require "backtrace"
29
29
  require "active_support/core_ext/hash/conversions"
30
+ require_relative "../../os"
30
31
  require_relative "../../salt"
31
32
  require_relative "../../opts"
33
+ require_relative "svn"
32
34
 
33
35
  module Lazylead
34
36
  module Task
@@ -43,14 +45,13 @@ module Lazylead
43
45
  end
44
46
 
45
47
  def run(_, postman, opts)
46
- cmd = [
47
- "svn log --diff --no-auth-cache",
48
- "--username #{opts.decrypt('svn_user', 'svn_salt')}",
49
- "--password #{opts.decrypt('svn_password', 'svn_salt')}",
50
- "-r#{opts['since_rev']}:HEAD #{opts['svn_url']}"
51
- ]
52
- stdout = `#{cmd.join(" ")}`
53
- send_email postman, opts.merge(stdout: stdout) unless stdout.blank?
48
+ stdout = OS.new.run "svn log --diff --no-auth-cache",
49
+ "--username #{opts.decrypt('svn_user', 'svn_salt')}",
50
+ "--password #{opts.decrypt('svn_password', 'svn_salt')}",
51
+ "-r#{opts['since_rev']}:HEAD #{opts['svn_url']}"
52
+ return if stdout.blank?
53
+ commits = Lazylead::Svn::Commits.new(stdout)
54
+ send_email postman, opts.merge(commits: commits) unless commits.empty?
54
55
  end
55
56
 
56
57
  # Send email with svn log as an attachment.
@@ -60,8 +61,7 @@ module Lazylead
60
61
  Dir.mktmpdir do |dir|
61
62
  name = "svn-log-#{Date.today.strftime('%d-%b-%Y')}.html"
62
63
  begin
63
- to_f(File.join(dir, name), opts)
64
- postman.send opts.merge(attachments: [File.join(dir, name)])
64
+ postman.send opts.merge(attachments: [to_f(File.join(dir, name), opts)])
65
65
  ensure
66
66
  FileUtils.rm_rf("#{dir}/*")
67
67
  end
@@ -91,7 +91,7 @@ module Lazylead
91
91
  end
92
92
  f.write body
93
93
  f.close
94
- f
94
+ f.path
95
95
  end
96
96
  end
97
97
  end
@@ -25,8 +25,10 @@
25
25
  require "tmpdir"
26
26
  require "nokogiri"
27
27
  require "active_support/core_ext/hash/conversions"
28
+ require_relative "../../os"
28
29
  require_relative "../../salt"
29
30
  require_relative "../../opts"
31
+ require_relative "svn"
30
32
 
31
33
  module Lazylead
32
34
  module Task
@@ -47,14 +49,12 @@ module Lazylead
47
49
 
48
50
  # Return all svn commits for particular date range in repo
49
51
  def svn_log(opts)
50
- cmd = [
51
- "svn log --diff --no-auth-cache",
52
- "--username #{opts.decrypt('svn_user', 'svn_salt')}",
53
- "--password #{opts.decrypt('svn_password', 'svn_salt')}",
54
- "-r {#{from(opts)}}:{#{now(opts)}} #{opts['svn_url']}"
55
- ]
56
- stdout = `#{cmd.join(" ")}`
57
- stdout.split("-" * 72).reject(&:blank?).reverse.map { |e| Entry.new(e) }
52
+ stdout = OS.new.run "svn log --diff --no-auth-cache",
53
+ "--username #{opts.decrypt('svn_user', 'svn_salt')}",
54
+ "--password #{opts.decrypt('svn_password', 'svn_salt')}",
55
+ "-r {#{from(opts)}}:{#{now(opts)}} #{opts['svn_url']}"
56
+ return [] if stdout.blank?
57
+ Lazylead::Svn::Commits.new(stdout)
58
58
  end
59
59
 
60
60
  # The start date & time for search range
@@ -73,67 +73,4 @@ module Lazylead
73
73
  end
74
74
  end
75
75
  end
76
-
77
- # Single SVN commit details
78
- class Entry
79
- def initialize(commit)
80
- @commit = commit
81
- end
82
-
83
- def to_s
84
- "#{rev} #{msg}"
85
- end
86
-
87
- def rev
88
- header.first[1..]
89
- end
90
-
91
- def author
92
- header[1]
93
- end
94
-
95
- def time
96
- header[2]
97
- end
98
-
99
- def msg
100
- lines[1]
101
- end
102
-
103
- # The modified lines contains expected text
104
- def includes?(text)
105
- text = [text] unless text.respond_to? :each
106
- lines[4..].select { |l| l.start_with? "+" }
107
- .any? { |l| text.any? { |t| l.include? t } }
108
- end
109
-
110
- def lines
111
- @lines ||= @commit.split("\n").reject(&:blank?)
112
- end
113
-
114
- def header
115
- @header ||= lines.first.split(" | ").reject(&:blank?)
116
- end
117
-
118
- # Detect SVN diff lines with particular text
119
- def diff(text)
120
- @diff ||= begin
121
- files = affected(text).uniq
122
- @commit.split("Index: ")
123
- .select { |i| files.any? { |f| i.start_with? f } }
124
- .map { |i| i.split "\n" }
125
- .flatten
126
- end
127
- end
128
-
129
- # Detect affected files with particular text
130
- def affected(text)
131
- occurrences = lines.each_index.select do |i|
132
- lines[i].start_with?("+") && text.any? { |t| lines[i].include? t }
133
- end
134
- occurrences.map do |occ|
135
- lines[2..occ].reverse.find { |l| l.start_with? "Index: " }[7..]
136
- end
137
- end
138
- end
139
76
  end
@@ -0,0 +1,107 @@
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 "forwardable"
26
+
27
+ module Lazylead
28
+ module Svn
29
+ #
30
+ # SVN commit built from command-line stdout (OS#run).
31
+ #
32
+ class Commit
33
+ def initialize(raw)
34
+ @raw = raw
35
+ end
36
+
37
+ def to_s
38
+ "#{rev} #{msg}"
39
+ end
40
+
41
+ def rev
42
+ header.first[1..]
43
+ end
44
+
45
+ def author
46
+ header[1]
47
+ end
48
+
49
+ def time
50
+ header[2]
51
+ end
52
+
53
+ def msg
54
+ lines[1]
55
+ end
56
+
57
+ def lines
58
+ @lines ||= @raw.split("\n").reject(&:blank?)
59
+ end
60
+
61
+ def header
62
+ @header ||= lines.first.split(" | ").reject(&:blank?)
63
+ end
64
+
65
+ # The modified lines contains expected text
66
+ def includes?(text)
67
+ text = [text] unless text.respond_to? :each
68
+ lines[4..].select { |l| l.start_with? "+" }.any? { |l| text.any? { |t| l.include? t } }
69
+ end
70
+
71
+ # Detect SVN diff lines with particular text
72
+ def diff(text)
73
+ @diff ||= begin
74
+ files = affected(text).uniq
75
+ @raw.split("Index: ")
76
+ .select { |i| files.any? { |f| i.start_with? f } }
77
+ .map { |i| i.split "\n" }
78
+ .flatten
79
+ end
80
+ end
81
+
82
+ # Detect affected files with particular text
83
+ def affected(text)
84
+ occurrences = lines.each_index.select do |i|
85
+ lines[i].start_with?("+") && text.any? { |t| lines[i].include? t }
86
+ end
87
+ occurrences.map do |occ|
88
+ lines[2..occ].reverse.find { |l| l.start_with? "Index: " }[7..]
89
+ end
90
+ end
91
+ end
92
+
93
+ #
94
+ # SVN commits built from command-line stdout (OS#run).
95
+ #
96
+ class Commits
97
+ extend Forwardable
98
+ def_delegators :@all, :each, :map, :select, :empty?
99
+
100
+ # @todo #/DEV Find a way how to avoid @all initialization directly in constructor.
101
+ # There should be a way how to make lazy initialization of @all with 'forwardable'.
102
+ def initialize(stdout)
103
+ @all = stdout.split("-" * 72).reject(&:blank?).reverse.map { |e| Commit.new(e) }
104
+ end
105
+ end
106
+ end
107
+ end
@@ -25,6 +25,7 @@
25
25
  require "tmpdir"
26
26
  require "nokogiri"
27
27
  require "active_support/core_ext/hash/conversions"
28
+ require_relative "../../os"
28
29
  require_relative "../../salt"
29
30
  require_relative "../../opts"
30
31
 
@@ -64,13 +65,10 @@ module Lazylead
64
65
  DateTime.now
65
66
  end
66
67
  start = (now.to_time - opts["period"].to_i).to_datetime
67
- cmd = [
68
- "svn log --no-auth-cache",
69
- "--username #{opts.decrypt('svn_user', 'svn_salt')}",
70
- "--password #{opts.decrypt('svn_password', 'svn_salt')}",
71
- "--xml -v -r {#{start}}:{#{now}} #{opts['svn_url']}"
72
- ]
73
- raw = `#{cmd.join(" ")}`
68
+ raw = OS.new.run "svn log --no-auth-cache",
69
+ "--username #{opts.decrypt('svn_user', 'svn_salt')}",
70
+ "--password #{opts.decrypt('svn_password', 'svn_salt')}",
71
+ "--xml -v -r {#{start}}:{#{now}} #{opts['svn_url']}"
74
72
  Nokogiri.XML(raw, nil, "UTF-8")
75
73
  end
76
74
 
@@ -23,5 +23,5 @@
23
23
  # OR OTHER DEALINGS IN THE SOFTWARE.
24
24
 
25
25
  module Lazylead
26
- VERSION = "0.8.2"
26
+ VERSION = "0.9.3"
27
27
  end
@@ -102,8 +102,7 @@
102
102
  <tr>
103
103
  <td><a href='<%= a.issue.url %>'><%= a.issue.key %></a></td>
104
104
  <td><%= a.issue.priority %></td>
105
- <td><%= DateTime.parse(a.last["created"])
106
- .strftime('%d-%b-%Y %I:%M:%S %p') %></td>
105
+ <td><%= DateTime.parse(a.last["created"]).strftime('%d-%b') %></td>
107
106
  <td><span style='color: red'><%= a.last["author"]["displayName"] %></span>
108
107
  (<%= a.last["author"]["name"] %>)
109
108
  </td>
@@ -0,0 +1,122 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <style>
5
+ /* CSS styles taken from https://github.com/yegor256/tacit */
6
+ th {
7
+ font-weight: 600
8
+ }
9
+
10
+ table tr {
11
+ border-bottom-width: 2.16px
12
+ }
13
+
14
+ table tr th {
15
+ border-bottom-width: 2.16px
16
+ }
17
+
18
+ table tr td, table tr th {
19
+ overflow: hidden;
20
+ padding: 5.4px 3.6px
21
+ }
22
+
23
+ a {
24
+ color: #275a90;
25
+ text-decoration: none
26
+ }
27
+
28
+ a:hover {
29
+ text-decoration: underline
30
+ }
31
+
32
+ pre, code, kbd, samp, var, output {
33
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
34
+ font-size: 13px
35
+ }
36
+
37
+ pre code {
38
+ background: none;
39
+ border: 0;
40
+ line-height: 29.7px;
41
+ padding: 0
42
+ }
43
+
44
+ code, kbd {
45
+ background: #daf1e0;
46
+ border-radius: 3.6px;
47
+ color: #2a6f3b;
48
+ display: inline-block;
49
+ line-height: 18px;
50
+ padding: 3.6px 6.3px 2.7px
51
+ }
52
+
53
+ * {
54
+ border: 0;
55
+ border-collapse: separate;
56
+ border-spacing: 0;
57
+ box-sizing: border-box;
58
+ margin: 0;
59
+ max-width: 100%;
60
+ padding: 0;
61
+ vertical-align: baseline;
62
+ font-family: system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif;
63
+ font-size: 13px;
64
+ font-stretch: normal;
65
+ font-style: normal;
66
+ font-weight: 400;
67
+ line-height: 29.7px
68
+ }
69
+
70
+ html, body {
71
+ width: 100%
72
+ }
73
+
74
+ html {
75
+ height: 100%
76
+ }
77
+
78
+ body {
79
+ background: #fff;
80
+ color: #1a1919;
81
+ padding: 36px
82
+ }
83
+ </style>
84
+ <title>Not authorized "Assignee" change</title>
85
+ </head>
86
+ <body>
87
+ <p>Hi,</p>
88
+
89
+ <p>The <span style='font-weight:bold'>'Duedate'</span> for the following
90
+ ticket(s) changed by not authorized person(s):</p>
91
+ <table summary="ticket(s) where duedate changed">
92
+ <tr>
93
+ <th id="key">Key</th>
94
+ <th id="priority">Priority</th>
95
+ <th id="when">When</th>
96
+ <th id="who">Who</th>
97
+ <th id="to">To</th>
98
+ <th id="assignee">Assignee</th>
99
+ <th id="reporter">Reporter</th>
100
+ <th id="summary">Summary</th>
101
+ </tr>
102
+ <% dues.each do |d| %>
103
+ <tr>
104
+ <td><a href='<%= d.issue.url %>'><%= d.issue.key %></a></td>
105
+ <td><%= d.issue.priority %></td>
106
+ <td><%= d.when.to_date %></td>
107
+ <td><span style='color: red'><%= d.last.name %></span> (<%= d.last.id %>)</td>
108
+ <td><span style='color: red'><%= d.issue.duedate %></span></td>
109
+ <td><%= d.issue.assignee.name %></td>
110
+ <td><%= d.issue.reporter.name %></td>
111
+ <td><%= d.issue.summary %></td>
112
+ </tr>
113
+ <% end %>
114
+ </table>
115
+
116
+ <p>Authorized person(s) are: <code><%= allowed %></code>.</p>
117
+
118
+ <p>Posted by
119
+ <a href="https://github.com/dgroup/lazylead">lazylead v<%= version %></a>.
120
+ </p>
121
+ </body>
122
+ </html>
@@ -69,12 +69,11 @@
69
69
  <body>
70
70
  <table summary="tickets">
71
71
  <tr>
72
- <th id="uid">ID</th>
73
72
  <th id="name">User</th>
74
73
  <th id="total">
75
- <a href="https://jira.spring.io/issues/?jql=<%= CGI.escape(jql) %>">Assigned From</a>
74
+ <a href="<%= search_link %><%= CGI.escape(jql) %>">Assigned From</a>
76
75
  </th>
77
- <th id="duedate">Next Due date</th>
76
+ <th id="duedate">Next due date</th>
78
77
  </tr>
79
78
  <% assignments.each do |teammate, assignment| %>
80
79
  <% if assignment.free? %>
@@ -83,17 +82,19 @@
83
82
  <tr>
84
83
  <% end %>
85
84
  <td>
86
- <div class="auto"><a href="<%= user_link %>"><%= teammate %></a></div>
87
- </td>
88
- <td>
89
- <div class="auto"><%= assignment.name %></div>
85
+ <div class="auto"><a href="<%= user_link %><%= teammate %>"><%= assignment.name %></a></div>
90
86
  </td>
91
87
  <td>
92
88
  <div class="auto">
93
89
  <% if assignment.free? %>
94
90
  <span style="color: red">0</span>
95
91
  <% else %>
96
- <a href="https://jira.spring.io/issues/?jql=<%= CGI.escape("#{jql} and assignee=#{teammate}") %>"><%= assignment.size %></a>
92
+ <% assignment.sprints(defined?(sprint) ? sprint : "customfield_10480").each do |s| %>
93
+ <a href="<%= search_link %><%= CGI.escape("#{jql} and assignee=#{teammate}") %>">
94
+ <%= s.first.blank? ? "No sprint" : s.first %>: <%= s.last.size %>
95
+ </a>
96
+ <br/>
97
+ <% end %>
97
98
  <% end %>
98
99
  </div>
99
100
  </td>