lib/how_is/analyzer.rb in how_is-18.0.4 vs lib/how_is/analyzer.rb in how_is-18.0.5
- old
+ new
@@ -1,31 +1,32 @@
# frozen_string_literal: true
-require 'contracts'
-require 'ostruct'
-require 'date'
-require 'json'
+require "contracts"
+require "ostruct"
+require "date"
+require "json"
class HowIs
##
# Represents a completed analysis of the repository being analyzed.
class Analysis < OpenStruct
end
+ # Creates Analysis objects with input data formatted in useful ways.
class Analyzer
include Contracts::Core
##
- # Raised when attempting to export to an unsupported format.
+ # Raised when attempting to import to an unsupported format.
class UnsupportedImportFormat < StandardError
def initialize(format)
super("Unsupported import format: #{format}")
end
end
##
- # Generates and returns an analysis.i
+ # Generates and returns an analysis.
#
# @param data [Fetcher::Results] The results gathered by Fetcher.
# @param analysis_class (You don't need this.) A class to use instead of
# HowIs::Analysis.
Contract Fetcher::Results, C::KeywordArgs[analysis_class: C::Optional[Class]] => Analysis
@@ -41,11 +42,11 @@
number_of_issues: issues.length,
number_of_pulls: pulls.length,
issues_with_label: with_label_links(num_with_label(issues), data.repository),
- issues_with_no_label: {'link' => nil, 'total' => num_with_no_label(issues)},
+ issues_with_no_label: {"link" => nil, "total" => num_with_no_label(issues)},
average_issue_age: average_age_for(issues),
average_pull_age: average_age_for(pulls),
oldest_issue: issue_or_pull_to_hash(oldest_for(issues)),
@@ -62,19 +63,19 @@
# Generates an analysis from a hash of report data.
#
# @param data [Hash] The hash to generate an Analysis from.
def self.from_hash(data)
hash = data.map { |k, v|
- v = DateTime.parse(v) if k.end_with?('_date')
+ v = DateTime.parse(v) if k.end_with?("_date")
[k, v]
}.to_h
hash.keys.each do |key|
- next unless hash[key].is_a?(Hash) && hash[key]['date']
+ next unless hash[key].is_a?(Hash) && hash[key]["date"]
- hash[key]['date'] = DateTime.parse(hash[key]['date'])
+ hash[key]["date"] = DateTime.parse(hash[key]["date"])
end
Analysis.new(hash)
end
@@ -88,64 +89,43 @@
# "label2" => 5
# }
hash = Hash.new(0)
issues_or_pulls.each do |iop|
- next unless iop['labels']
+ next unless iop["labels"]
- iop['labels'].each do |label|
- hash[label['name']] += 1
+ iop["labels"].each do |label|
+ hash[label["name"]] += 1
end
end
hash
end
# Returns the number of issues with no label.
def num_with_no_label(issues)
- issues.select { |x| x['labels'].empty? }.length
+ issues.select { |x| x["labels"].empty? }.length
end
# Given an Array of dates, average the timestamps and return the date that
# represents.
def average_date_for(issues_or_pulls)
- timestamps = issues_or_pulls.map { |iop| Date.parse(iop['created_at']).strftime('%s').to_i }
+ timestamps = issues_or_pulls.map { |iop| Date.parse(iop["created_at"]).strftime("%s").to_i }
average_timestamp = timestamps.reduce(:+) / issues_or_pulls.length
- DateTime.strptime(average_timestamp.to_s, '%s')
+ DateTime.strptime(average_timestamp.to_s, "%s")
end
# Given an Array of issues or pulls, return the average age of them.
# Returns nil if no issues or pulls are provided.
def average_age_for(issues_or_pulls)
return nil if issues_or_pulls.empty?
- ages = issues_or_pulls.map { |iop| time_ago_in_seconds(iop['created_at']) }
- raw_average = ages.reduce(:+) / ages.length
+ ages = issues_or_pulls.map { |iop| time_ago_in_seconds(iop["created_at"]) }
+ average_age_in_seconds = ages.reduce(:+) / ages.length
- seconds_in_a_year = 31_556_926
- seconds_in_a_month = 2_629_743
- seconds_in_a_week = 604_800
- seconds_in_a_day = 86_400
-
- years = raw_average / seconds_in_a_year
- years_remainder = raw_average % seconds_in_a_year
-
- months = years_remainder / seconds_in_a_month
- months_remainder = years_remainder % seconds_in_a_month
-
- weeks = months_remainder / seconds_in_a_week
- weeks_remainder = months_remainder % seconds_in_a_week
-
- days = weeks_remainder / seconds_in_a_day
-
- values = [
- [years, "year"],
- [months, "month"],
- [weeks, "week"],
- [days, "day"],
- ].reject { |(v, _)| v == 0 }.map { |(v, k)|
- k += 's' if v != 1
+ values = period_pairs_for(average_age_in_seconds).reject { |(v, _)| v.zero? }.map { |(v, k)|
+ k += "s" if v != 1
[v, k]
}
most_significant = values[0, 2].map { |x| x.join(" ") }
@@ -158,11 +138,11 @@
"approximately #{value}"
end
def sort_iops_by_created_at(issues_or_pulls)
- issues_or_pulls.sort_by { |x| DateTime.parse(x['created_at']) }
+ issues_or_pulls.sort_by { |x| DateTime.parse(x["created_at"]) }
end
# Given an Array of issues or pulls, return the oldest.
# Returns nil if no issues or pulls are provided.
def oldest_for(issues_or_pulls)
@@ -179,22 +159,22 @@
sort_iops_by_created_at(issues_or_pulls).last
end
# Given an issue or PR, returns the date it was created.
def date_for(issue_or_pull)
- DateTime.parse(issue_or_pull['created_at'])
+ DateTime.parse(issue_or_pull["created_at"])
end
private
# Takes an Array of labels, and returns amodified list that includes links
# to each label.
def with_label_links(labels, repository)
labels.map { |label, num_issues|
label_link = "https://github.com/#{repository}/issues?q=" + CGI.escape("is:open is:issue label:\"#{label}\"")
- [label, {'link' => label_link, 'total' => num_issues}]
+ [label, {"link" => label_link, "total" => num_issues}]
}.to_h
end
# Returns how many seconds ago a date (as a String) was.
def time_ago_in_seconds(x)
@@ -204,13 +184,39 @@
def issue_or_pull_to_hash(iop)
return nil if iop.nil?
ret = {}
- ret['html_url'] = iop['html_url']
- ret['number'] = iop['number']
- ret['date'] = date_for(iop)
+ ret["html_url"] = iop["html_url"]
+ ret["number"] = iop["number"]
+ ret["date"] = date_for(iop)
ret
+ end
+
+ SECONDS_IN_A_YEAR = 31_556_926
+ SECONDS_IN_A_MONTH = 2_629_743
+ SECONDS_IN_A_WEEK = 604_800
+ SECONDS_IN_A_DAY = 86_400
+
+ # Calculates a list of pairs of value and period label.
+ #
+ # @param age_in_seconds [Float]
+ #
+ # @return [Array<Array>] The input age_in_seconds expressed as different
+ # units, as pairs of value and unit name.
+ def period_pairs_for(age_in_seconds)
+ years_remainder = age_in_seconds % SECONDS_IN_A_YEAR
+
+ months_remainder = years_remainder % SECONDS_IN_A_MONTH
+
+ weeks_remainder = months_remainder % SECONDS_IN_A_WEEK
+
+ [
+ [age_in_seconds / SECONDS_IN_A_YEAR, "year"],
+ [years_remainder / SECONDS_IN_A_MONTH, "month"],
+ [months_remainder / SECONDS_IN_A_WEEK, "week"],
+ [weeks_remainder / SECONDS_IN_A_DAY, "day"],
+ ]
end
end
end