pivotal_reporting 1.0.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.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gem 'pivotal-tracker'
4
+
5
+ group :development do
6
+ gem 'jeweler', '~> 1.8.3'
7
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,37 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ builder (3.0.0)
5
+ git (1.2.5)
6
+ happymapper (0.4.0)
7
+ libxml-ruby (~> 2.0)
8
+ jeweler (1.8.3)
9
+ bundler (~> 1.0)
10
+ git (>= 1.2.5)
11
+ rake
12
+ rdoc
13
+ json (1.6.5)
14
+ libxml-ruby (2.2.2)
15
+ mime-types (1.17.2)
16
+ nokogiri (1.5.0)
17
+ pivotal-tracker (0.4.1)
18
+ builder
19
+ builder
20
+ happymapper (>= 0.3.2)
21
+ happymapper (>= 0.3.2)
22
+ nokogiri (>= 1.4.3)
23
+ nokogiri (~> 1.4)
24
+ rest-client (~> 1.6.0)
25
+ rest-client (~> 1.6.0)
26
+ rake (0.9.2.2)
27
+ rdoc (3.11)
28
+ json (~> 1.4)
29
+ rest-client (1.6.7)
30
+ mime-types (>= 1.16)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ jeweler (~> 1.8.3)
37
+ pivotal-tracker
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ ## Pivotal Reporting
2
+
3
+ This is a simple ruby script I use to produce a weekly report on what us
4
+ developers have been up to.
5
+
6
+ ### Usage
7
+
8
+ bundle exec ./bin/pivotal_report.rb -t <pivotal token> -p <project id>
9
+
10
+ Where `<pivotal token>` is your pivotal API token available at the end
11
+ of [your profile page][1], and `<project id>` is the id of your project (I
12
+ just look at the URL when I'm examining the project, like in
13
+ https://www.pivotaltracker.com/projects/**12345**, 12345 is the project
14
+ id.)
15
+
16
+
17
+ ### Output
18
+
19
+ You'll get four sections: Discussion Items, Story Bullets, PPU Table,
20
+ and In Progress. The first two are specific to my team's process (we
21
+ pre- and post- estimate stories to improve our estimation process and
22
+ create a feedback loop for production tokens). The PPU Table and In
23
+ Progress sections are probably generically useful.
24
+
25
+ #### The PPU Table
26
+
27
+ PPU is *Points Per User*. Here's an example table:
28
+
29
+ | Elvis | Frank | Ron | Bob | Bill | Reggie | Total || Feature | Bug | Chore |
30
+ client | 0 | 5 | 9 | 0 | 8 | 3 | 25 || 22 | 0 | 3 |
31
+ fires | 0 | 1 | 1 | 0 | 0 | 0 | 2 || 0 | 1 | 1 |
32
+ operations | 10 | 1 | 0 | 5 | 0 | 1 | 17 || 12 | 2 | 3 |
33
+ ops:intake | 2 | 0 | 0 | 0 | 0 | 0 | 2 || 2 | 0 | 0 |
34
+ performance | 0 | 0 | 0 | 0 | 1 | 0 | 1 || 1 | 0 | 0 |
35
+ servers | 0 | 0 | 0 | 2 | 0 | 0 | 2 || 0 | 0 | 2 |
36
+ sourcing | 0 | 0 | 0 | 1 | 0 | 0 | 1 || 0 | 0 | 1 |
37
+ ------------+-------+-------+-----+-----+------+--------+--------++---------+-----+-------+
38
+ Total | 12 | 7 | 10 | 8 | 9 | 4 | 50 || 37 | 3 | 10 |
39
+
40
+ This table is about the stories in the most recently completed
41
+ iteration. The first Y axis is the unique labels in the iteration, the X
42
+ axis is first the developers who contributed and then a breakdown of
43
+ story type. The numbers in the body of the table are the points
44
+ allocated to that combination of label and either developer or story
45
+ type. There are of course a pair of totals columns.
46
+
47
+ This table is useful in our process of both planning for development
48
+ capacity and monitoring usage. I take this report and add a column for
49
+ planned points by label and over/under by label. We then use this
50
+ analysis to discuss the tech department's future planning and modify our
51
+ plans and expectations.
52
+
53
+ #### In Progress
54
+
55
+ This is just a sum of the points allocated to stories that are currently
56
+ started, delivered, or unaccepted. I use this to show the current state
57
+ of projects in progress during my iteration planning meeting.
58
+
59
+ [1]: https://www.pivotaltracker.com/profile
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "pivotal_reporting"
18
+ gem.homepage = "http://github.com/joshsz/pivotal_reporting"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{A command-line reporting tool for @pivotaltracker}
21
+ gem.description = %Q{Please see the README. This tool generates some reports that could be useful if you're managing a team of developers using Pivotal Tracker}
22
+ gem.email = "joshsz@gmail.com"
23
+ gem.authors = ["Joshua Szmajda"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ # require 'rspec/core'
29
+ # require 'rspec/core/rake_task'
30
+ # RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ # spec.pattern = FileList['spec/**/*_spec.rb']
32
+ # end
33
+
34
+ # RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ # spec.pattern = 'spec/**/*_spec.rb'
36
+ # spec.rcov = true
37
+ # end
38
+
39
+ # task :default => :spec
40
+
41
+ # require 'rdoc/task'
42
+ # Rake::RDocTask.new do |rdoc|
43
+ # version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ # rdoc.rdoc_dir = 'rdoc'
46
+ # rdoc.title = "pivotal_reporting_2 #{version}"
47
+ # rdoc.rdoc_files.include('README*')
48
+ # rdoc.rdoc_files.include('lib/**/*.rb')
49
+ # end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+
4
+ opts = {}
5
+ parser = OptionParser.new do |os|
6
+ os.banner =<<-EOT
7
+ Usage: pivotal_report.rb [options]
8
+ EOT
9
+
10
+ os.on('-t [TOKEN]', '--token [TOKEN]', 'Set API token') do |v|
11
+ opts[:token] = v
12
+ end
13
+
14
+ os.on('-p [PROJECT ID]', '--project-id [PROJECT ID]', 'Set Project ID') do |v|
15
+ opts[:project_id] = v
16
+ end
17
+ end
18
+
19
+ begin
20
+ parser.parse!
21
+ if opts[:token].nil? || opts[:project_id].nil?
22
+ puts parser
23
+ exit
24
+ end
25
+ end
26
+
27
+ require 'rubygems'
28
+ require 'bundler/setup'
29
+ require 'pivotal-tracker'
30
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'pivotal_report')
31
+
32
+ app = PivotalReport.new(opts)
33
+ app.report
@@ -0,0 +1,158 @@
1
+ class PivotalTracker::Story
2
+ def initials
3
+ owned_by.split(/ /).map{|e| e[0] }.join
4
+ end
5
+ end
6
+ class PivotalReport
7
+ def initialize(options)
8
+ PivotalTracker::Client.token = options[:token]
9
+ @project = PivotalTracker::Project.find(options[:project_id])
10
+ end
11
+
12
+ POST_EST = 'epost'
13
+
14
+ def report
15
+ show_discussion_items
16
+ separator
17
+ show_story_bullets
18
+ separator
19
+ show_ppu_table
20
+ separator
21
+ show_stories_in_progress
22
+ end
23
+
24
+ def show_accounting_breakdown
25
+ # TODO
26
+ raise 'Unimplemented!'
27
+ end
28
+
29
+ def separator
30
+ puts "\n-----\n\n"
31
+ end
32
+
33
+ def show_discussion_items
34
+ puts "Discussion Items"
35
+ puts ""
36
+ estimates = stories.sort{|a,b| a.owned_by <=> b.owned_by }.map(&:labels).map{|e| e.to_s.split(/,/) }.flatten.map(&:strip).compact.uniq.select{|l| l =~ /^(#{POST_EST}|e\d)$/ }.sort
37
+ estimates.each do |est|
38
+ set = stories_with_label(est)
39
+ set.each do |story|
40
+ puts "(\e[31m#{story.estimate}\e[0m: \e[32m#{est}\e[0m) #{story.name.strip} (\e[33m#{story.initials}\e[0m) [\e[34m#{story.labels}\e[0m]"
41
+ end
42
+ end
43
+ end
44
+
45
+ def show_story_bullets
46
+ puts "Story Bullets"
47
+ puts ""
48
+ lis = nil
49
+ stories.sort{|a,b| a.owned_by <=> b.owned_by }.each do |story|
50
+ if story.initials != lis
51
+ lis = story.initials
52
+ puts "#{story.initials}:"
53
+ end
54
+ puts "#{story.story_type.to_s[0]} #{story.name.strip} (#{story.estimate} point#{story.estimate > 1 ? 's' : ''})"
55
+ end
56
+ end
57
+
58
+ def show_ppu_table
59
+ puts "PPU Table"
60
+
61
+ categories = stories.map(&:labels).map{|e| e.to_s.split(/,/) }.flatten.map(&:strip).compact.uniq.reject{|l| l =~ /^(#{POST_EST}|e\d)$/ }.sort
62
+ people = stories.map(&:owned_by).uniq.sort.map{|p| [p, p.split(/ /).first]}
63
+ c_width = (['Total'] + categories).map(&:length).max
64
+
65
+ sseen = []
66
+
67
+ o = []
68
+ o << "".ljust(c_width)
69
+ o << " | "
70
+ people.each {|p| o << p[1]; o << " | " }
71
+ o << " Total || Feature | Bug | Chore |"
72
+ puts o.join
73
+
74
+ categories.each do |cat|
75
+ o = []
76
+ o << cat.ljust(c_width)
77
+ o << " | "
78
+ people.each do |person, short|
79
+ o << stories_for_user(person, stories_with_label(cat)).map(&:estimate).inject(0){|s,e| s + e }.to_s.rjust(short.length)
80
+ o << " | "
81
+ end
82
+ set = stories_with_label(cat)
83
+ sseen += set
84
+ o << set.map(&:estimate).inject(0){|s,e| s + e }.to_s.rjust('Total '.length)
85
+ o << " || "
86
+ %w{feature bug chore}.each do |type|
87
+ o << stories_with_label(cat, stories_for_type(type)).map(&:estimate).inject(0){|s,e| s + e }.to_s.rjust(type.length)
88
+ o << " | "
89
+ end
90
+ puts o.join
91
+ end
92
+
93
+ o = []
94
+ o << "-" * c_width
95
+ o << "-+-"
96
+ people.each do |person, short|
97
+ o << "-" * short.length
98
+ o << "-+-"
99
+ end
100
+ o << "-------++---------+-----+-------+"
101
+ puts o.join
102
+
103
+ o = []
104
+ o << "Total".ljust(c_width)
105
+ o << " | "
106
+ people.each do |person, short|
107
+ o << stories_for_user(person).map(&:estimate).inject(0){|s,e| s + e }.to_s.rjust(short.length)
108
+ o << " | "
109
+ end
110
+ o << stories.map(&:estimate).inject(0){|s,e| s + e }.to_s.rjust('Total '.length)
111
+ o << " || "
112
+ %w{feature bug chore}.each do |type|
113
+ o << stories_for_type(type).map(&:estimate).inject(0){|s,e| s + e }.to_s.rjust(type.length)
114
+ o << " | "
115
+ end
116
+ puts o.join
117
+
118
+ missing = stories - sseen
119
+ if missing.any?
120
+ puts "Missing:"
121
+ puts missing.map(&:name).join("\n")
122
+ end
123
+
124
+ end
125
+
126
+ def show_stories_in_progress
127
+ in_progress = @project.stories.all(:state => ['started', 'delivered'])
128
+ points_in_progress = in_progress.map(&:estimate).inject(0){|s,e| s+e}
129
+
130
+ puts "In Progress: #{points_in_progress} points"
131
+ end
132
+
133
+
134
+
135
+ private
136
+ def iteration
137
+ @iteration ||= PivotalTracker::Iteration.done(@project, :offset => '-1').first
138
+ end
139
+
140
+ def stories
141
+ @stories ||= iteration.stories
142
+ end
143
+
144
+ def stories_with_label(label, set=nil)
145
+ set ||= stories
146
+ set.select{|s| s.labels =~ /#{label}/ }
147
+ end
148
+
149
+ def stories_for_user(user, set=nil)
150
+ set ||= stories
151
+ set.select{|s| s.owned_by == user }
152
+ end
153
+
154
+ def stories_for_type(type, set=nil)
155
+ set ||= stories
156
+ set.select{|s| s.story_type == type }
157
+ end
158
+ end
@@ -0,0 +1,50 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "pivotal_reporting"
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Joshua Szmajda"]
12
+ s.date = "2012-02-07"
13
+ s.description = "Please see the README. This tool generates some reports that could be useful if you're managing a team of developers using Pivotal Tracker"
14
+ s.email = "joshsz@gmail.com"
15
+ s.executables = ["pivotal_report"]
16
+ s.extra_rdoc_files = [
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "bin/pivotal_report",
26
+ "lib/pivotal_report.rb",
27
+ "pivotal_reporting.gemspec"
28
+ ]
29
+ s.homepage = "http://github.com/joshsz/pivotal_reporting"
30
+ s.licenses = ["MIT"]
31
+ s.require_paths = ["lib"]
32
+ s.rubygems_version = "1.8.10"
33
+ s.summary = "A command-line reporting tool for @pivotaltracker"
34
+
35
+ if s.respond_to? :specification_version then
36
+ s.specification_version = 3
37
+
38
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
39
+ s.add_runtime_dependency(%q<pivotal-tracker>, [">= 0"])
40
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
41
+ else
42
+ s.add_dependency(%q<pivotal-tracker>, [">= 0"])
43
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
44
+ end
45
+ else
46
+ s.add_dependency(%q<pivotal-tracker>, [">= 0"])
47
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
48
+ end
49
+ end
50
+
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pivotal_reporting
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joshua Szmajda
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-07 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pivotal-tracker
16
+ requirement: &21831180 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *21831180
25
+ - !ruby/object:Gem::Dependency
26
+ name: jeweler
27
+ requirement: &21830700 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.8.3
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *21830700
36
+ description: Please see the README. This tool generates some reports that could be
37
+ useful if you're managing a team of developers using Pivotal Tracker
38
+ email: joshsz@gmail.com
39
+ executables:
40
+ - pivotal_report
41
+ extensions: []
42
+ extra_rdoc_files:
43
+ - README.md
44
+ files:
45
+ - Gemfile
46
+ - Gemfile.lock
47
+ - README.md
48
+ - Rakefile
49
+ - VERSION
50
+ - bin/pivotal_report
51
+ - lib/pivotal_report.rb
52
+ - pivotal_reporting.gemspec
53
+ homepage: http://github.com/joshsz/pivotal_reporting
54
+ licenses:
55
+ - MIT
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ segments:
67
+ - 0
68
+ hash: -1627381237856098628
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 1.8.10
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: A command-line reporting tool for @pivotaltracker
81
+ test_files: []