tpscript 0.0.1

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.
@@ -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: []