github-stats 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7b06fb068ddd3c1e7e0e13ac784674cb3b74c33e
4
- data.tar.gz: 825ae518a430007fbdbd65b492556db6c365d917
3
+ metadata.gz: ab422a50b60641cae6051a695a86876b5e5ad707
4
+ data.tar.gz: 43dfaa3d02fe256f39641d7f132afd9879b724c8
5
5
  SHA512:
6
- metadata.gz: 8d5a88b2a8408bd4d259e0ea1eb5f9f423e1131540c165c1e3f1cefe4b4181e74915065b6179ba4a2888c6fc7d41522f78e1f32950d5479e01467c70a0bbfc31
7
- data.tar.gz: 0127f29ca42621cce9d1779331c0da9013b5ce6a05a42e31b83bf271d25df2e22f7f1d0d0ef607afe662f7a1e5199ea17d5b0f33d3d5c3308718faf6951ddb15
6
+ metadata.gz: c43a0a82b19ca8e1db9301965e89c0577da86e1253314da443873e0ab95fc689cb289833ba1cb99ad1052ac532b2bce397fbb04f908f8c051c7c1a2fe1dd54a9
7
+ data.tar.gz: 83a13c2de52cfa8b2a56af2f0db89057a39047d25ef2d1ee9a7ebe6f034403acac89bb158df4c3c261d958c222815cce760185571f56b48f35c4f7121b7a3749
data/README.md CHANGED
@@ -1,22 +1,23 @@
1
- # Github Issue Stats
1
+ # Github Issue Stats [![Gem Version](https://badge.fury.io/rb/github-stats.svg)](https://badge.fury.io/rb/github-stats)
2
+
2
3
 
3
4
  Github issues are a decent way to track work on a small project; and with the rise of tools such as Waffle.io and ZenHub, it appears that they will slowly but surely become better and better for managing long term projects.
4
5
 
5
- However statistical analysis of github issues is still very much lacking. `github-issue-stats` is a command line tool / gem that takes github searches and converts them into useful project reports.
6
+ However statistical analysis of github issues is still very much lacking. `github-stats` is a command line tool / gem that takes github searches and converts them into useful project reports.
6
7
 
7
8
  ## Installation & Usage
8
- Assuming you are using Ruby 1.9.3 or above: `gem install github-issue-stats` will install the tool. Running `github-issue-stats "whatever github search string you want"` will run the search, outputting a closed by week report.
9
+ Assuming you are using Ruby 1.9.3 or above: `gem install github-stats` will install the tool. Running `github-stats "whatever github search string you want"` will run the search, outputting a closed by week report.
9
10
 
10
- For a detailed list of options and command line flags, please refer to `github-issue-stats --help`.
11
+ For a detailed list of options and command line flags, please refer to `github-stats --help`.
11
12
 
12
13
  **Please Note!** Github's Search API restricts searches to [the first 1,000 results](https://developer.github.com/v3/search/#about-the-search-api) and limits [unauthenticated requests to 10 per minute](https://developer.github.com/v3/search/#rate-limit). This means you can run 1 report per-minute that would return a full 1,000 issues.
13
14
 
14
- I recommend using filters such as [`state:closed`](https://help.github.com/articles/searching-issues/#search-based-on-whether-an-issue-or-pull-request-is-open) and/or [`updated:>=2016-01-01`](https://help.github.com/articles/searching-issues/#search-based-on-when-an-issue-or-pull-request-was-created-or-last-updated) to scope your requests down, based upon the report type.
15
+ I recommend using filters such as [`state:closed`](https://help.github.com/articles/searching-issues/#search-based-on-whether-anor-pull-request-is-open) and/or [`updated:>=2016-01-01`](https://help.github.com/articles/searching-issues/#search-based-on-when-anor-pull-request-was-created-or-last-updated) to scope your requests down, based upon the report type.
15
16
 
16
17
  ### Example
17
18
  The following example shows how to get a closed by week report for Rails for the first 4 weeks of May 2016
18
19
  ```
19
- $ github-issue-stats "repo:rails/rails type:issue is:closed closed:2016-05-01..2016-05-28"
20
+ $ github-stats "repo:rails/rails type:issue is:closed closed:2016-05-01..2016-05-28"
20
21
  2016-17 3 1
21
22
  2016-18 27 10
22
23
  2016-19 30 20
@@ -28,7 +28,7 @@ OptionParser.new do |opts|
28
28
  options[:ingest] = ingest
29
29
  end
30
30
 
31
- opts.on('-r REPORT_TYPE', '--report REPORT_TYPE', 'Defaults to closed_by_week') do |report_type|
31
+ opts.on('-r REPORT_TYPE', '--report REPORT_TYPE', 'Default is closed_by_week; may be cycle_time, closed_by_week, or created_by_week') do |report_type|
32
32
  options[:report_type] = report_type
33
33
  end
34
34
  end.parse!
@@ -3,6 +3,8 @@ require 'sequel'
3
3
 
4
4
  require_relative 'github_stats/cli'
5
5
  require_relative 'github_stats/database'
6
+ require_relative 'github_stats/reports'
7
+ require_relative 'github_stats/created_by_week_report'
6
8
  require_relative 'github_stats/closed_by_week_report'
7
9
  require_relative 'github_stats/issue_ingester'
8
10
 
@@ -19,7 +19,7 @@ module GithubStats
19
19
  def run
20
20
  setup_db
21
21
  ingest
22
- report
22
+ results
23
23
  end
24
24
 
25
25
  private def setup_db
@@ -38,13 +38,16 @@ module GithubStats
38
38
  end
39
39
 
40
40
  private def report
41
- results = ClosedByWeekReport.new(search_string, options).results
42
- SpaceSeperatedLinePerResultResultsView.new(results)
41
+ Reports.for(options[:report_type]).new(search_string, options)
42
+ end
43
+
44
+ private def results
45
+ CommaSeperatedLinePerResultResultsView.new(report.results)
43
46
  end
44
47
 
45
48
  # Transforms a result set into a space-seperated table the results hash
46
49
  # keys becoming the table headers and line breaks between rows.
47
- class SpaceSeperatedLinePerResultResultsView
50
+ class CommaSeperatedLinePerResultResultsView
48
51
  attr_accessor :results
49
52
  def initialize(results)
50
53
  self.results = results
@@ -55,8 +58,8 @@ module GithubStats
55
58
  end
56
59
 
57
60
  def to_s
58
- fields.join(' ') + "\n" + results.map do |result|
59
- fields.map(&result.method(:fetch)).join(' ')
61
+ fields.join(',') + "\n" + results.map do |result|
62
+ fields.map(&result.method(:fetch)).join(',')
60
63
  end.join("\n")
61
64
  end
62
65
  end
@@ -1,65 +1,26 @@
1
1
  require_relative 'database'
2
+ require_relative 'report'
2
3
  module GithubStats
3
- # Provides week-by-week breakdown of issues closed, grouped by week
4
- # with a 3 week moving average.
5
- class ClosedByWeekReport
6
- attr_accessor :search_string, :options
7
-
8
- def initialize(search_string, options)
9
- self.search_string = search_string
10
- self.options = options
11
- end
12
-
13
- def results
14
- results = with_velocity(with_week_closed(with_qty(issues)))
15
- Results.new(results)
16
- end
17
-
18
- private def with_velocity(issues)
19
- closed_two_weeks_ago = 0
20
- closed_last_week = 0
21
- issues.map do |issue|
22
- issue[:velocity] = average(closed_two_weeks_ago, closed_last_week,
23
- issue[:qty])
24
- closed_two_weeks_ago = closed_last_week
25
- closed_last_week = issue[:qty]
26
- issue
4
+ module Reports
5
+ # Provides week-by-week breakdown of issues closed, grouped by week
6
+ # with a 3 week moving average.
7
+ class ClosedByWeekReport
8
+ attr_accessor :search_string, :options
9
+ include Report
10
+
11
+ def results
12
+ results = with_moving_average(:velocity,
13
+ by_week_closed(with_qty(issues)))
14
+ Reports::Results.new(results, keys: [:week_closed, :qty, :velocity])
27
15
  end
28
- end
29
-
30
- private def average(*numbers)
31
- numbers.reduce(:+) / numbers.length
32
- end
33
-
34
- private def with_qty(dataset)
35
- dataset.select { count(:id).as qty }
36
- end
37
-
38
- private def with_week_closed(dataset)
39
- dataset.select_append { strftime('%Y-%W', closed_at).as(week_closed) }
40
- .group_by(:week_closed)
41
- end
42
-
43
- private def issues
44
- db.issues.where(search_string: search_string).where { closed_at !~ nil }
45
- end
46
-
47
- private def db
48
- @db ||= Database.new(options)
49
- end
50
-
51
- # Provides enumerable access to the results
52
- class Results
53
- attr_accessor :data
54
- extend Forwardable
55
- def_delegators :data, :each, :map
56
16
 
57
- def initialize(data)
58
- self.data = data
17
+ private def by_week_closed(dataset)
18
+ dataset.select_append { strftime('%Y-%W', closed_at).as(week_closed) }
19
+ .group_by(:week_closed)
59
20
  end
60
21
 
61
- def keys
62
- [:week_closed, :qty, :velocity]
22
+ private def issues
23
+ db.issues.where(search_string: search_string).where { closed_at !~ nil }
63
24
  end
64
25
  end
65
26
  end
@@ -0,0 +1,29 @@
1
+ require_relative 'database'
2
+ require_relative 'report'
3
+ module GithubStats
4
+ module Reports
5
+ # Provides week-by-week breakdown of issues created, grouped by week
6
+ # with a 3 week moving average.
7
+ class CreatedByWeekReport
8
+ attr_accessor :search_string, :options
9
+
10
+ include Report
11
+
12
+ def results
13
+ results = with_moving_average(:add_rate,
14
+ by_week_created(with_qty(issues)))
15
+ Reports::Results.new(results, keys: [:week_created, :qty, :add_rate])
16
+ end
17
+
18
+ private def by_week_created(dataset)
19
+ dataset.select_append { strftime('%Y-%W', created_at).as(week_created) }
20
+ .group_by(:week_created)
21
+ end
22
+
23
+ private def issues
24
+ db.issues.where(search_string: search_string)
25
+ .where { created_at !~ nil }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'database'
2
+ require_relative 'report'
3
+ module GithubStats
4
+ module Reports
5
+ # Provides issue cycle time from started at to done
6
+ class CycleTimeReport
7
+ attr_accessor :search_string, :options
8
+ include Report
9
+
10
+ def results
11
+ Results.new(with_cycle_time(issues), keys: [:week_closed, :created_at, :started_at, :closed_at, :cycle_time, :url])
12
+ end
13
+
14
+ private def with_cycle_time(dataset)
15
+ dataset.all.map do |item|
16
+ item[:week_closed] = (item[:closed_at].strftime("%Y-%W"))
17
+ item[:cycle_time] = work_days_between(item[:closed_at], item[:started_at])
18
+ item
19
+ end
20
+ end
21
+
22
+ private def work_days_between(end_time, start_time)
23
+ hours_between = (end_time - start_time) / 60 / 60
24
+ return hours_between.to_f / 24.to_f if hours_between < 12
25
+ days_between = (end_time.strftime('%j').to_i - start_time.strftime('%j').to_i)
26
+ weeks_between = (end_time.strftime('%W').to_i - start_time.strftime('%W').to_i)
27
+ return (hours_between - (days_between * 10 + weeks_between * 28)).to_f / 24.to_f
28
+ end
29
+
30
+ private def issues
31
+ db.issues.where(search_string: search_string).where { closed_at !~ nil }
32
+ .where { started_at !~ nil }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -6,33 +6,12 @@ module GithubStats
6
6
 
7
7
  def initialize(options)
8
8
  self.octokit = Octokit::Client.new(access_token:
9
- options[:github_access_token])
9
+ options[:github_access_token],
10
+ auto_paginate: true)
10
11
  end
11
12
 
12
13
  def search_issues(query, options = { per_page: 100 })
13
14
  octokit.search_issues(query, options)
14
- Response.new(self)
15
- end
16
-
17
- def last_response
18
- octokit.last_response
19
- end
20
-
21
- # Auto-paginate!
22
- class Response
23
- attr_reader :client
24
- def initialize(client)
25
- @client = client
26
- end
27
-
28
- def each(&block)
29
- last_response = client.last_response
30
- loop do
31
- last_response.data.items.each(&block)
32
- last_response = last_response.rels[:next].get
33
- break if last_response.rels[:next].nil?
34
- end
35
- end
36
15
  end
37
16
  end
38
17
  end
@@ -11,25 +11,39 @@ module GithubStats
11
11
 
12
12
  def ingest
13
13
  return unless options[:ingest]
14
- github.search_issues(search_string).each(&method(:insert_or_update))
14
+ github.search_issues(search_string).items.each(&method(:insert_or_update))
15
15
  end
16
16
 
17
17
  private def insert_or_update(result)
18
18
  issue = issues.first(github_id: result[:id])
19
- return insert(result) unless issue
20
- return update(issue, result) if issue[:closed_at] != result[:closed_at] ||
21
- issue[:created_at] != result[:created_at]
19
+ started_at = started_at(result)
20
+ return insert(result, started_at: started_at) unless issue
21
+ return update(issue, result, started_at: started_at) if issue[:closed_at] != result[:closed_at] ||
22
+ issue[:created_at] != result[:created_at] ||
23
+ issue[:started_at] != started_at
22
24
  end
23
25
 
24
- private def update(issue, result)
26
+ private def started_at(result)
27
+ starting_events = result.rels[:events].get.data.select do |event|
28
+ event[:event] == 'labeled' && (event[:label] || {})[:name] == 'in-progress'
29
+ end.sort_by do |event|
30
+ event[:created_at]
31
+ end
32
+ starting_event = starting_events.last
33
+ starting_event.nil? ? nil : starting_event[:created_at]
34
+ end
35
+
36
+ private def update(issue, result, started_at:)
25
37
  issue.update(closed_at: result[:closed_at],
26
- created_at: result[:created_at])
38
+ created_at: result[:created_at],
39
+ started_at: started_at)
27
40
  end
28
41
 
29
- private def insert(result)
42
+ private def insert(result, started_at:)
30
43
  issues.insert(search_string: search_string, github_id: result[:id],
31
44
  closed_at: result[:closed_at],
32
45
  created_at: result[:created_at],
46
+ started_at: started_at,
33
47
  url: result[:url])
34
48
  end
35
49
 
@@ -0,0 +1,34 @@
1
+ module GithubStats
2
+ module Reports
3
+ module Report
4
+ def initialize(search_string, options)
5
+ self.search_string = search_string
6
+ self.options = options
7
+ end
8
+
9
+ private def with_moving_average(label, weeks)
10
+ closed_two_weeks_ago = 0
11
+ closed_last_week = 0
12
+ weeks.map do |week|
13
+ week[label] = average(closed_two_weeks_ago, closed_last_week,
14
+ week[:qty])
15
+ closed_two_weeks_ago = closed_last_week
16
+ closed_last_week = week[:qty]
17
+ week
18
+ end
19
+ end
20
+
21
+ private def average(*numbers)
22
+ numbers.reduce(:+) / numbers.length
23
+ end
24
+
25
+ private def with_qty(dataset)
26
+ dataset.select { count(:id).as qty }
27
+ end
28
+
29
+ private def db
30
+ @db ||= Database.new(options)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ require_relative 'closed_by_week_report'
2
+ require_relative 'cycle_time_report'
3
+ require_relative 'created_by_week_report'
4
+
5
+ module GithubStats
6
+ module Reports
7
+ def self.for(report_type)
8
+ { 'closed_by_week' => ClosedByWeekReport,
9
+ 'created_by_week' => CreatedByWeekReport,
10
+ 'cycle_time' => CycleTimeReport }.fetch(report_type)
11
+ end
12
+
13
+ # Provides enumerable access to the results
14
+ class Results
15
+ attr_accessor :data, :keys
16
+ extend Forwardable
17
+ def_delegators :data, :each, :map
18
+
19
+ def initialize(data, keys: [])
20
+ self.data = data
21
+ self.keys = keys
22
+ end
23
+ end
24
+ end
25
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: github-stats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zee@Zinc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-29 00:00:00.000000000 Z
11
+ date: 2016-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: octokit
@@ -69,9 +69,13 @@ files:
69
69
  - lib/github_stats.rb
70
70
  - lib/github_stats/cli.rb
71
71
  - lib/github_stats/closed_by_week_report.rb
72
+ - lib/github_stats/created_by_week_report.rb
73
+ - lib/github_stats/cycle_time_report.rb
72
74
  - lib/github_stats/database.rb
73
75
  - lib/github_stats/github_client.rb
74
76
  - lib/github_stats/issue_ingester.rb
77
+ - lib/github_stats/report.rb
78
+ - lib/github_stats/reports.rb
75
79
  homepage: https://github.com/zincmade/github-stats
76
80
  licenses:
77
81
  - MIT