open_source_stats 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c70f2d8e89dc9d029d1929cbfbd784e91bb95a85
4
+ data.tar.gz: 4b9b9d02d18899a671b999fbe19b1b2455ad42bd
5
+ SHA512:
6
+ metadata.gz: a958a8d47104f7d21e66b26dcd40b18368bdbd604a22f9fc7c81ed3e90bf5aab6227be44f139c6904bcae78a5acc2dcff91d6577071420c814527d74448dfceb
7
+ data.tar.gz: 3956f62f8a04c95cf4a046ec7577982261099ad2c5380c0feb865d139ebb75261ed51f665cab4a6858e971e457fd87b5ba7d34283bab2c14080e27654b4f5477
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .env
16
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in open_source_stats.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Ben Balter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,43 @@
1
+ # Open Source Stats
2
+
3
+ *A quick script to generate metrics about the contribution your organization makes to the open source community in a 24-hour period*.
4
+
5
+ ## What it looks at
6
+
7
+ * Public activity across all public repositories from members of a given team
8
+ * Public activity across all users on repositories owned by a given organization or organizations
9
+
10
+ ## How it works
11
+
12
+ By doing terrible, terrible things to GitHub's events API.
13
+
14
+ ## Setup
15
+
16
+ 1. `gem install open_source_stats`
17
+ 2. Create a `.env` file and add the following:
18
+ * `GITHUB_TOKEN` - A personal access token with `read:org` scope
19
+ * `GITHUB_TEAM_ID` - The numeric ID of the team to pull users from (e.g., 12345)
20
+ * `GITHUB_ORGS` - A comma separated list of the orgs to look at (e.g, `github,atom`)
21
+ 3. Run `bundle exec oss`
22
+
23
+ This will spit markdown formatted results into standard out.
24
+
25
+ ## Output
26
+
27
+ Output might look something like this...
28
+
29
+ | Metric | Count |
30
+ |----------------------|-------|
31
+ | Repositories created | 6 |
32
+ | Issue comments | 309 |
33
+ | Issues opened | 40 |
34
+ | Issues closed | 30 |
35
+ | Repos open sourced | 1 |
36
+ | Pull requests opened | 32 |
37
+ | Pull requests merged | 35 |
38
+ | Versions released | 3 |
39
+ | Pushes | 442 |
40
+ | Commits | 1548 |
41
+ | Total events | 945 |
42
+
43
+ Metrics will also be broken down by organization, by user, and by repository
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/oss ADDED
@@ -0,0 +1,38 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require_relative "../lib/open_source_stats"
4
+ require 'active_support/core_ext/string/inflections'
5
+ require 'terminal-table'
6
+
7
+ oss = OpenSourceStats.new
8
+
9
+ def pretty_print_hash(hash)
10
+ rows = []
11
+ hash.each { |key, value| rows << [key.to_s.humanize, value] }
12
+ table = Terminal::Table.new(:rows => rows, :style => { :border_i => "|" }, :headings => ["Metric", "Count"]).to_s
13
+ puts table.split("\n")[1...-1].join("\n") + "\n\n"
14
+ end
15
+
16
+ puts "# Open source actvity for the 24-hour period begining at #{OpenSourceStats.start_time.strftime("%l:%S %p %Z on %B %-d, %Y")}\n\n"
17
+
18
+ puts "## Overall\n\n"
19
+ pretty_print_hash oss.stats
20
+
21
+ puts "## By organization\n\n"
22
+ oss.orgs.each do |org|
23
+ puts "### #{org.name.humanize}\n\n"
24
+ pretty_print_hash oss.stats(org.events)
25
+ end
26
+
27
+ puts "## Top users\n\n"
28
+ users = oss.users.reject { |u| u.login == "hubot" }.sort_by { |u| u.events.count }.reverse[0...10]
29
+ users.each_with_index do |user, index|
30
+ puts "### #{index+1}. @#{user.login}\n\n"
31
+ pretty_print_hash oss.stats(user.events)
32
+ end
33
+
34
+ puts "## Most active repositories\n\n"
35
+ repos = oss.events.group_by { |e| e.repo }.map { |k,v| { k => v.count } }.sort_by { |r| r.values.first }.reverse[0...10]
36
+ repos.each_with_index do |repo, index|
37
+ puts "#{index + 1}. #{repo.keys.first} - #{repo.values.first} events"
38
+ end
@@ -0,0 +1,102 @@
1
+ require_relative "./open_source_stats/version.rb"
2
+ require_relative "./open_source_stats/user"
3
+ require_relative "./open_source_stats/organization"
4
+ require_relative "./open_source_stats/event"
5
+ require 'active_support'
6
+ require 'active_support/core_ext/numeric/time'
7
+ require 'octokit'
8
+ require 'dotenv'
9
+
10
+ Dotenv.load
11
+
12
+ class OpenSourceStats
13
+
14
+ # Authenticated Octokit client instance
15
+ def self.client
16
+ @client ||= Octokit::Client.new :access_token => ENV["GITHUB_TOKEN"]
17
+ end
18
+
19
+ # Returns a Ruby Time instance coresponding to the earliest possible event timestamp
20
+ def self.start_time
21
+ @start_time ||= Time.now - 24.hours
22
+ end
23
+
24
+ # Helper method to overide the timestamp
25
+ def self.start_time=(time)
26
+ @start_time = time
27
+ end
28
+
29
+ def team
30
+ @team ||= client.team ENV["GITHUB_TEAM_ID"]
31
+ end
32
+
33
+ # Returns an array of Users from the given team
34
+ def users
35
+ @users ||= begin
36
+ users = client.team_members ENV["GITHUB_TEAM_ID"], :per_page => 100
37
+ while client.last_response.rels[:next] && client.rate_limit.remaining > 0
38
+ users.concat client.get client.last_response.rels[:next].href
39
+ end
40
+ users.map { |u| User.new u[:login] }
41
+ end
42
+ end
43
+
44
+ # Returns an array of Events across all Users
45
+ def user_events
46
+ @user_events ||= users.map { |u| u.events }.flatten
47
+ end
48
+
49
+ # Returns an array of Organizations from the specified list of organizations
50
+ def orgs
51
+ @orgs ||= ENV["GITHUB_ORGS"].split(/, ?/).map { |o| Organization.new(o) }
52
+ end
53
+
54
+ # Returns an array of Events across all Organziations
55
+ def org_events
56
+ @org_events ||= orgs.map { |o| o.events }.flatten
57
+ end
58
+
59
+ # Returns an array of unique Events accross all Users and Organizations
60
+ def events
61
+ @events ||= user_events.concat(org_events).uniq
62
+ end
63
+
64
+ # Returns the calculated stats hash
65
+ def stats(events=events)
66
+ {
67
+ :repositories_created => event_count(events, :type =>"CreateEvent"),
68
+ :issue_comments => event_count(events, :type =>"IssueCommentEvent"),
69
+ :issues_opened => event_count(events, :type =>"IssuesEvent", :action => "opened"),
70
+ :issues_closed => event_count(events, :type =>"IssuesEvent", :action => "closed"),
71
+ :repos_open_sourced => event_count(events, :type =>"PublicEvent"),
72
+ :pull_requests_opened => event_count(events, :type =>"PullRequestEvent", :action => "opened"),
73
+ :pull_requests_merged => event_count(events, :type =>"PullRequestEvent", :action => "closed", :merged => true),
74
+ :versions_released => event_count(events, :type =>"ReleaseEvent", :action => "published"),
75
+ :pushes => event_count(events, :type =>"PushEvent"),
76
+ :commits => events.map { |e| e.commits }.flatten.inject{|sum,x| sum + x },
77
+ :total_events => event_count(events)
78
+ }
79
+ end
80
+
81
+ private
82
+
83
+ # Helper method to count events by set conditions
84
+ #
85
+ # Options:
86
+ # :type - the type of event to filter by
87
+ # :action - the payload action to filter by
88
+ # :merged - whether the pull request has been merged
89
+ #
90
+ # Returns an integer reflecting the resulting event count
91
+ def event_count(events, options={})
92
+ events = events.select { |e| e.type == options[:type] } if options[:type]
93
+ events = events.select { |e| e.payload[:action] == options[:action] } if options[:action]
94
+ events = events.select { |e| e.payload[:pull_request][:merged] == options[:merged] } if options[:merged]
95
+ events.count
96
+ end
97
+
98
+ # Helper method to reference the client stored on the class and DRY things up a bit
99
+ def client
100
+ OpenSourceStats.client
101
+ end
102
+ end
@@ -0,0 +1,67 @@
1
+ class OpenSourceStats
2
+ class Event
3
+
4
+ # Hash of event types to count as an event
5
+ #
6
+ # The top-level key should be the event type
7
+ # The top-level value should be a hash used to filter acceptable key/value pairs
8
+ #
9
+ # For the sub-hash, keys are keys to look for in the event,
10
+ # and the value is an array of acceptable values
11
+ #
12
+ # Events that don't match an event type and key/value pair with fail the in_scope? check
13
+ TYPES = {
14
+ "CreateEvent" => { :ref_type => ["repository"] },
15
+ "IssueCommentEvent" => {:action => ["created"] },
16
+ "IssuesEvent" => { :action => ["opened", "closed"] },
17
+ "PublicEvent" => {},
18
+ "PullRequestEvent" => { :action => ["opened", "closed"]},
19
+ "PullRequestReviewCommentEvent" => { :action => ["created"] },
20
+ "PushEvent" => {},
21
+ "ReleaseEvent" => { :action => "published" },
22
+ }
23
+
24
+ attr_reader :type, :repo, :actor, :time, :payload, :id
25
+
26
+ # Takes the raw event object from Octokit
27
+ def initialize(event)
28
+ @type = event[:type]
29
+ @repo = event[:repo][:name]
30
+ @actor = User.new(event[:actor][:login])
31
+ @time = event[:created_at]
32
+ @payload = event[:payload]
33
+ @id = event[:id]
34
+ end
35
+
36
+ # Is this event type within our timeframe AND an acceptable event type?
37
+ def in_scope?
38
+ return false unless time >= OpenSourceStats.start_time
39
+ return false unless TYPES.keys.include?(type)
40
+ TYPES[type].each do |key, values|
41
+ return false unless values.include? payload[key]
42
+ end
43
+ true
44
+ end
45
+
46
+ # Use event IDs to compare uniquness via Array#uniq
47
+ def ==(other_event)
48
+ id == other_event.id
49
+ end
50
+ alias_method :eql?, :==
51
+
52
+ # How many commmits does this event involve?
53
+ #
54
+ # For push events, this is the number of commits pushed
55
+ # For pull request events, this is the number of commits contained in the PR
56
+ def commits
57
+ case type
58
+ when "PushEvent"
59
+ payload[:commits].count
60
+ when "PullRequestEvent"
61
+ payload[:pull_request][:commits]
62
+ else
63
+ 0
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,9 @@
1
+ class OpenSourceStats
2
+ class Organization < OpenSourceStats::User
3
+ attr_accessor :name
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ class OpenSourceStats
2
+ class User
3
+
4
+ attr_accessor :login
5
+
6
+ def initialize(login)
7
+ @login = login
8
+ end
9
+
10
+ # Returns an array of in-scope Events from the user's public activity feed
11
+ def events
12
+ @events ||= begin
13
+ if self.class == OpenSourceStats::User
14
+ events = client.user_public_events login, :per_page => 100
15
+ else
16
+ events = client.organization_public_events name, :per_page => 100
17
+ end
18
+
19
+ events.concat client.get client.last_response.rels[:next].href while next_page?
20
+ events = events.map { |e| Event.new(e) }
21
+ events.select { |e| e.in_scope? }
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # Helper method to access the shared Octokit instance
28
+ def client
29
+ OpenSourceStats.client
30
+ end
31
+
32
+ # Helper method to improve readability.
33
+ # Asks is there another page to the results?
34
+ # Looks at both pagination and if we've gone past our allowed timeframe
35
+ def next_page?
36
+ client.last_response.rels[:next] &&
37
+ client.rate_limit.remaining > 0 &&
38
+ client.last_response.data.last[:created_at] >= OpenSourceStats.start_time
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ class OpenSourceStats
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'open_source_stats/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "open_source_stats"
8
+ spec.version = OpenSourceStats::VERSION
9
+ spec.authors = ["Ben Balter"]
10
+ spec.email = ["ben.balter@github.com"]
11
+ spec.summary = %q{A quick script to generate metrics about the contribution your organization makes to the open source community in a 24-hour period}
12
+ spec.homepage = "https://github.com/benbalter/open_source_stats"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "octokit"
21
+ spec.add_dependency "dotenv"
22
+ spec.add_dependency "activesupport"
23
+ spec.add_dependency "terminal-table"
24
+
25
+ spec.add_development_dependency "pry"
26
+ spec.add_development_dependency "bundler", "~> 1.6"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ end
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ bundle install
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ bundle exec gem build open_source_stats.gemspec
@@ -0,0 +1 @@
1
+ bundle exec pry -r "./lib/open_source_stats.rb"
@@ -0,0 +1,38 @@
1
+ #!/bin/sh
2
+ # Tag and push a release.
3
+
4
+ set -e
5
+
6
+ # Make sure we're in the project root.
7
+
8
+ cd $(dirname "$0")/..
9
+
10
+ # Build a new gem archive.
11
+
12
+ rm -rf open_source_stats-*.gem
13
+ gem build -q open_source_stats.gemspec
14
+
15
+ # Make sure we're on the master branch.
16
+
17
+ (git branch | grep -q '* master') || {
18
+ echo "Only release from the master branch."
19
+ exit 1
20
+ }
21
+
22
+ # Figure out what version we're releasing.
23
+
24
+ tag=v`ls open_source_stats-*.gem | sed 's/^open_source_stats-\(.*\)\.gem$/\1/'`
25
+
26
+ # Make sure we haven't released this version before.
27
+
28
+ git fetch -t origin
29
+
30
+ (git tag -l | grep -q "$tag") && {
31
+ echo "Whoops, there's already a '${tag}' tag."
32
+ exit 1
33
+ }
34
+
35
+ # Tag it and bag it.
36
+
37
+ gem push open_source_stats-*.gem && git tag "$tag" &&
38
+ git push origin master && git push origin "$tag"
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: open_source_stats
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ben Balter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: octokit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: terminal-table
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.6'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.6'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '10.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '10.0'
111
+ description:
112
+ email:
113
+ - ben.balter@github.com
114
+ executables:
115
+ - oss
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - Gemfile
121
+ - LICENSE.md
122
+ - README.md
123
+ - Rakefile
124
+ - bin/oss
125
+ - lib/open_source_stats.rb
126
+ - lib/open_source_stats/event.rb
127
+ - lib/open_source_stats/organization.rb
128
+ - lib/open_source_stats/user.rb
129
+ - lib/open_source_stats/version.rb
130
+ - open_source_stats.gemspec
131
+ - script/bootstrap
132
+ - script/cibuild
133
+ - script/console
134
+ - script/release
135
+ homepage: https://github.com/benbalter/open_source_stats
136
+ licenses:
137
+ - MIT
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.2.0
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: A quick script to generate metrics about the contribution your organization
159
+ makes to the open source community in a 24-hour period
160
+ test_files: []