tpscript 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: 9239fce751b676a3dc58d6ac0cc61d5d1f0c5545
4
+ data.tar.gz: a211c7b9bd46f62807fd14335549a1f299ff1220
5
+ SHA512:
6
+ metadata.gz: 505a7a4a2b6a8e873a4efc62d346a6e9959e64dd6471106c677cc6233e887795c176237c2b3e8358c80f2d87e82a0c98f8d85a9ca71f0bf57a7a1e4c67fd4480
7
+ data.tar.gz: bd765a20e32aed54d9c26315bee9fda67eb8fd7336ae91588090074cf5beed3ff76c9dcb8d0605b6213ffe38d35109340afb6b91ae3fa0e73e5a23380ab2440b
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.json
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ *.bundle
20
+ *.so
21
+ *.o
22
+ *.a
23
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tpscript.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Max Fliri
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ # tpscript
2
+
3
+ Scripts for TargetProcess
4
+
5
+ ## Cumulative flow
6
+
7
+ Extracts from TargetProcess the cumulative flow for a specific team, and prints it in CSV format.
8
+
9
+ From the repository main directory, run:
10
+
11
+ ./cumulative_flow -t <a TP team> -b <TP base url> -u <TP username> -p <TP password>
12
+
13
+ The team must be provided as the abbreviation assigned to it in TargetProcess.
14
+
15
+ *Example:*
16
+
17
+ ./cumulative_flow -t XYZ -b https://mytpurl -u bob -p agqywq865q
18
+
19
+ ### Select specific states
20
+
21
+ It is possible to include only specific states in the result, and to define a specific order for them.
22
+ To do this, add the `--states` option with the names of the states you want, separated by commas.
23
+
24
+ ./cumulative_flow -t XYZ -b https://mytpurl -u bob -p agqywq865q \
25
+ --states 'State one,State two,State three'
26
+
27
+ ### Iteration day
28
+
29
+ Iteration start day can be defined using the `--day` option.
30
+
31
+ ./cumulative_flow -t XYZ -b https://mytpurl -u bob -p agqywq865q --day wednesday
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/target_process'
4
+ require_relative '../lib/enumerable_ext'
5
+ require_relative '../lib/cli'
6
+ require_relative '../lib/cumulative_flow'
7
+
8
+ options = Cli.options!
9
+
10
+ tp = TargetProcess.new(username: options[:username], password: options[:password], base_uri: options[:base_uri])
11
+
12
+ stories = tp.user_stories(options[:team])
13
+ history = stories.flat_map {|s| tp.story_history(s["Id"]) }
14
+
15
+ puts CumulativeFlow.new(history, iteration_start: options[:day], states: options[:states]).to_csv
@@ -0,0 +1,46 @@
1
+ require 'optparse'
2
+
3
+ module Cli
4
+
5
+ def self.options!(args = ARGV)
6
+
7
+ options = {}
8
+
9
+ OptionParser.new do |opts|
10
+ opts.banner = <<-STRING.gsub(/^ +/, '')
11
+ Extracts from TargetProcess the cumulative flow for a specific team, and prints it in CSV format
12
+
13
+ Usage: #{$PROGRAM_NAME} [options]
14
+
15
+ STRING
16
+
17
+ opts.on("-t", "--team TEAM_ABBREVIATION", "(required) select a team to filter the data") do |team|
18
+ options[:team] = team
19
+ end
20
+
21
+ opts.on("-u", "--username USERNAME", "(required) username to log on TargetProcess") do |username|
22
+ options[:username] = username
23
+ end
24
+
25
+ opts.on("-p", "--password PASSWORD", "(required) password to log on TargetProcess") do |password|
26
+ options[:password] = password
27
+ end
28
+
29
+ opts.on("-b", "--base-uri URI", "(required) URI of TargetProcess") do |uri|
30
+ options[:base_uri] = uri
31
+ end
32
+
33
+ opts.on("--states STATES", "included states, separated by commas") do |states|
34
+ options[:states] = states.split(',').map(&:chomp)
35
+ end
36
+
37
+ options[:day] = "tuesday"
38
+ opts.on("-d", "--day DAY", %w{monday tuesday wednesday thursday friday saturday sunday},
39
+ "day of week used as iteration start (defaults to tuesday)") do |day|
40
+ options[:day] = day
41
+ end
42
+ end.parse!(args)
43
+
44
+ options
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ require 'csv'
2
+ require 'date'
3
+ require 'set'
4
+
5
+ class CumulativeFlow
6
+
7
+ def initialize(user_story_history, states: nil, iteration_start: nil)
8
+ @data = dates(user_story_history, iteration_start).map {|date| {"Date" => date}.merge(effort_by_state(snapshot(user_story_history, date))) }
9
+ @states = states || all_states(user_story_history)
10
+ end
11
+
12
+ def to_csv
13
+ CSV.generate do |csv|
14
+ csv << [''] + @states
15
+ @data.each do |i|
16
+ csv << [ i["Date"] ] + @states.map {|state| i[state] || 0.0}
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def snapshot(history, date)
24
+ history.reject {|i| i["Date"] >= date}.group_by {|i| i["UserStory"]["Id"] }.values.map {|a| a.max_by {|i| i["Date"]}}
25
+ end
26
+
27
+ def effort_by_state(snapshot)
28
+ stories_by_state = snapshot.group_by {|i| i["EntityState"]["Id"] }.values
29
+
30
+ stories_by_state.hmap do |stories|
31
+ state_name = stories.first["EntityState"]["Name"]
32
+ cumulated_effort = stories.map {|i| i["Effort"]}.reduce(:+)
33
+ [state_name, cumulated_effort]
34
+ end
35
+ end
36
+
37
+ def all_states(history)
38
+ history.reduce(Set.new) {|states, i| states << i["EntityState"]["Name"] }.to_a
39
+ end
40
+
41
+ def dates(history, iteration_start)
42
+ start = Date.parse iteration_start
43
+ stop = history.map {|i| i["Date"]}.min
44
+ dates = start.step(stop, -7).to_a.reverse
45
+ end
46
+ end
@@ -0,0 +1,8 @@
1
+ module Enumerable
2
+ def hmap
3
+ self.reduce({}) do |map, item|
4
+ key, value = yield item
5
+ map.tap {|map| map[key] = value }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,45 @@
1
+ require 'date'
2
+ require 'httparty'
3
+ require 'json'
4
+ require 'uri'
5
+
6
+ class TargetProcess
7
+
8
+ def initialize(username: nil, password: nil, base_uri: nil)
9
+ @username = username
10
+ @password = password
11
+ @base_uri = base_uri
12
+ end
13
+
14
+ def user_stories(team_abbr)
15
+ get_items "#{@base_uri}/api/v1/UserStories?format=json&where=Team.Abbreviation%20eq%20%27#{team_abbr}%27"
16
+ end
17
+
18
+ def story_history(id)
19
+ get_items("#{@base_uri}/api/v1/UserStories/#{id}/History?format=json").map {|i| i.tap {|i| i["Date"] = parse_date(i["Date"]) }}
20
+ end
21
+
22
+ private
23
+
24
+ def parse_date(date)
25
+ match = /\/Date\(([0-9]+)([+-][0-9]+)\)\//.match date
26
+ DateTime.strptime(match[1], "%Q")
27
+ end
28
+
29
+ def get_items(url)
30
+ json = get(url)
31
+
32
+ if json.has_key? "Next" then
33
+ json["Items"] + get_items(URI.escape(json["Next"]))
34
+ else
35
+ json["Items"]
36
+ end
37
+ end
38
+
39
+ def get(url)
40
+ $stderr.puts "Getting #{url}..."
41
+ response = HTTParty.get(url, basic_auth: {username: @username, password: @password})
42
+ JSON.parse(response.body)
43
+ end
44
+
45
+ end
@@ -0,0 +1,3 @@
1
+ module Tpscript
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tpscript"
8
+ spec.version = Tpscript::VERSION
9
+ spec.authors = ["Max Fliri"]
10
+ spec.email = ["mfliri@thoughtworks.com"]
11
+ spec.summary = %q{Scripts for TargetProcess}
12
+ spec.description = %q{Scripts for TargetProcess}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = "~> 2.1"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "httparty", "0.13.2"
25
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tpscript
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Max Fliri
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
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: httparty
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.13.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.13.2
55
+ description: Scripts for TargetProcess
56
+ email:
57
+ - mfliri@thoughtworks.com
58
+ executables:
59
+ - cumulative_flow
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - bin/cumulative_flow
70
+ - lib/cli.rb
71
+ - lib/cumulative_flow.rb
72
+ - lib/enumerable_ext.rb
73
+ - lib/target_process.rb
74
+ - lib/version.rb
75
+ - tpscript.gemspec
76
+ homepage: ''
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '2.1'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.2.2
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Scripts for TargetProcess
100
+ test_files: []