how_is 19.0.0 → 20.0.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 +5 -5
- data/.codeclimate.yml +1 -0
- data/.rubocop.yml +16 -0
- data/CHANGELOG.md +30 -3
- data/Gemfile +0 -6
- data/README.md +5 -2
- data/exe/how_is +7 -2
- data/fixtures/vcr_cassettes/how-is-example-empty-repository.yml +446 -727
- data/fixtures/vcr_cassettes/how-is-example-repository.yml +508 -727
- data/fixtures/vcr_cassettes/how-is-from-config-frontmatter.yml +43522 -1218
- data/fixtures/vcr_cassettes/how-is-how-is-travis-api-repos-builds.yml +287 -0
- data/fixtures/vcr_cassettes/how-is-with-config-file.yml +44307 -23286
- data/fixtures/vcr_cassettes/how_is_contributions_additions_count.yml +307 -0
- data/fixtures/vcr_cassettes/how_is_contributions_all_contributors.yml +81 -0
- data/fixtures/vcr_cassettes/how_is_contributions_changed_files.yml +307 -0
- data/fixtures/vcr_cassettes/how_is_contributions_changes.yml +307 -0
- data/fixtures/vcr_cassettes/how_is_contributions_commits.yml +307 -0
- data/fixtures/vcr_cassettes/how_is_contributions_compare_url.yml +74 -0
- data/fixtures/vcr_cassettes/how_is_contributions_deletions_count.yml +307 -0
- data/fixtures/vcr_cassettes/how_is_contributions_new_contributors.yml +234 -0
- data/fixtures/vcr_cassettes/how_is_contributions_summary.yml +378 -0
- data/fixtures/vcr_cassettes/how_is_contributions_summary_2.yml +378 -0
- data/fixtures/vcr_cassettes/how_is_fetcher_call.yml +572 -0
- data/how_is.gemspec +3 -2
- data/lib/how_is/cli.rb +4 -6
- data/lib/how_is/frontmatter.rb +46 -0
- data/lib/how_is/report.rb +59 -63
- data/lib/how_is/sources/github/contributions.rb +164 -0
- data/lib/how_is/sources/github/issues.rb +142 -0
- data/lib/how_is/sources/github/pulls.rb +20 -0
- data/lib/how_is/sources/github.rb +11 -0
- data/lib/how_is/sources/github_helpers.rb +191 -0
- data/lib/how_is/sources/travis.rb +34 -0
- data/lib/how_is/sources.rb +9 -0
- data/lib/how_is/templates/issues_or_pulls_partial.html_template +7 -0
- data/lib/how_is/templates/report.html_template +27 -0
- data/lib/how_is/templates/report_partial.html_template +12 -0
- data/lib/how_is/version.rb +2 -2
- data/lib/how_is.rb +59 -158
- metadata +42 -16
- data/lib/how_is/analyzer.rb +0 -222
- data/lib/how_is/builds.rb +0 -36
- data/lib/how_is/contributions.rb +0 -156
- data/lib/how_is/fetcher.rb +0 -77
- data/lib/how_is/pulse.rb +0 -47
- data/lib/how_is/report/base_report.rb +0 -148
- data/lib/how_is/report/html.rb +0 -120
- data/lib/how_is/report/json.rb +0 -34
data/how_is.gemspec
CHANGED
@@ -19,10 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.add_runtime_dependency "github_api", "~> 0.
|
22
|
+
spec.add_runtime_dependency "github_api", "~> 0.18.1"
|
23
23
|
spec.add_runtime_dependency "contracts", "~> 0.16.0"
|
24
24
|
|
25
|
-
spec.add_runtime_dependency "
|
25
|
+
spec.add_runtime_dependency "okay", "~> 4.0.0"
|
26
26
|
|
27
27
|
spec.add_development_dependency "bundler", "~> 1.11"
|
28
28
|
spec.add_development_dependency "rake", "~> 11.2"
|
@@ -32,4 +32,5 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_development_dependency "webmock"
|
33
33
|
spec.add_development_dependency "rubocop", "~> 0.49.1"
|
34
34
|
spec.add_development_dependency "github_changelog_generator"
|
35
|
+
spec.add_development_dependency "pry"
|
35
36
|
end
|
data/lib/how_is/cli.rb
CHANGED
@@ -3,9 +3,7 @@
|
|
3
3
|
require "how_is"
|
4
4
|
require "optparse"
|
5
5
|
|
6
|
-
|
7
|
-
DEFAULT_REPORT_FILE = "report.#{HowIs::DEFAULT_FORMAT}".freeze
|
8
|
-
|
6
|
+
module HowIs::CLI
|
9
7
|
# Parent class of all exceptions raised in HowIs::CLI.
|
10
8
|
class OptionsError < StandardError
|
11
9
|
end
|
@@ -67,7 +65,7 @@ class HowIs::CLI
|
|
67
65
|
end
|
68
66
|
|
69
67
|
opts.on("--report REPORT_FILE",
|
70
|
-
"Output file for the report (valid extensions: #{HowIs.supported_formats.join(', ')}; default: #{DEFAULT_REPORT_FILE})") do |filename|
|
68
|
+
"Output file for the report (valid extensions: #{HowIs.supported_formats.join(', ')}; default: #{HowIs::DEFAULT_REPORT_FILE})") do |filename|
|
71
69
|
options[:report] = filename
|
72
70
|
end
|
73
71
|
|
@@ -115,8 +113,8 @@ class HowIs::CLI
|
|
115
113
|
# If we get here, we're generating a report from the command line,
|
116
114
|
# without using --from or --config.
|
117
115
|
|
118
|
-
# If --report isn't specified, default to DEFAULT_REPORT_FILE.
|
119
|
-
options[:report] ||= DEFAULT_REPORT_FILE
|
116
|
+
# If --report isn't specified, default to HowIs::DEFAULT_REPORT_FILE.
|
117
|
+
options[:report] ||= HowIs::DEFAULT_REPORT_FILE
|
120
118
|
|
121
119
|
# If we can't export to the specified file, raise an exception.
|
122
120
|
unless HowIs.can_export_to?(options[:report])
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "how_is/version"
|
4
|
+
|
5
|
+
module HowIs
|
6
|
+
module Frontmatter
|
7
|
+
# Generates YAML frontmatter, as is used in Jekyll and other blog engines.
|
8
|
+
#
|
9
|
+
# E.g.,
|
10
|
+
# generate_frontmatter({'foo' => "bar %{baz}"}, {'baz' => "asdf"})
|
11
|
+
# => "---\nfoo: bar asdf\n"
|
12
|
+
def self.generate(frontmatter, report_data)
|
13
|
+
return "" if frontmatter.nil?
|
14
|
+
|
15
|
+
frontmatter = convert_keys(frontmatter, :to_s)
|
16
|
+
report_data = convert_keys(report_data, :to_sym)
|
17
|
+
|
18
|
+
frontmatter = frontmatter.map { |k, v|
|
19
|
+
# Sometimes report_data has unused keys, which generates a warning, but
|
20
|
+
# we're okay with it.
|
21
|
+
v = silence_warnings { v % report_data }
|
22
|
+
|
23
|
+
[k, v]
|
24
|
+
}.to_h
|
25
|
+
|
26
|
+
YAML.dump(frontmatter) + "---\n\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
# @example
|
30
|
+
# convert_keys({'foo' => 'bar'}, :to_sym)
|
31
|
+
# # => {:foo => 'bar'}
|
32
|
+
def self.convert_keys(data, method_name)
|
33
|
+
data.map { |k, v| [k.send(method_name), v] }.to_h
|
34
|
+
end
|
35
|
+
private_class_method :convert_keys
|
36
|
+
|
37
|
+
def self.silence_warnings(&_block)
|
38
|
+
old_verbose = $VERBOSE
|
39
|
+
$VERBOSE = nil # Disable warnings entirely.
|
40
|
+
yield
|
41
|
+
ensure
|
42
|
+
$VERBOSE = old_verbose
|
43
|
+
end
|
44
|
+
private_class_method :silence_warnings
|
45
|
+
end
|
46
|
+
end
|
data/lib/how_is/report.rb
CHANGED
@@ -1,82 +1,78 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
class UnsupportedExportFormat < StandardError
|
9
|
-
def initialize(format)
|
10
|
-
super("Unsupported export format: #{format}")
|
11
|
-
end
|
12
|
-
end
|
3
|
+
require "how_is/frontmatter"
|
4
|
+
require "how_is/sources/github/contributions"
|
5
|
+
require "how_is/sources/github/issues"
|
6
|
+
require "how_is/sources/github/pulls"
|
7
|
+
require "how_is/sources/travis"
|
13
8
|
|
14
|
-
|
15
|
-
# or to save reports in files, or otherwise interact with the files.
|
9
|
+
module HowIs
|
16
10
|
class Report
|
17
|
-
|
18
|
-
|
11
|
+
def initialize(repository, end_date)
|
12
|
+
@repository = repository
|
13
|
+
@end_date = end_date
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
report = get_report_class(format).new(analysis)
|
25
|
-
|
26
|
-
report.export_file(file)
|
15
|
+
@gh_contributions = HowIs::Sources::Github::Contributions.new(repository, end_date)
|
16
|
+
@gh_issues = HowIs::Sources::Github::Issues.new(repository, end_date)
|
17
|
+
@gh_pulls = HowIs::Sources::Github::Pulls.new(repository, end_date)
|
18
|
+
@travis = HowIs::Sources::Travis.new(repository, end_date)
|
27
19
|
end
|
28
20
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
21
|
+
def to_h(frontmatter_data = nil)
|
22
|
+
@report_hash ||= {
|
23
|
+
title: "How is #{@repository}?",
|
24
|
+
repository: @repository,
|
33
25
|
|
34
|
-
|
35
|
-
|
26
|
+
contributions_summary: @gh_contributions.to_html,
|
27
|
+
issues_summary: @gh_issues.to_html,
|
28
|
+
pulls_summary: @gh_pulls.to_html,
|
29
|
+
issues_per_label: @gh_issues.issues_per_label_html,
|
36
30
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
end
|
31
|
+
issues: @gh_issues.to_a,
|
32
|
+
pulls: @gh_issues.to_a,
|
33
|
+
|
34
|
+
number_of_issues: @gh_issues.to_a.length,
|
35
|
+
number_of_pulls: @gh_pulls.to_a.length,
|
36
|
+
|
37
|
+
average_issue_age: @gh_issues.average_age,
|
38
|
+
average_pull_age: @gh_pulls.average_age,
|
47
39
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
40
|
+
oldest_issue_link: @gh_issues.oldest[:link],
|
41
|
+
oldest_issue_date: @gh_issues.oldest[:creation_date],
|
42
|
+
|
43
|
+
newest_issue_link: @gh_issues.newest[:link],
|
44
|
+
newest_issue_date: @gh_issues.newest[:creation_date],
|
45
|
+
|
46
|
+
oldest_pull_link: @gh_pulls.oldest[:link],
|
47
|
+
oldest_pull_date: @gh_pulls.oldest[:creation_date],
|
48
|
+
|
49
|
+
travis_builds: @travis.builds.to_h,
|
50
|
+
}
|
51
|
+
|
52
|
+
frontmatter =
|
53
|
+
if frontmatter_data
|
54
|
+
HowIs::Frontmatter.generate(frontmatter_data, @report_hash)
|
55
|
+
else
|
56
|
+
""
|
57
|
+
end
|
58
|
+
|
59
|
+
@report_hash.merge(frontmatter: frontmatter)
|
56
60
|
end
|
57
61
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# @param report [Report]
|
63
|
-
#
|
64
|
-
# @return [String] The rendered report
|
65
|
-
def self.to_format_based_on(file, report)
|
66
|
-
report_format = infer_format(file)
|
67
|
-
|
68
|
-
report.public_send("to_#{report_format}")
|
62
|
+
def to_html_partial(frontmatter = nil)
|
63
|
+
template_data = to_h(frontmatter)
|
64
|
+
|
65
|
+
Kernel.format(HowIs.template("report_partial.html_template"), template_data)
|
69
66
|
end
|
70
67
|
|
71
|
-
|
72
|
-
|
73
|
-
def self.get_report_class(format)
|
74
|
-
class_name = "#{format.capitalize}Report"
|
68
|
+
def to_html(frontmatter = nil)
|
69
|
+
template_data = to_h(frontmatter).merge({report: to_html_partial})
|
75
70
|
|
76
|
-
|
71
|
+
Kernel.format(HowIs.template("report.html_template"), template_data)
|
72
|
+
end
|
77
73
|
|
78
|
-
|
74
|
+
def to_json(frontmatter = nil)
|
75
|
+
frontmatter.to_s + JSON.pretty_generate(to_h)
|
79
76
|
end
|
80
|
-
private_class_method :get_report_class
|
81
77
|
end
|
82
78
|
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "how_is/version"
|
4
|
+
require "how_is/sources/github"
|
5
|
+
require "how_is/sources/github_helpers"
|
6
|
+
require "date"
|
7
|
+
|
8
|
+
module HowIs::Sources
|
9
|
+
class Github
|
10
|
+
# Fetch information about who has contributed to a repository during a given
|
11
|
+
# period.
|
12
|
+
#
|
13
|
+
# Usage:
|
14
|
+
#
|
15
|
+
# c = HowIs::Contributions.new(start_date: '2017-07-01', user: 'how-is', repo: 'how_is')
|
16
|
+
# c.commits #=> All commits during July 2017.
|
17
|
+
# c.contributors #=> All contributors during July 2017.
|
18
|
+
# c.new_contributors #=> New contributors during July 2017.
|
19
|
+
class Contributions
|
20
|
+
include HowIs::Sources::GithubHelpers
|
21
|
+
|
22
|
+
# Returns an object that fetches contributor information about a particular
|
23
|
+
# repository for a month-long period starting on +start_date+.
|
24
|
+
#
|
25
|
+
# @param repository [String] GitHub repository in the form of "user/repo".
|
26
|
+
# @param end_date [String] Date in the format YYYY-MM-DD. The last date
|
27
|
+
# to include commits from.
|
28
|
+
def initialize(repository, end_date)
|
29
|
+
@user, @repo = repository.split("/")
|
30
|
+
@github = HowIs.github
|
31
|
+
|
32
|
+
# IMPL. DETAIL: The external API uses "end_date" so it's clearer,
|
33
|
+
# but internally we use "until_date" to match GitHub's API.
|
34
|
+
|
35
|
+
# NOTE: Use DateTime because it defaults to UTC and that's less gross
|
36
|
+
# than trying to get Date to use UTC.
|
37
|
+
#
|
38
|
+
# Not using UTC for this results in #compare_url giving different
|
39
|
+
# results for different time zones, which makes it harder to test.
|
40
|
+
#
|
41
|
+
# (I'm also guessing/hoping that GitHub's URLs use UTC.)
|
42
|
+
@until_date = DateTime.strptime(end_date, "%Y-%m-%d")
|
43
|
+
|
44
|
+
d = @until_date.day
|
45
|
+
m = @until_date.month
|
46
|
+
y = @until_date.year
|
47
|
+
@since_date = DateTime.new(y, m - 1, d)
|
48
|
+
|
49
|
+
@commit = {}
|
50
|
+
@stats = nil
|
51
|
+
@changed_files = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a list of contributors that have zero commits before the @since_date.
|
55
|
+
#
|
56
|
+
# @return [Hash{String => Hash}] Contributors keyed by email
|
57
|
+
def new_contributors
|
58
|
+
# author: GitHub login, name or email by which to filter by commit author.
|
59
|
+
@new_contributors ||= contributors.select do |email, _committer|
|
60
|
+
# Returns true if +email+ never wrote a commit for +@repo+ before +@since_date+.
|
61
|
+
@github.repos.commits.list(user: @user,
|
62
|
+
repo: @repo,
|
63
|
+
until: @since_date,
|
64
|
+
author: email).count.zero?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Hash{String => Hash}] Author information keyed by author's email.
|
69
|
+
def contributors
|
70
|
+
commits.map { |api_response|
|
71
|
+
[api_response.commit.author.email, api_response.commit.author.to_h]
|
72
|
+
}.to_h
|
73
|
+
end
|
74
|
+
|
75
|
+
def commits
|
76
|
+
@commits ||= begin
|
77
|
+
@github.repos.commits.list(user: @user,
|
78
|
+
repo: @repo,
|
79
|
+
since: @since_date).map { |c|
|
80
|
+
# The commits list endpoint doesn't include all commit data, e.g. stats.
|
81
|
+
# So, we make N requests here, where N == number of commits returned,
|
82
|
+
# and then we die a bit inside.
|
83
|
+
commit(c.sha)
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def commit(sha)
|
89
|
+
@commit[sha] ||= @github.repos.commits.get(user: @user, repo: @repo, sha: sha)
|
90
|
+
end
|
91
|
+
|
92
|
+
def changes
|
93
|
+
if @stats.nil? || @changed_files.nil?
|
94
|
+
@stats = {
|
95
|
+
"total" => 0,
|
96
|
+
"additions" => 0,
|
97
|
+
"deletions" => 0,
|
98
|
+
}
|
99
|
+
|
100
|
+
@changed_files = []
|
101
|
+
|
102
|
+
commits.map do |commit|
|
103
|
+
@stats.keys.each do |key|
|
104
|
+
@stats[key] += commit.stats[key]
|
105
|
+
end
|
106
|
+
|
107
|
+
@changed_files += commit.files.map { |file| file["filename"] }
|
108
|
+
end
|
109
|
+
|
110
|
+
@changed_files = @changed_files.sort.uniq
|
111
|
+
end
|
112
|
+
|
113
|
+
{"stats" => @stats, "files" => @changed_files}
|
114
|
+
end
|
115
|
+
|
116
|
+
def changed_files
|
117
|
+
changes["files"]
|
118
|
+
end
|
119
|
+
|
120
|
+
def additions_count
|
121
|
+
changes["stats"]["additions"]
|
122
|
+
end
|
123
|
+
|
124
|
+
def deletions_count
|
125
|
+
changes["stats"]["deletions"]
|
126
|
+
end
|
127
|
+
|
128
|
+
def compare_url
|
129
|
+
since_timestamp = @since_date.to_time.to_i
|
130
|
+
until_timestamp = @until_date.to_time.to_i
|
131
|
+
"https://github.com/#{@user}/#{@repo}/compare/#{default_branch}@%7B#{since_timestamp}%7D...#{default_branch}@%7B#{until_timestamp}%7D" # rubocop:disable Metrics/LineLength
|
132
|
+
end
|
133
|
+
|
134
|
+
def default_branch
|
135
|
+
@default_branch ||= @github.repos.get(user: @user,
|
136
|
+
repo: @repo).default_branch
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_html(start_text: nil)
|
140
|
+
# TODO: Pulse has information about _all_ branches. Do we want that?
|
141
|
+
# If we do, we'd need to pass a branch name as the 'sha' parameter
|
142
|
+
# to /repos/:owner/:repo/commits.
|
143
|
+
# https://developer.github.com/v3/repos/commits/
|
144
|
+
|
145
|
+
start_text ||= "From #{pretty_date(@since_date)} through #{pretty_date(@until_date)}"
|
146
|
+
|
147
|
+
"#{start_text}, #{@user}/#{@repo} gained "\
|
148
|
+
"<a href=\"#{compare_url}\">#{pluralize('new commit', commits.length)}</a>, " \
|
149
|
+
"contributed by #{pluralize('author', contributors.length)}. There " \
|
150
|
+
"#{(additions_count == 1) ? 'was' : 'were'} " \
|
151
|
+
"#{pluralize('addition', additions_count)} and " \
|
152
|
+
"#{pluralize('deletion', deletions_count)} across " \
|
153
|
+
"#{pluralize('file', changed_files.length)}."
|
154
|
+
end
|
155
|
+
alias :summary :to_html # For backwards compatibility.
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def pretty_date(date)
|
160
|
+
date.strftime("%b %d, %Y")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "how_is/version"
|
4
|
+
require "how_is/sources/github"
|
5
|
+
require "how_is/sources/github_helpers"
|
6
|
+
require "date"
|
7
|
+
|
8
|
+
module HowIs::Sources
|
9
|
+
class Github
|
10
|
+
class Issues
|
11
|
+
include HowIs::Sources::GithubHelpers
|
12
|
+
|
13
|
+
def initialize(repository, end_date)
|
14
|
+
@repository = repository
|
15
|
+
@user, @repo = repository.split("/", 2)
|
16
|
+
end
|
17
|
+
|
18
|
+
def url
|
19
|
+
"https://github.com/#{@repository}/#{type}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def average_age
|
23
|
+
average_age_for(@data)
|
24
|
+
end
|
25
|
+
|
26
|
+
def oldest
|
27
|
+
fetch!
|
28
|
+
oldest_for(@data) || {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def newest
|
32
|
+
fetch!
|
33
|
+
newest_for(@data) || {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def summary
|
37
|
+
number_open = to_a.length
|
38
|
+
pretty_number =
|
39
|
+
pluralize(pretty_type, number_open, zero_is_no: true)
|
40
|
+
|
41
|
+
"There #{are_or_is(number_open)} <a href=\"#{url}\">#{pretty_number} open</a>."
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_html
|
45
|
+
fetch!
|
46
|
+
|
47
|
+
summary_ = "<p>#{summary}</p>"
|
48
|
+
|
49
|
+
return summary_ if to_a.empty?
|
50
|
+
|
51
|
+
template_data = {
|
52
|
+
summary: summary_,
|
53
|
+
average_age: average_age,
|
54
|
+
type: type,
|
55
|
+
pretty_type: pretty_type,
|
56
|
+
|
57
|
+
oldest_link: oldest[:link],
|
58
|
+
oldest_date: pretty_date(oldest[:created_at]),
|
59
|
+
|
60
|
+
newest_link: newest[:link],
|
61
|
+
newest_date: pretty_date(newest[:created_at]),
|
62
|
+
}
|
63
|
+
|
64
|
+
Kernel.format(HowIs.template("issues_or_pulls_partial.html_template"), template_data)
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO: Clean up Issues Per Label stuff, or replace it with different functionality.
|
68
|
+
|
69
|
+
def issues_per_label
|
70
|
+
ipl = with_label_links(num_with_label(@data), @repository)
|
71
|
+
number_with_no_label = num_with_no_label(@data)
|
72
|
+
|
73
|
+
if number_with_no_label > 0
|
74
|
+
ipl["(No label)"] = {
|
75
|
+
"link" => nil,
|
76
|
+
"total" => number_with_no_label,
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
ipl
|
81
|
+
end
|
82
|
+
|
83
|
+
HTML_GRAPH_ROW = <<-EOF
|
84
|
+
<tr>
|
85
|
+
<td style="width: %{label_width}">%{label_text}</td>
|
86
|
+
<td><span class="fill" style="width: %{percentage}%%">%{link_text}</span></td>
|
87
|
+
</tr>
|
88
|
+
EOF
|
89
|
+
|
90
|
+
def issues_per_label_html
|
91
|
+
data = issues_per_label
|
92
|
+
|
93
|
+
return "<p>There are no open issues to graph.</p>" if data.empty?
|
94
|
+
|
95
|
+
biggest = data.map { |_label, info| info["total"] }.max
|
96
|
+
get_percentage = ->(number_of_issues) { number_of_issues * 100 / biggest }
|
97
|
+
|
98
|
+
longest_label_length = data.map(&:first).map(&:length).max
|
99
|
+
label_width = "#{longest_label_length}ch"
|
100
|
+
|
101
|
+
parts = data.map { |label, info|
|
102
|
+
# TODO: Remove this hack to get around unlabeled issues not having a link.
|
103
|
+
label_text = label
|
104
|
+
unless info["link"].nil?
|
105
|
+
label_text = '<a href="' + info["link"] + '">' + label_text + '</a>'
|
106
|
+
end
|
107
|
+
|
108
|
+
Kernel.format(HTML_GRAPH_ROW, {
|
109
|
+
label_width: label_width,
|
110
|
+
label_text: label_text,
|
111
|
+
label_link: info["link"],
|
112
|
+
percentage: get_percentage.call(info["total"]),
|
113
|
+
link_text: info["total"].to_s,
|
114
|
+
})
|
115
|
+
}
|
116
|
+
|
117
|
+
"<table class=\"horizontal-bar-graph\">\n" +
|
118
|
+
parts.join("\n") +
|
119
|
+
"\n</table>"
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_a
|
123
|
+
fetch!
|
124
|
+
obj_to_array_of_hashes(@data)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def type
|
130
|
+
"issues"
|
131
|
+
end
|
132
|
+
|
133
|
+
def pretty_type
|
134
|
+
"issue"
|
135
|
+
end
|
136
|
+
|
137
|
+
def fetch!
|
138
|
+
@data ||= HowIs.github.send(type).list(user: @user, repo: @repo)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "how_is/version"
|
4
|
+
require "how_is/sources/github"
|
5
|
+
require "how_is/sources/github_helpers"
|
6
|
+
require "date"
|
7
|
+
|
8
|
+
module HowIs::Sources
|
9
|
+
class Github
|
10
|
+
class Pulls < Issues
|
11
|
+
def type
|
12
|
+
"pulls"
|
13
|
+
end
|
14
|
+
|
15
|
+
def pretty_type
|
16
|
+
"pull request"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|