how_is 19.0.0 → 20.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|