newsman 1.2.2 → 1.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0166813d55623364efd38ff1f3bc83ff3ae76ee9d793b1b5ac52924477122135'
4
- data.tar.gz: 48d3c00284b0973851b95c2b144fe50972f801deeb826b400ab31e87102b7cfc
3
+ metadata.gz: 7d8f8d600c711d2570fdff045de97d9162f5173e733b164c98ec667ad01e1158
4
+ data.tar.gz: 427198b752758475747275768a3db990f563d90d0853653ba81b1b03a1664257
5
5
  SHA512:
6
- metadata.gz: 193cf28ea41b994cab278212cc255dcf0d1f7b78570e631f68e17003d489d505fd0e12a8b3244d576fa1e0826d992ead7af9c1febefd80074dc8260174294dae
7
- data.tar.gz: 4e3a9c5920d471c3c5a1dcabe16692a6154ea50d44adaf775d263ce04186e814068391555d3e90682b043dbb2b3c4fb0372da2f8fa7e91abf331a594bfefe4e7
6
+ metadata.gz: 4f1855fcef29439b336ef10eef275a927a5a4c47574c67275fd35e10bbe9a64b731081bcc80c0525b17d1c1b46c68759c14f05564de251f9e097b3708edebfd5
7
+ data.tar.gz: 3fde1f89ade004ccf2e3a77a86e94489590a933d3cf36e824a43d7ba314ddef53ff70235bee91e1a20edb3950804b1604caa72317aac099d95081b1941f1f7f1
@@ -45,7 +45,8 @@ class Assistant
45
45
  example = "repository-name:\n
46
46
  - To publish ABC package draft [#27]\n
47
47
  - To review first draft of the report [#56]\n
48
- - To implement optimization for the class X [#125]"
48
+ - To implement optimization for the class X [#125]\n
49
+ - Updated dependencies: rubocop, ruby [#197, #199, #204]"
49
50
  prompt = <<~PROMPT
50
51
  Please compile a summary of the plans for the next week using the GitHub Issues.
51
52
  Each issue should be summarized in a single sentence.
@@ -53,6 +54,8 @@ class Assistant
53
54
  Pay attention, that you didn't loose any issue.
54
55
  Ensure that each sentence includes the corresponding issue number as an integer value. If an issue doesn't mention an issue number, just print [#chore].
55
56
  If several issues have the same number, combine them into a single sentence.
57
+ Dependency version-bump issues (e.g. titles like "chore(deps): update dependency X to vY" or similar renovate/dependabot style bumps) must all be grouped into a single combined bullet listing the dependency names and all associated issue numbers, instead of one bullet per issue.
58
+ Other substantive issues should keep their own individual bullet as usual.
56
59
  Please strictly adhere to the example template provided: "#{example}".
57
60
  List of GitHub Issues in JSON format: ```json #{issues}```.
58
61
  PROMPT
@@ -65,13 +68,16 @@ class Assistant
65
68
  example = "repository-name:\n
66
69
  - Added 100 new files to the Dataset [#168]\n
67
70
  - Fixed the deployment of XYZ [#169]\n
68
- - Refined the requirements [#177]\n"
71
+ - Refined the requirements [#177]\n
72
+ - Updated dependencies: rubocop, ruby, nokogiri [#197, #199, #200, #202, #203, #204]\n"
69
73
  prompt = <<~PROMPT
70
74
  Please compile a summary of the work completed in the following Pull Requests (PRs).
71
75
  Each PR should be summarized in a single sentence, focusing on the PR title rather than the implementation details.
72
76
  Ensure no PR is omitted.
73
77
  Each sentence must include the corresponding issue number as an integer value. If a PR does not mention an issue number, use [#chore].
74
78
  If several PRs have the same number, combine them into a single sentence.
79
+ Dependency version-bump PRs (e.g. titles like "chore(deps): update dependency X to vY" or similar renovate/dependabot style bumps) must all be grouped into a single combined bullet listing the dependency names and all associated PR numbers, instead of one bullet per PR.
80
+ Other substantive PRs should keep their own individual bullet as usual.
75
81
  Combine the information from each PR into a concise and fluent sentence.
76
82
  Follow the provided example template strictly: "#{example}".
77
83
  List of Pull Requests in JSON format: ```json #{prs}```.
@@ -30,7 +30,7 @@ class Docxout
30
30
  # Content types part, required by every OOXML package.
31
31
  CONTENT_TYPES = <<~XML
32
32
  <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
33
- <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>
33
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/></Types>
34
34
  XML
35
35
 
36
36
  # Package-level relationships, pointing at the main document part.
@@ -39,6 +39,27 @@ class Docxout
39
39
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>
40
40
  XML
41
41
 
42
+ # Document-level relationships, pointing at the numbering part used by lists.
43
+ DOCUMENT_RELS = <<~XML
44
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
45
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/></Relationships>
46
+ XML
47
+
48
+ # Numbering definitions: numId 1 is a bulleted list, numId 2 is a decimal (numbered) list.
49
+ NUMBERING = <<~XML
50
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
51
+ <w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:abstractNum w:abstractNumId="0"><w:lvl w:ilvl="0"><w:numFmt w:val="bullet"/><w:lvlText w:val="&#8226;"/><w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr></w:lvl></w:abstractNum><w:abstractNum w:abstractNumId="1"><w:lvl w:ilvl="0"><w:numFmt w:val="decimal"/><w:lvlText w:val="%1."/><w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr></w:lvl></w:abstractNum><w:num w:numId="1"><w:abstractNumId w:val="0"/></w:num><w:num w:numId="2"><w:abstractNumId w:val="1"/></w:num></w:numbering>
52
+ XML
53
+
54
+ # Matches markdown headers, e.g. "## Title" (levels 1-6).
55
+ HEADING = /^\s{0,3}(\#{1,6})\s+(.+?)\s*$/
56
+ # Matches markdown bullet list items, e.g. "- item" or "* item".
57
+ BULLET = /^\s{0,3}[-*]\s+(.+?)\s*$/
58
+ # Matches markdown numbered list items, e.g. "1. item".
59
+ NUMBERED = /^\s{0,3}\d+\.\s+(.+?)\s*$/
60
+ # Matches markdown bold markers, e.g. "**bold**" or "__bold__".
61
+ BOLD = /(\*\*|__)(.+?)\1/
62
+
42
63
  FONT = 'Times New Roman'
43
64
  # Word expresses font size in half-points, so 12pt is 24.
44
65
  SIZE = '24'
@@ -50,11 +71,7 @@ class Docxout
50
71
  def print(report, reporter, model)
51
72
  puts "Create a docx file in a directory #{@root}"
52
73
  path = File.join(@root, filename(reporter, model))
53
- Zip::File.open(path, create: true) do |zip|
54
- zip.get_output_stream('[Content_Types].xml') { |f| f.write(CONTENT_TYPES) }
55
- zip.get_output_stream('_rels/.rels') { |f| f.write(RELS) }
56
- zip.get_output_stream('word/document.xml') { |f| f.write(document_xml(report)) }
57
- end
74
+ write_package(path, report)
58
75
  puts "Report was successfully printed to a #{path}"
59
76
  File.basename(path)
60
77
  end
@@ -67,16 +84,63 @@ class Docxout
67
84
 
68
85
  private
69
86
 
87
+ def write_package(path, report)
88
+ Zip::File.open(path, create: true) do |zip|
89
+ zip.get_output_stream('[Content_Types].xml') { |f| f.write(CONTENT_TYPES) }
90
+ zip.get_output_stream('_rels/.rels') { |f| f.write(RELS) }
91
+ zip.get_output_stream('word/_rels/document.xml.rels') { |f| f.write(DOCUMENT_RELS) }
92
+ zip.get_output_stream('word/numbering.xml') { |f| f.write(NUMBERING) }
93
+ zip.get_output_stream('word/document.xml') { |f| f.write(document_xml(report)) }
94
+ end
95
+ end
96
+
70
97
  def document_xml(report)
71
- body = paragraphs(report).map { |lines| paragraph_xml(lines) }.join
98
+ body = blocks(report).map { |block| block_xml(block) }.join
72
99
  <<~XML
73
100
  <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
74
101
  <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>#{body}<w:sectPr/></w:body></w:document>
75
102
  XML
76
103
  end
77
104
 
78
- def paragraphs(report)
79
- report.to_s.split(/\n{2,}/).map { |paragraph| paragraph.split("\n") }
105
+ # Splits the report into blocks: markdown headers and list items each become
106
+ # their own block, and consecutive plain-text lines are grouped together.
107
+ def blocks(report)
108
+ classified = report.to_s.split("\n", -1).map { |line| classify(line) }
109
+ classified.chunk_while { |prev, curr| prev[:type] == :text && curr[:type] == :text }
110
+ .reject { |group| group.first[:type] == :blank }
111
+ .map { |group| merge_text(group) }
112
+ end
113
+
114
+ def merge_text(group)
115
+ return group.first unless group.first[:type] == :text
116
+
117
+ { type: :text, lines: group.map { |block| block[:line] } }
118
+ end
119
+
120
+ def classify(line)
121
+ return { type: :blank } if line.strip.empty?
122
+ return { type: :heading, level: ::Regexp.last_match(1).length, text: ::Regexp.last_match(2) } if line =~ HEADING
123
+ return { type: :bullet, text: ::Regexp.last_match(1) } if line =~ BULLET
124
+ return { type: :numbered, text: ::Regexp.last_match(1) } if line =~ NUMBERED
125
+
126
+ { type: :text, line: line }
127
+ end
128
+
129
+ def block_xml(block)
130
+ case block[:type]
131
+ when :heading
132
+ "<w:p><w:pPr><w:pStyle w:val=\"Heading#{block[:level]}\"/></w:pPr>#{run_xml(block[:text])}</w:p>"
133
+ when :bullet
134
+ list_item_xml(block[:text], '1')
135
+ when :numbered
136
+ list_item_xml(block[:text], '2')
137
+ else
138
+ paragraph_xml(block[:lines])
139
+ end
140
+ end
141
+
142
+ def list_item_xml(text, num_id)
143
+ "<w:p><w:pPr><w:numPr><w:ilvl w:val=\"0\"/><w:numId w:val=\"#{num_id}\"/></w:numPr></w:pPr>#{run_xml(text)}</w:p>"
80
144
  end
81
145
 
82
146
  def paragraph_xml(lines)
@@ -89,6 +153,6 @@ class Docxout
89
153
  def run_xml(line)
90
154
  "<w:r><w:rPr><w:rFonts w:ascii=\"#{FONT}\" w:hAnsi=\"#{FONT}\" w:cs=\"#{FONT}\"/>" \
91
155
  "<w:sz w:val=\"#{SIZE}\"/><w:szCs w:val=\"#{SIZE}\"/></w:rPr>" \
92
- "<w:t xml:space=\"preserve\">#{CGI.escapeHTML(line)}</w:t></w:r>"
156
+ "<w:t xml:space=\"preserve\">#{CGI.escapeHTML(line.gsub(BOLD, '\2'))}</w:t></w:r>"
93
157
  end
94
158
  end
@@ -24,8 +24,8 @@
24
24
 
25
25
  # This class represents a useful abstraction over Github API.
26
26
  class Github
27
- def initialize(token)
28
- @client = Octokit::Client.new(github_token: token)
27
+ def initialize(token, client: nil)
28
+ @client = client || Octokit::Client.new(github_token: token)
29
29
  end
30
30
 
31
31
  def pull_requests(username, repositories)
@@ -48,6 +48,24 @@ class Github
48
48
  end.select(&:important?)
49
49
  end
50
50
 
51
+ def issues_reviewed(username, repositories)
52
+ since = date_one_week_ago(Date.today)
53
+ reviewed_query = "is:issue involves:#{username} updated:>=#{since} #{repositories}"
54
+ closed_query = "is:issue is:closed involves:#{username} closed:>=#{since} #{repositories}"
55
+ puts "Searching issues reviewed using the following query: '#{reviewed_query}'"
56
+ reviewed = @client.search_issues(reviewed_query).total_count
57
+ puts "Searching issues closed using the following query: '#{closed_query}'"
58
+ closed = @client.search_issues(closed_query).total_count
59
+ IssueActivity.new(reviewed, closed)
60
+ end
61
+
62
+ def pull_requests_reviewed(username, repositories)
63
+ since = date_one_week_ago(Date.today)
64
+ query = "is:pr reviewed-by:#{username} updated:>=#{since} #{repositories}"
65
+ puts "Searching pull requests reviewed using the following query: '#{query}'"
66
+ @client.search_issues(query).total_count
67
+ end
68
+
51
69
  def parse_pr(pull_request)
52
70
  title = pull_request.title.to_s
53
71
  repository = pull_request.repository_url.split('/').last
@@ -25,6 +25,21 @@ require 'json'
25
25
 
26
26
  IMPORTANT_ISSUE = 'soon'
27
27
 
28
+ # Represents how many issues a user reviewed (labeled, assigned, commented, etc.)
29
+ # and how many of those were closed, during a given period.
30
+ class IssueActivity
31
+ attr_reader :reviewed, :closed
32
+
33
+ def initialize(reviewed, closed)
34
+ @reviewed = reviewed
35
+ @closed = closed
36
+ end
37
+
38
+ def to_s
39
+ "Issues reviewed: #{@reviewed} (labeled/assigned), #{@closed} closed"
40
+ end
41
+ end
42
+
28
43
  # This class represents a GitHub Issue abstraction created by a user.
29
44
  class Issue
30
45
  attr_accessor :title, :body, :repo, :number
@@ -65,9 +65,11 @@ end
65
65
 
66
66
  # Report items inner class.
67
67
  class ReportItems
68
- def initialize(prs, issues)
68
+ def initialize(prs, issues, issues_reviewed: nil, prs_reviewed: nil)
69
69
  @prs = prs || []
70
70
  @issues = issues || []
71
+ @issues_reviewed = issues_reviewed
72
+ @prs_reviewed = prs_reviewed
71
73
  end
72
74
 
73
75
  # Returns true if there are no pull requests or issues, false otherwise
@@ -78,6 +80,15 @@ class ReportItems
78
80
  def to_s
79
81
  prs_list = @prs.map(&:detailed_title).map { |obj| " - #{obj}\n" }.join
80
82
  issues_list = @issues.map(&:detailed_title).map { |obj| " - #{obj}\n" }.join
81
- "Closed Pull Requests:\n#{prs_list}\nOpen Issues:\n#{issues_list}"
83
+ "Closed Pull Requests:\n#{prs_list}\nOpen Issues:\n#{issues_list}#{activity}"
84
+ end
85
+
86
+ private
87
+
88
+ def activity
89
+ return '' unless @issues_reviewed || @prs_reviewed
90
+
91
+ lines = [@issues_reviewed&.to_s, (@prs_reviewed ? "PRs reviewed: #{@prs_reviewed}" : nil)].compact
92
+ "\n#{lines.join("\n")}\n"
82
93
  end
83
94
  end
data/lib/newsman.rb CHANGED
@@ -99,6 +99,8 @@ def generate
99
99
  github = Github.new(github_token)
100
100
  prs = github.pull_requests(github_username, github_repositories)
101
101
  issues = github.issues(github_username, github_repositories)
102
+ issues_reviewed = github.issues_reviewed(github_username, github_repositories)
103
+ prs_reviewed = github.pull_requests_reviewed(github_username, github_repositories)
102
104
  puts "\nNow lets test some aggregation using OpenAI\n\n"
103
105
  assistant = Assistant.new(openai_token, model: options[:model])
104
106
  # Build previous results
@@ -118,7 +120,7 @@ def generate
118
120
  reporter,
119
121
  reporter_position,
120
122
  options[:title],
121
- additional: ReportItems.new(prs, issues)
123
+ additional: ReportItems.new(prs, issues, issues_reviewed: issues_reviewed, prs_reviewed: prs_reviewed)
122
124
  )
123
125
  full_answer = assistant.format(report.build(
124
126
  answer,
data/test/test_docxout.rb CHANGED
@@ -50,4 +50,62 @@ class TestDocxout < Minitest::Test
50
50
  assert_includes(document_xml, 'Issue description')
51
51
  end
52
52
  end
53
+
54
+ def test_renders_markdown_headers_as_word_headings
55
+ Dir.mktmpdir do |temp_dir|
56
+ output = Docxout.new(temp_dir)
57
+ report = "# Title\n\n## Subtitle\n\n### Section"
58
+ path = File.join(temp_dir, output.print(report, 'volodya-lombrozo', 'gpt-3.5-turbo'))
59
+ document_xml = Zip::File.open(path) { |zip| zip.read('word/document.xml') }
60
+ %w[Heading1 Heading2 Heading3].each { |style| assert_includes(document_xml, %(<w:pStyle w:val="#{style}"/>)) }
61
+ assert_includes(document_xml, '>Title<')
62
+ refute_includes(document_xml, '# Title')
63
+ end
64
+ end
65
+
66
+ def test_renders_markdown_bullet_lists_as_word_lists
67
+ Dir.mktmpdir do |temp_dir|
68
+ output = Docxout.new(temp_dir)
69
+ report = "List is here:\n\n- one\n- two\n* three"
70
+ path = File.join(temp_dir, output.print(report, 'volodya-lombrozo', 'gpt-3.5-turbo'))
71
+ document_xml = Zip::File.open(path) { |zip| zip.read('word/document.xml') }
72
+ assert_includes(document_xml, '<w:numId w:val="1"/>')
73
+ assert_includes(document_xml, '>one<')
74
+ refute_includes(document_xml, '>- one<')
75
+ end
76
+ end
77
+
78
+ def test_renders_markdown_numbered_lists_as_word_lists
79
+ Dir.mktmpdir do |temp_dir|
80
+ output = Docxout.new(temp_dir)
81
+ report = "1. first\n2. second"
82
+ path = File.join(temp_dir, output.print(report, 'volodya-lombrozo', 'gpt-3.5-turbo'))
83
+ document_xml = Zip::File.open(path) { |zip| zip.read('word/document.xml') }
84
+ assert_includes(document_xml, '<w:numId w:val="2"/>')
85
+ assert_includes(document_xml, '>first<')
86
+ end
87
+ end
88
+
89
+ def test_strips_markdown_bold_markers
90
+ Dir.mktmpdir do |temp_dir|
91
+ output = Docxout.new(temp_dir)
92
+ report = 'Deploys are **blocked** until review, __really__ blocked.'
93
+ path = File.join(temp_dir, output.print(report, 'volodya-lombrozo', 'gpt-3.5-turbo'))
94
+ document_xml = Zip::File.open(path) { |zip| zip.read('word/document.xml') }
95
+ assert_includes(document_xml, 'Deploys are blocked until review, really blocked.')
96
+ refute_includes(document_xml, '**')
97
+ refute_includes(document_xml, '__')
98
+ end
99
+ end
100
+
101
+ def test_docx_package_includes_a_valid_numbering_part
102
+ Dir.mktmpdir do |temp_dir|
103
+ output = Docxout.new(temp_dir)
104
+ path = File.join(temp_dir, output.print("- one\n- two", 'volodya-lombrozo', 'gpt-3.5-turbo'))
105
+ Zip::File.open(path) do |zip|
106
+ assert(zip.find_entry('word/numbering.xml'))
107
+ assert(zip.find_entry('word/_rels/document.xml.rels'))
108
+ end
109
+ end
110
+ end
53
111
  end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright (c) 2024 Volodya Lombrozo
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the 'Software'), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ require 'minitest/autorun'
25
+ require_relative '../lib/newsman/issues'
26
+ require_relative '../lib/newsman/github'
27
+
28
+ # A fake Octokit client that records every query it receives and answers
29
+ # with a canned total_count, so we can assert on the query strings we build
30
+ # without touching the network.
31
+ class FakeOctokit
32
+ Result = Struct.new(:total_count, :items)
33
+
34
+ def initialize(counts)
35
+ @counts = counts
36
+ @queries = []
37
+ end
38
+
39
+ attr_reader :queries
40
+
41
+ def search_issues(query)
42
+ @queries << query
43
+ Result.new(@counts.fetch(query, 0), [])
44
+ end
45
+ end
46
+
47
+ class TestGithub < Minitest::Test
48
+ def test_issues_reviewed_builds_involves_and_closed_queries
49
+ client = FakeOctokit.new({})
50
+ github = Github.new('token', client: client)
51
+ github.issues_reviewed('volodya-lombrozo', 'repo:objectionary/eo')
52
+ assert(client.queries.any? { |q| q.include?('is:issue') && q.include?('involves:volodya-lombrozo') })
53
+ assert(client.queries.any? do |q|
54
+ q.include?('is:issue') && q.include?('is:closed') && q.include?('involves:volodya-lombrozo')
55
+ end)
56
+ end
57
+
58
+ def test_issues_reviewed_returns_reviewed_and_closed_counts
59
+ since = date_one_week_ago(Date.today)
60
+ reviewed_query = "is:issue involves:volodya-lombrozo updated:>=#{since} repo:objectionary/eo"
61
+ closed_query = "is:issue is:closed involves:volodya-lombrozo closed:>=#{since} repo:objectionary/eo"
62
+ client = FakeOctokit.new(reviewed_query => 7, closed_query => 3)
63
+ github = Github.new('token', client: client)
64
+ activity = github.issues_reviewed('volodya-lombrozo', 'repo:objectionary/eo')
65
+ assert_equal 7, activity.reviewed
66
+ assert_equal 3, activity.closed
67
+ assert_equal 'Issues reviewed: 7 (labeled/assigned), 3 closed', activity.to_s
68
+ end
69
+
70
+ def test_pull_requests_reviewed_builds_reviewed_by_query
71
+ since = date_one_week_ago(Date.today)
72
+ query = "is:pr reviewed-by:volodya-lombrozo updated:>=#{since} repo:objectionary/eo"
73
+ client = FakeOctokit.new(query => 5)
74
+ github = Github.new('token', client: client)
75
+ assert_equal 5, github.pull_requests_reviewed('volodya-lombrozo', 'repo:objectionary/eo')
76
+ end
77
+ end
data/test/test_report.rb CHANGED
@@ -73,4 +73,17 @@ class TestReport < Minitest::Test
73
73
  actual = ReportItems.new(prs, issues).to_s
74
74
  assert_equal expected, actual
75
75
  end
76
+
77
+ def test_report_items_with_activity_metrics
78
+ expected = <<~EXPECTED
79
+ Closed Pull Requests:
80
+
81
+ Open Issues:
82
+
83
+ Issues reviewed: 7 (labeled/assigned), 3 closed
84
+ PRs reviewed: 5
85
+ EXPECTED
86
+ actual = ReportItems.new([], [], issues_reviewed: IssueActivity.new(7, 3), prs_reviewed: 5).to_s
87
+ assert_equal expected, actual
88
+ end
76
89
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: newsman
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Volodya Lombrozo
@@ -201,6 +201,7 @@ files:
201
201
  - lib/newsman/txt_output.rb
202
202
  - test/test_assistant.rb
203
203
  - test/test_docxout.rb
204
+ - test/test_github.rb
204
205
  - test/test_htmlout.rb
205
206
  - test/test_issue.rb
206
207
  - test/test_pdd_issue.rb