Class: Inq::Sources::Github::Contributions

Inherits:
Object
  • Object
show all
Includes:
Inq::Sources::GithubHelpers
Defined in:
lib/inq/sources/github/contributions.rb

Overview

Fetch information about who has contributed to a repository during a given period.

Usage:

c = Inq::Contributions.new(start_date: '2017-07-01', user: 'duckinator', repo: 'inq')
c.commits          #=> All commits during July 2017.
c.contributors #=> All contributors during July 2017.
c.new_contributors #=> New contributors during July 2017.

Instance Method Summary collapse

Methods included from Inq::Sources::GithubHelpers

#average_age_for, #newest_for, #obj_to_array_of_hashes, #oldest_for, #sort_iops_by_created_at

Constructor Details

#initialize(config, start_date, end_date, cache) ⇒ Contributions

Returns an object that fetches contributor information about a particular repository for a month-long period starting on start_date.

Parameters:

  • config (Hash)

    A config object.

  • start_date (String)

    Date in the format YYYY-MM-DD. The first date to include commits from.

  • end_date (String)

    Date in the format YYYY-MM-DD. The last date to include commits from.

  • cache (Cacheable)

    Instance of Inq::Cacheable to cache API calls



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/inq/sources/github/contributions.rb', line 35

def initialize(config, start_date, end_date, cache)
  raise "Got String, need Hash. The Github::Contributions API changed." if config.is_a?(String)

  @config = config
  @cache = cache
  @github = Sources::Github.new(config)
  @repository = config["repository"]

  @user, @repo = @repository.split("/")
  @github = ::Github.new(auto_pagination: true) { |conf|
    conf.basic_auth = @github.basic_auth
  }

  # IMPL. DETAIL: The external API uses "end_date" so it's clearer,
  #               but internally we use "until_date" to match GitHub's API.
  @since_date = start_date
  @until_date = end_date

  @commit = {}
  @stats = nil
  @changed_files = nil
end

Instance Method Details

#additions_countObject



165
166
167
# File 'lib/inq/sources/github/contributions.rb', line 165

def additions_count
  changes["stats"]["additions"]
end

#changed_filesObject



153
154
155
156
157
158
159
# File 'lib/inq/sources/github/contributions.rb', line 153

def changed_files
  return @changed_files if @changed_files
  files = commits.flat_map do |commit|
    commit.files.map { |file| file["filename"] }
  end
  @changed_files = files.sort.uniq
end

#changesObject



161
162
163
# File 'lib/inq/sources/github/contributions.rb', line 161

def changes
  {"stats" => stats, "files" => changed_files}
end

#commit(sha) ⇒ Object



135
136
137
138
139
# File 'lib/inq/sources/github/contributions.rb', line 135

def commit(sha)
  @commit[sha] ||= @cache.cached("repos_commit_#{sha}") do
    @github.repos.commits.get(user: @user, repo: @repo, sha: sha)
  end
end

#commitsObject



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/inq/sources/github/contributions.rb', line 110

def commits
  return @commits if instance_variable_defined?(:@commits)

  args = {
    user: @user,
    repo: @repo,
    since: @since_date,
    until: @until_date,
  }

  Inq::Text.print "Fetching #{@repository} commit data."

  # The commits list endpoint doesn't include all stats.
  # So, to compensate, we make N requests here, where N is number
  # of commits returned, and then we die a bit inside.
  @commits = @cache.cached("repos_commits", args.to_json) do
    @github.repos.commits.list(**args).map { |c|
      Inq::Text.print "."
      commit(c.sha)
    }
  end
  Inq::Text.puts
  @commits
end

#compare_urlObject



173
174
175
# File 'lib/inq/sources/github/contributions.rb', line 173

def compare_url
  "https://github.com/#{@user}/#{@repo}/compare/#{default_branch}@%7B#{@since_date}%7D...#{default_branch}@%7B#{@until_date}%7D" # rubocop:disable Metrics/LineLength
end

#contributorsHash{String => Hash}

Returns Author information keyed by author’s email.

Returns:

  • (Hash{String => Hash})

    Author information keyed by author’s email.



104
105
106
107
108
# File 'lib/inq/sources/github/contributions.rb', line 104

def contributors
  commits.map { |api_response|
    [api_response.commit.author.email, api_response.commit.author.to_h]
  }.to_h
end

#default_branchObject



177
178
179
180
181
# File 'lib/inq/sources/github/contributions.rb', line 177

def default_branch
  @default_branch ||= @cache.cached("repos_default_branch") do
    @github.repos.get(user: @user, repo: @repo).default_branch
  end
end

#deletions_countObject



169
170
171
# File 'lib/inq/sources/github/contributions.rb', line 169

def deletions_count
  changes["stats"]["deletions"]
end

#new_contributorsHash{String => Hash}

Returns a list of contributors that have zero commits before the @since_date.

Returns:

  • (Hash{String => Hash})

    Contributors keyed by email



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/inq/sources/github/contributions.rb', line 61

def new_contributors
  @new_contributors ||= contributors.select { |email, _committer|
    args = {
      user: @user,
      repo: @repo,
      until: @since_date,
      author: email,
    }
    # True if +email+ never wrote a commit for +@repo+ before +@since_date+, false otherwise.
    commits = @cache.cached("repos_commits", args.to_json) do
      @github.repos.commits.list(**args)
    end
    commits.count.zero?
  }
end

#new_contributors_htmlObject



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/inq/sources/github/contributions.rb', line 77

def new_contributors_html
  names = new_contributors.values.map { |c| c["name"] }
  list_items = names.map { |n| "  <li>#{n}</li>" }.join("\n")

  if names.length.zero?
    num_new_contributors = "no"
  else
    num_new_contributors = names.length
  end

  if names.length == 1
    was_were = "was"
    contributor_s = ""
  else
    was_were = "were"
    contributor_s = "s"
  end

  Template.apply("new_contributors_partial.html", {
    was_were: was_were,
    contributor_s: contributor_s,
    number_of_new_contributors: num_new_contributors,
    list_items: list_items,
  }).strip
end

#statsObject



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/inq/sources/github/contributions.rb', line 141

def stats
  return @stats if @stats

  stats = {"total" => 0, "additions" => 0, "deletions" => 0}
  commits.map do |commit|
    stats.keys.each do |key|
      stats[key] += commit.stats[key]
    end
  end
  @stats = stats
end

#to_html(start_text: nil) ⇒ Object

rubocop:disable Metrics/AbcSize



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/inq/sources/github/contributions.rb', line 184

def to_html(start_text: nil)
  start_text ||= "From #{pretty_date(@since_date)} through #{pretty_date(@until_date)}"

  Inq::Template.apply("contributions_partial.html", {
    start_text: start_text,
    user: @user,
    repo: @repo,
    compare_url: compare_url,
    additions_count_str: (additions_count == 1) ? "was" : "were",
    authors: pluralize("author", contributors.length),
    new_commits: pluralize("new commit", commits.length),
    additions: pluralize("addition", additions_count),
    deletions: pluralize("deletion", deletions_count),
    changed_files: pluralize("file", changed_files.length),
  }).strip
end