pivotal-tracker-api 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/pivotal-tracker-api.iml +53 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +81 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/pivotal-tracker-api/activity.rb +42 -0
- data/lib/pivotal-tracker-api/attachment.rb +19 -0
- data/lib/pivotal-tracker-api/client.rb +96 -0
- data/lib/pivotal-tracker-api/comment.rb +25 -0
- data/lib/pivotal-tracker-api/iteration.rb +39 -0
- data/lib/pivotal-tracker-api/person.rb +29 -0
- data/lib/pivotal-tracker-api/pivotal_service.rb +114 -0
- data/lib/pivotal-tracker-api/project.rb +63 -0
- data/lib/pivotal-tracker-api/story.rb +199 -0
- data/lib/pivotal-tracker-api/task.rb +25 -0
- data/lib/pivotal-tracker-api.rb +12 -0
- data/test/helper.rb +18 -0
- data/test/test_pivotal-tracker-api.rb +7 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cbe2c90c2bba74b1786f6b01037b640aa7bb18cc
|
4
|
+
data.tar.gz: 1f8948d45390620446e88181d5fbf88da5747b79
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: be5c6620c49eede7d9804279c3031ff4636653b4c740cc6613e4f702b5e69db06da77b5cb66a62833adad507978e3955d8954bf00cab521751779b40db5043ee
|
7
|
+
data.tar.gz: 4c439b0aad7bf21d998bd770abf2070adfa4041f8a2b5ab60b36e0c35251f4a38bcfbfcf0ba46512437c15f0a783081a093c2242ac79f0488daa67032fdeb5c5
|
data/.document
ADDED
data/.idea/.name
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pivotal-tracker-api
|
data/.idea/.rakeTasks
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<Settings><!--This file was automatically generated by Ruby plugin.
|
3
|
+
You are allowed to:
|
4
|
+
1. Remove rake task
|
5
|
+
2. Add existing rake tasks
|
6
|
+
To add existing rake tasks automatically delete this file and reload the project.
|
7
|
+
--><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Build gem into pkg/" fullCmd="build" taksId="build" /><RakeTask description="Remove any temporary products" fullCmd="clean" taksId="clean" /><RakeTask description="Remove any generated file" fullCmd="clobber" taksId="clobber" /><RakeTask description="Remove RDoc HTML files" fullCmd="clobber_rdoc" taksId="clobber_rdoc" /><RakeTask description="Start IRB with all runtime dependencies loaded" fullCmd="console[script]" taksId="console[script]" /><RakeGroup description="" fullCmd="" taksId="gemcutter"><RakeTask description="Release gem to Gemcutter" fullCmd="gemcutter:release" taksId="release" /></RakeGroup><RakeTask description="Generate and validate gemspec" fullCmd="gemspec" taksId="gemspec" /><RakeGroup description="" fullCmd="" taksId="gemspec"><RakeTask description="Display the gemspec for debugging purposes, as jeweler knows it (not from the filesystem)" fullCmd="gemspec:debug" taksId="debug" /><RakeTask description="Regenerate the gemspec on the filesystem" fullCmd="gemspec:generate" taksId="generate" /><RakeTask description="Regenerate and validate gemspec, and then commits and pushes to git" fullCmd="gemspec:release" taksId="release" /><RakeTask description="Validates the gemspec on the filesystem" fullCmd="gemspec:validate" taksId="validate" /></RakeGroup><RakeGroup description="" fullCmd="" taksId="git"><RakeTask description="Tag and push release to git" fullCmd="git:release" taksId="release" /></RakeGroup><RakeTask description="Build and install gem using `gem install`" fullCmd="install" taksId="install" /><RakeTask description="Build RDoc HTML files" fullCmd="rdoc" taksId="rdoc" /><RakeTask description="Release gem" fullCmd="release" taksId="release" /><RakeTask description="Rebuild RDoc HTML files" fullCmd="rerdoc" taksId="rerdoc" /><RakeTask description="Run tests" fullCmd="test" taksId="test" /><RakeTask description="Displays the current version" fullCmd="version" taksId="version" /><RakeGroup description="" fullCmd="" taksId="version"><RakeGroup description="" fullCmd="" taksId="bump"><RakeTask description="Bump the major version by 1" fullCmd="version:bump:major" taksId="major" /><RakeTask description="Bump the a minor version by 1" fullCmd="version:bump:minor" taksId="minor" /><RakeTask description="Bump the patch version by 1" fullCmd="version:bump:patch" taksId="patch" /></RakeGroup><RakeTask description="Writes out an explicit version" fullCmd="version:write" taksId="write" /></RakeGroup><RakeTask description="" fullCmd="console" taksId="console" /><RakeTask description="" fullCmd="default" taksId="default" /><RakeTask description="" fullCmd="gemspec_required" taksId="gemspec_required" /><RakeTask description="" fullCmd="rdoc/index.html" taksId="rdoc/index.html" /><RakeTask description="" fullCmd="version_required" taksId="version_required" /></RakeGroup></Settings>
|
data/.idea/encodings.xml
ADDED
data/.idea/misc.xml
ADDED
data/.idea/modules.xml
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<project version="4">
|
3
|
+
<component name="ProjectModuleManager">
|
4
|
+
<modules>
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/pivotal-tracker-api.iml" filepath="$PROJECT_DIR$/.idea/pivotal-tracker-api.iml" />
|
6
|
+
</modules>
|
7
|
+
</component>
|
8
|
+
</project>
|
9
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<module type="RUBY_MODULE" version="4">
|
3
|
+
<component name="FacetManager">
|
4
|
+
<facet type="gem" name="Gem">
|
5
|
+
<configuration>
|
6
|
+
<option name="GEM_APP_ROOT_PATH" value="$MODULE_DIR$" />
|
7
|
+
<option name="GEM_APP_TEST_PATH" value="$MODULE_DIR$/test" />
|
8
|
+
<option name="GEM_APP_LIB_PATH" value="$MODULE_DIR$/lib" />
|
9
|
+
</configuration>
|
10
|
+
</facet>
|
11
|
+
</component>
|
12
|
+
<component name="NewModuleRootManager">
|
13
|
+
<content url="file://$MODULE_DIR$">
|
14
|
+
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
15
|
+
</content>
|
16
|
+
<orderEntry type="inheritedJdk" />
|
17
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
18
|
+
<orderEntry type="library" scope="PROVIDED" name="activesupport (v4.0.0, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
19
|
+
<orderEntry type="library" scope="PROVIDED" name="addressable (v2.3.5, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
20
|
+
<orderEntry type="library" scope="PROVIDED" name="atomic (v1.1.14, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
21
|
+
<orderEntry type="library" scope="PROVIDED" name="builder (v3.2.2, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
22
|
+
<orderEntry type="library" scope="PROVIDED" name="bundler (v1.3.5, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
23
|
+
<orderEntry type="library" scope="PROVIDED" name="faraday (v0.8.8, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
24
|
+
<orderEntry type="library" scope="PROVIDED" name="git (v1.2.6, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
25
|
+
<orderEntry type="library" scope="PROVIDED" name="github_api (v0.10.1, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
26
|
+
<orderEntry type="library" scope="PROVIDED" name="hashie (v2.0.5, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
27
|
+
<orderEntry type="library" scope="PROVIDED" name="highline (v1.6.20, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
28
|
+
<orderEntry type="library" scope="PROVIDED" name="httpauth (v0.2.0, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
29
|
+
<orderEntry type="library" scope="PROVIDED" name="i18n (v0.6.5, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
30
|
+
<orderEntry type="library" scope="PROVIDED" name="jeweler (v1.8.8, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
31
|
+
<orderEntry type="library" scope="PROVIDED" name="json (v1.8.1, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
32
|
+
<orderEntry type="library" scope="PROVIDED" name="jwt (v0.1.8, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
33
|
+
<orderEntry type="library" scope="PROVIDED" name="mime-types (v1.25, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
34
|
+
<orderEntry type="library" scope="PROVIDED" name="minitest (v4.7.5, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
35
|
+
<orderEntry type="library" scope="PROVIDED" name="multi_json (v1.8.2, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
36
|
+
<orderEntry type="library" scope="PROVIDED" name="multi_xml (v0.5.5, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
37
|
+
<orderEntry type="library" scope="PROVIDED" name="multipart-post (v1.2.0, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
38
|
+
<orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.5.10, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
39
|
+
<orderEntry type="library" scope="PROVIDED" name="oauth2 (v0.9.2, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
40
|
+
<orderEntry type="library" scope="PROVIDED" name="rack (v1.5.2, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
41
|
+
<orderEntry type="library" scope="PROVIDED" name="rake (v10.1.0, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
42
|
+
<orderEntry type="library" scope="PROVIDED" name="rdoc (v3.12.2, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
43
|
+
<orderEntry type="library" scope="PROVIDED" name="rest-client (v1.6.7, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
44
|
+
<orderEntry type="library" scope="PROVIDED" name="shoulda (v3.5.0, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
45
|
+
<orderEntry type="library" scope="PROVIDED" name="shoulda-context (v1.1.5, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
46
|
+
<orderEntry type="library" scope="PROVIDED" name="shoulda-matchers (v2.4.0, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
47
|
+
<orderEntry type="library" scope="PROVIDED" name="simplecov (v0.7.1, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
48
|
+
<orderEntry type="library" scope="PROVIDED" name="simplecov-html (v0.7.1, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
49
|
+
<orderEntry type="library" scope="PROVIDED" name="thread_safe (v0.1.3, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
50
|
+
<orderEntry type="library" scope="PROVIDED" name="tzinfo (v0.3.38, RVM: ruby-2.0.0-p195) [gem]" level="application" />
|
51
|
+
</component>
|
52
|
+
</module>
|
53
|
+
|
data/.idea/vcs.xml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem 'activesupport', '>= 2.3.5'
|
5
|
+
gem 'rest-client', '~> 1.6.7'
|
6
|
+
|
7
|
+
# Add dependencies to develop your gem here.
|
8
|
+
# Include everything needed to run rake, tests, features, etc.
|
9
|
+
group :development do
|
10
|
+
gem 'shoulda', '>= 0'
|
11
|
+
gem 'rdoc', '~> 3.12'
|
12
|
+
gem 'bundler', '~> 1.0'
|
13
|
+
gem 'jeweler', '~> 1.8.7'
|
14
|
+
gem 'simplecov'
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (4.0.0)
|
5
|
+
i18n (~> 0.6, >= 0.6.4)
|
6
|
+
minitest (~> 4.2)
|
7
|
+
multi_json (~> 1.3)
|
8
|
+
thread_safe (~> 0.1)
|
9
|
+
tzinfo (~> 0.3.37)
|
10
|
+
addressable (2.3.5)
|
11
|
+
atomic (1.1.14)
|
12
|
+
builder (3.2.2)
|
13
|
+
faraday (0.8.8)
|
14
|
+
multipart-post (~> 1.2.0)
|
15
|
+
git (1.2.6)
|
16
|
+
github_api (0.10.1)
|
17
|
+
addressable
|
18
|
+
faraday (~> 0.8.1)
|
19
|
+
hashie (>= 1.2)
|
20
|
+
multi_json (~> 1.4)
|
21
|
+
nokogiri (~> 1.5.2)
|
22
|
+
oauth2
|
23
|
+
hashie (2.0.5)
|
24
|
+
highline (1.6.20)
|
25
|
+
httpauth (0.2.0)
|
26
|
+
i18n (0.6.5)
|
27
|
+
jeweler (1.8.8)
|
28
|
+
builder
|
29
|
+
bundler (~> 1.0)
|
30
|
+
git (>= 1.2.5)
|
31
|
+
github_api (= 0.10.1)
|
32
|
+
highline (>= 1.6.15)
|
33
|
+
nokogiri (= 1.5.10)
|
34
|
+
rake
|
35
|
+
rdoc
|
36
|
+
json (1.8.1)
|
37
|
+
jwt (0.1.8)
|
38
|
+
multi_json (>= 1.5)
|
39
|
+
mime-types (1.25)
|
40
|
+
minitest (4.7.5)
|
41
|
+
multi_json (1.8.2)
|
42
|
+
multi_xml (0.5.5)
|
43
|
+
multipart-post (1.2.0)
|
44
|
+
nokogiri (1.5.10)
|
45
|
+
oauth2 (0.9.2)
|
46
|
+
faraday (~> 0.8)
|
47
|
+
httpauth (~> 0.2)
|
48
|
+
jwt (~> 0.1.4)
|
49
|
+
multi_json (~> 1.0)
|
50
|
+
multi_xml (~> 0.5)
|
51
|
+
rack (~> 1.2)
|
52
|
+
rack (1.5.2)
|
53
|
+
rake (10.1.0)
|
54
|
+
rdoc (3.12.2)
|
55
|
+
json (~> 1.4)
|
56
|
+
rest-client (1.6.7)
|
57
|
+
mime-types (>= 1.16)
|
58
|
+
shoulda (3.5.0)
|
59
|
+
shoulda-context (~> 1.0, >= 1.0.1)
|
60
|
+
shoulda-matchers (>= 1.4.1, < 3.0)
|
61
|
+
shoulda-context (1.1.5)
|
62
|
+
shoulda-matchers (2.4.0)
|
63
|
+
activesupport (>= 3.0.0)
|
64
|
+
simplecov (0.7.1)
|
65
|
+
multi_json (~> 1.0)
|
66
|
+
simplecov-html (~> 0.7.1)
|
67
|
+
simplecov-html (0.7.1)
|
68
|
+
thread_safe (0.1.3)
|
69
|
+
atomic
|
70
|
+
tzinfo (0.3.38)
|
71
|
+
|
72
|
+
PLATFORMS
|
73
|
+
ruby
|
74
|
+
|
75
|
+
DEPENDENCIES
|
76
|
+
bundler (~> 1.0)
|
77
|
+
jeweler (~> 1.8.7)
|
78
|
+
rdoc (~> 3.12)
|
79
|
+
rest-client (~> 1.6.7)
|
80
|
+
shoulda
|
81
|
+
simplecov
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 jfox
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= pivotal-tracker-api
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to pivotal-tracker-api
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
9
|
+
* Fork the project.
|
10
|
+
* Start a feature/bugfix branch.
|
11
|
+
* Commit and push until you are happy with your contribution.
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2013 jfox. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
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-tracker-api"
|
18
|
+
gem.homepage = "http://github.com/atljeremy/pivotal-tracker-api"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{A ruby gem to communicate with the Picotal Tracker API v5}
|
21
|
+
gem.description = %Q{A ruby gem to communicate with the Picotal Tracker API v5}
|
22
|
+
gem.email = "atljeremy@me.com"
|
23
|
+
gem.authors = ["jfox"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
#require 'rcov/rcovtask'
|
36
|
+
#Rcov::RcovTask.new do |test|
|
37
|
+
# test.libs << 'test'
|
38
|
+
# test.pattern = 'test/**/test_*.rb'
|
39
|
+
# test.verbose = true
|
40
|
+
# test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
#end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rdoc/task'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "pivotal-tracker-api #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Scorer
|
2
|
+
class Activity
|
3
|
+
|
4
|
+
attr_accessor :project_id, :occurred_at, :highlight, :primary_resources, :changes, :guid, :kind, :performed_by_id,
|
5
|
+
:performed_by, :message, :project_version
|
6
|
+
|
7
|
+
def initialize(attributes={})
|
8
|
+
update_attributes(attributes)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.parse_json_activity(json_activity, project_id)
|
12
|
+
json_activity.each do |activity|
|
13
|
+
new({
|
14
|
+
project_id: project_id,
|
15
|
+
occurred_at: activity[:stories],
|
16
|
+
highlight: activity[:number],
|
17
|
+
primary_resources: activity[:team_strength],
|
18
|
+
changes: activity[:finish],
|
19
|
+
guid: activity[:kind],
|
20
|
+
kind: activity[:start],
|
21
|
+
performed_by_id: activity[:performed_by_id],
|
22
|
+
performed_by: parse_person(activity[:performed_by]),
|
23
|
+
message: activity[:message],
|
24
|
+
project_version: activity[:project_version]
|
25
|
+
})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def self.parse_person(json_person)
|
32
|
+
Scorer::Person.parse_json_person(json_person)
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_attributes(attrs)
|
36
|
+
attrs.each do |key, value|
|
37
|
+
self.send("#{key}=", value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Scorer
|
2
|
+
class Attachment
|
3
|
+
|
4
|
+
attr_accessor :status, :url, :filename, :id, :description, :uploaded_at, :uploaded_by
|
5
|
+
|
6
|
+
def initialize(attributes={})
|
7
|
+
update_attributes(attributes)
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def update_attributes(attrs)
|
13
|
+
attrs.each do |key, value|
|
14
|
+
self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Scorer
|
2
|
+
class Client
|
3
|
+
|
4
|
+
class NoToken < StandardError; end
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
attr_writer :token, :ssl_enabled
|
9
|
+
attr_reader :api_host, :api_projects_path
|
10
|
+
|
11
|
+
def get(path)
|
12
|
+
begin
|
13
|
+
set_response_headers(connection["#{api_version}#{path}"].get)
|
14
|
+
rescue
|
15
|
+
puts "Encountered Error in get: #{$!}"
|
16
|
+
clear_connections
|
17
|
+
set_response_headers(connection["#{api_version}#{path}"].get)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_with_caching(path)
|
22
|
+
begin
|
23
|
+
connection["#{api_version}#{path}"].get
|
24
|
+
rescue
|
25
|
+
puts "Encountered Error in get_with_caching: #{$!}"
|
26
|
+
clear_connections
|
27
|
+
connection["#{api_version}#{path}"].get
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def put(path, updates={})
|
32
|
+
set_response_headers(connection["#{api_version}#{path}"].put(updates))
|
33
|
+
end
|
34
|
+
|
35
|
+
def connection
|
36
|
+
raise NoToken if @token.to_s.empty?
|
37
|
+
@connections ||= {}
|
38
|
+
cached_connection? ? cached_connection : new_connection
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear_connections
|
42
|
+
@connections = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def ssl_enabled
|
46
|
+
@ssl_enabled || true
|
47
|
+
end
|
48
|
+
|
49
|
+
def token(username, password)
|
50
|
+
response = RestClient.get("#{api_ssl_host(username, password)}#{api_version}/me")
|
51
|
+
json = JSON.parse(response, {:symbolize_names => true})
|
52
|
+
@token = json[:api_token]
|
53
|
+
end
|
54
|
+
|
55
|
+
def api_ssl_host(user=nil, password=nil)
|
56
|
+
user_password = (user && password) ? "#{user}:#{password}@" : ''
|
57
|
+
"https://#{user_password}www.pivotaltracker.com"
|
58
|
+
end
|
59
|
+
|
60
|
+
def api_host
|
61
|
+
@api_host ||= 'http://www.pivotaltracker.com'
|
62
|
+
end
|
63
|
+
|
64
|
+
def api_projects_path
|
65
|
+
@api_projects_path ||= "#{ssl_enabled ? api_ssl_host : api_host}#{api_version}/projects"
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def set_response_headers(response)
|
71
|
+
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' # HTTP 1.1.
|
72
|
+
response.headers['Pragma'] = 'no-cache' # HTTP 1.0.
|
73
|
+
response.headers['Expires'] = '0' # Proxies.
|
74
|
+
response
|
75
|
+
end
|
76
|
+
|
77
|
+
def api_version
|
78
|
+
'/services/v5'
|
79
|
+
end
|
80
|
+
|
81
|
+
def cached_connection?
|
82
|
+
!@connections[@token].nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
def cached_connection
|
86
|
+
@connections[@token]
|
87
|
+
end
|
88
|
+
|
89
|
+
def new_connection
|
90
|
+
@connections[@token] = RestClient::Resource.new("#{ssl_enabled ? api_ssl_host : api_host}", :headers => {'X-TrackerToken' => @token, 'Content-Type' => 'application/json'})
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Scorer
|
2
|
+
class Comment
|
3
|
+
|
4
|
+
attr_accessor :story, :project_id, :story_id, :id, :text, :author, :created_at
|
5
|
+
|
6
|
+
def initialize(attributes={})
|
7
|
+
if attributes[:owner]
|
8
|
+
self.story = attributes.delete(:owner)
|
9
|
+
self.project_id = self.story.project_id
|
10
|
+
self.story_id = self.story.id
|
11
|
+
end
|
12
|
+
|
13
|
+
update_attributes(attributes)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def update_attributes(attrs)
|
19
|
+
attrs.each do |key, value|
|
20
|
+
self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Scorer
|
2
|
+
class Iteration
|
3
|
+
|
4
|
+
attr_accessor :project_id, :length, :planned, :stories, :story_ids, :number, :team_strength, :finish, :kind, :start
|
5
|
+
|
6
|
+
def initialize(attributes={})
|
7
|
+
update_attributes(attributes)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.parse_json_iteration(json_iteration)
|
11
|
+
new({
|
12
|
+
project_id: json_iteration[:project_id].to_i,
|
13
|
+
stories: parse_stories(json_iteration[:stories], json_iteration[:project_id].to_i),
|
14
|
+
story_ids: json_iteration[:story_ids],
|
15
|
+
number: json_iteration[:number],
|
16
|
+
team_strength: json_iteration[:team_strength],
|
17
|
+
finish: json_iteration[:finish],
|
18
|
+
kind: json_iteration[:kind],
|
19
|
+
start: json_iteration[:start]
|
20
|
+
})
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def self.parse_stories(stories, project_id)
|
26
|
+
story_ids = Array.new
|
27
|
+
stories.each { |story| story_ids << story[:id].to_i if !story[:id].nil? }
|
28
|
+
project = PivotalService.one_project(project_id)
|
29
|
+
PivotalService.stories(project, true, story_ids, Scorer::Story.fields)
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_attributes(attrs)
|
33
|
+
attrs.each do |key, value|
|
34
|
+
self.send("#{key}=", value )
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Scorer
|
2
|
+
class Person
|
3
|
+
|
4
|
+
attr_accessor :name, :id, :initials, :email, :username
|
5
|
+
|
6
|
+
def initialize(attributes={})
|
7
|
+
update_attributes(attributes)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.parse_json_person(json_person)
|
11
|
+
new({
|
12
|
+
id: json_person[:id].to_i,
|
13
|
+
name: json_person[:name],
|
14
|
+
initials: json_person[:initals],
|
15
|
+
email: json_person[:email],
|
16
|
+
username: json_person[:username]
|
17
|
+
})
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def update_attributes(attrs)
|
23
|
+
attrs.each do |key, value|
|
24
|
+
self.send("#{key}=", value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class PivotalService
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def set_token(token)
|
8
|
+
Scorer::Client.token = token
|
9
|
+
end
|
10
|
+
|
11
|
+
def all_projects(fields=[])
|
12
|
+
api_url = append_fields('/projects', fields)
|
13
|
+
response = Scorer::Client.get(api_url)
|
14
|
+
json_projects = JSON.parse(response, {:symbolize_names => true})
|
15
|
+
Scorer::Project.parse_json_projects(json_projects)
|
16
|
+
end
|
17
|
+
|
18
|
+
def one_project(id, fields=[])
|
19
|
+
return @project if !@project.nil? && @project.id == id
|
20
|
+
|
21
|
+
api_url = append_fields('/projects' + "/#{id}", fields)
|
22
|
+
response = Scorer::Client.get(api_url)
|
23
|
+
json_project = JSON.parse(response, {:symbolize_names => true})
|
24
|
+
@project = Scorer::Project.parse_json_project(json_project)
|
25
|
+
end
|
26
|
+
|
27
|
+
def activity(project_id, story_id=0, limit=20)
|
28
|
+
api_url = "/projects/#{project_id}/"
|
29
|
+
api_url = api_url + "stories/#{story_id}/" if story_id > 0
|
30
|
+
api_url = api_url + "activity?limit=#{limit}"
|
31
|
+
response = Scorer::Client.get(api_url)
|
32
|
+
json_activity = JSON.parse(response, {:symbolize_names => true})
|
33
|
+
Scorer::Activity.parse_json_activity(json_activity, project_id)
|
34
|
+
end
|
35
|
+
|
36
|
+
def iterations(project_id, scope, fields=[], limit=1, offset=1)
|
37
|
+
return @iteration if !@iteration.nil? && @iteration.project_id == project_id
|
38
|
+
|
39
|
+
api_url = "/projects/#{project_id}/iterations?"
|
40
|
+
if scope
|
41
|
+
if scope != 'current'
|
42
|
+
api_url = api_url + "&offset=#{offset}&limit=#{limit}"
|
43
|
+
end
|
44
|
+
api_url = api_url + "&scope=#{scope}"
|
45
|
+
end
|
46
|
+
api_url = append_fields(api_url, fields)
|
47
|
+
response = Scorer::Client.get_with_caching(api_url)
|
48
|
+
json_iterations = JSON.parse(response, {:symbolize_names => true})
|
49
|
+
@iteration = Scorer::Iteration.parse_json_iteration(json_iterations[0])
|
50
|
+
end
|
51
|
+
|
52
|
+
def all_stories(project_label, project, fields=[])
|
53
|
+
api_url = append_fields('/projects' + "/#{project.id}/stories", fields)
|
54
|
+
api_url = api_url + "&with_label=#{project_label}&with_state=unstarted&limit=100"
|
55
|
+
response = Scorer::Client.get(api_url)
|
56
|
+
json_stories = JSON.parse(response, {:symbolize_names => true})
|
57
|
+
Scorer::Story.parse_json_stories(json_stories, project.id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def one_story(project_label, project, fields=[])
|
61
|
+
api_url = append_fields('/projects' + "/#{project.id}/stories", fields)
|
62
|
+
api_url = api_url + "&with_label=#{project_label}&with_state=unstarted&limit=100"
|
63
|
+
response = Scorer::Client.get(api_url)
|
64
|
+
json_story = JSON.parse(response, {:symbolize_names => true})
|
65
|
+
Scorer::Story.parse_json_story(json_story, project.id)
|
66
|
+
end
|
67
|
+
|
68
|
+
def stories(project, should_cache, ids=[], fields=[])
|
69
|
+
@stories = Array.new
|
70
|
+
api_url = append_fields('/projects' + "/#{project.id}/stories", fields)
|
71
|
+
api_url = api_url.rindex('?fields=') ? "#{api_url}&filter=id%3A" : "#{api_url}?filter=id%3A"
|
72
|
+
if ids.size == 1
|
73
|
+
api_url = api_url + ids[0].to_s
|
74
|
+
if should_cache
|
75
|
+
response = Scorer::Client.get_with_caching(api_url)
|
76
|
+
else
|
77
|
+
response = Scorer::Client.get(api_url)
|
78
|
+
end
|
79
|
+
json_story = JSON.parse(response, {:symbolize_names => true})
|
80
|
+
@stories << Scorer::Story.parse_json_story(json_story[0], project.id)
|
81
|
+
else
|
82
|
+
story_ids = ''
|
83
|
+
ids.each do |id|
|
84
|
+
story_ids = "#{story_ids}#{id}%2C"
|
85
|
+
end
|
86
|
+
api_url = api_url + story_ids
|
87
|
+
if should_cache
|
88
|
+
response = Scorer::Client.get_with_caching(api_url)
|
89
|
+
else
|
90
|
+
response = Scorer::Client.get(api_url)
|
91
|
+
end
|
92
|
+
json_stories = JSON.parse(response, {:symbolize_names => true})
|
93
|
+
@stories = Scorer::Story.parse_json_stories(json_stories, project.id)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def update_story(story_id, project_id, updates={})
|
98
|
+
project = one_project(project_id, Scorer::Project.fields)
|
99
|
+
api_url = '/projects' + "/#{project.id}/stories/#{story_id}"
|
100
|
+
Scorer::Client.put(api_url, updates)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def append_fields(api_url, fields)
|
106
|
+
url = api_url
|
107
|
+
fields.each do |field|
|
108
|
+
url = url.rindex('?fields=') ? url + ",#{field}" : url + "?fields=#{field}"
|
109
|
+
end
|
110
|
+
url
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Scorer
|
2
|
+
class Project
|
3
|
+
|
4
|
+
attr_accessor :updated_at, :bugs_and_chores_are_estimatable, :enable_planned_mode, :public, :story_ids, :name,
|
5
|
+
:iteration_length, :start_time, :version, :label_ids, :description, :profile_content,
|
6
|
+
:iteration_override_numbers, :velocity_averaged_over, :current_velocity, :point_scale_is_custom,
|
7
|
+
:epic_ids, :membership_ids, :integration_ids, :created_at, :week_start_day, :time_zone,
|
8
|
+
:shown_iterations_start_time, :has_google_domain, :kind, :id, :point_scale, :current_iteration_number,
|
9
|
+
:start_date, :number_of_done_iterations_to_show, :account_id, :labels
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def fields
|
14
|
+
['description', 'labels', 'name']
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_json_projects(json_projects)
|
18
|
+
projects = Array.new
|
19
|
+
json_projects.each do |project|
|
20
|
+
projects << parse_json_project(project)
|
21
|
+
end
|
22
|
+
projects
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_json_project(project)
|
26
|
+
new({
|
27
|
+
id: project[:id].to_i,
|
28
|
+
name: project[:name],
|
29
|
+
week_start_day: project[:week_start_day],
|
30
|
+
point_scale: project[:point_scale],
|
31
|
+
labels: parse_labels(project[:labels]),
|
32
|
+
iteration_length: project[:iteration_length],
|
33
|
+
current_velocity: project[:current_velocity]
|
34
|
+
})
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_labels(labels)
|
38
|
+
parsed_labels = ''
|
39
|
+
labels.each do |label|
|
40
|
+
parsed_labels = parsed_labels + "#{label[:name]},"
|
41
|
+
end
|
42
|
+
parsed_labels
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(attributes={})
|
47
|
+
update_attributes(attributes)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_labels
|
51
|
+
self.labels.split(',')
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def update_attributes(attrs)
|
57
|
+
attrs.each do |key, value|
|
58
|
+
self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
module Scorer
|
2
|
+
class Story
|
3
|
+
|
4
|
+
attr_accessor :project_id, :follower_ids, :updated_at, :current_state, :name, :comment_ids, :url, :story_type,
|
5
|
+
:label_ids, :description, :requested_by_id, :planned_iteration_number, :external_id, :deadline,
|
6
|
+
:owned_by_id, :owned_by, :created_at, :estimate, :kind, :id, :task_ids, :integration_id, :accepted_at,
|
7
|
+
:comments, :tasks, :attachments, :requested_by, :labels, :notes, :started_at, :status
|
8
|
+
|
9
|
+
def initialize(attributes={})
|
10
|
+
update_attributes(attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.fields
|
14
|
+
['url', 'name', 'description', 'story_type',
|
15
|
+
'estimate', 'current_state', 'requested_by',
|
16
|
+
'owned_by', 'labels', 'integration_id',
|
17
|
+
'deadline', 'comments', 'tasks']
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse_json_story(json_story, project_id)
|
21
|
+
requested_by = json_story[:requested_by][:name] if !json_story[:requested_by].nil?
|
22
|
+
story_id = json_story[:id].to_i
|
23
|
+
estimate = json_story[:estimate] ? json_story[:estimate].to_i : -1
|
24
|
+
current_state = json_story[:current_state]
|
25
|
+
parsed_story = new({
|
26
|
+
id: story_id,
|
27
|
+
url: json_story[:url],
|
28
|
+
project_id: project_id,
|
29
|
+
name: json_story[:name],
|
30
|
+
description: json_story[:description],
|
31
|
+
story_type: json_story[:story_type],
|
32
|
+
estimate: estimate,
|
33
|
+
current_state: current_state,
|
34
|
+
requested_by: requested_by,
|
35
|
+
owned_by_id: json_story[:owned_by_id],
|
36
|
+
owned_by: json_story[:owned_by],
|
37
|
+
labels: parse_labels(json_story[:labels]),
|
38
|
+
integration_id: json_story[:integration_id],
|
39
|
+
deadline: json_story[:deadline]
|
40
|
+
})
|
41
|
+
|
42
|
+
parsed_story.comments = parse_notes(json_story[:comments], json_story)
|
43
|
+
parsed_story.tasks = parse_tasks(json_story[:tasks], json_story)
|
44
|
+
parsed_story.attachments = []
|
45
|
+
parsed_story
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.parse_json_stories(json_stories, project_id)
|
49
|
+
stories = Array.new
|
50
|
+
json_stories.each do |story|
|
51
|
+
stories << parse_json_story(story, project_id)
|
52
|
+
end
|
53
|
+
stories
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.parse_notes(notes, story)
|
57
|
+
comments = Array.new
|
58
|
+
if notes
|
59
|
+
notes.each do |note|
|
60
|
+
comments << Scorer::Comment.new({
|
61
|
+
id: note[:id].to_i,
|
62
|
+
text: note[:text],
|
63
|
+
author: note[:author],
|
64
|
+
created_at: DateTime.parse(note[:created_at].to_s).to_s,
|
65
|
+
story: story
|
66
|
+
})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
comments
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.parse_tasks(tasks, story)
|
73
|
+
parsed_tasks = Array.new
|
74
|
+
if tasks
|
75
|
+
tasks.each do |task|
|
76
|
+
parsed_tasks << Scorer::Task.new({
|
77
|
+
id: task[:id].to_i,
|
78
|
+
description: task[:description],
|
79
|
+
complete: task[:complete],
|
80
|
+
created_at: DateTime.parse(task[:created_at].to_s).to_s,
|
81
|
+
story: story
|
82
|
+
})
|
83
|
+
end
|
84
|
+
end
|
85
|
+
parsed_tasks
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.parse_attachments(attachments)
|
89
|
+
parsed_attachments = Array.new
|
90
|
+
if attachments
|
91
|
+
attachments.each do |file|
|
92
|
+
attachment = Scorer::Attachment.new
|
93
|
+
attachment.id = file[:id].to_i
|
94
|
+
attachment.filename = file[:filename]
|
95
|
+
attachment.description = file[:description]
|
96
|
+
attachment.uploaded_by = file[:uploaded_by]
|
97
|
+
attachment.uploaded_at = file[:uploaded_at]
|
98
|
+
attachment.url = file[:url]
|
99
|
+
attachment.status = file[:status]
|
100
|
+
parsed_attachments << attachment
|
101
|
+
end
|
102
|
+
end
|
103
|
+
parsed_attachments
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.parse_labels(labels)
|
107
|
+
parsed_labels = ''
|
108
|
+
labels.each do |label|
|
109
|
+
parsed_labels = parsed_labels + "#{label[:name]},"
|
110
|
+
end
|
111
|
+
parsed_labels
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.get_story_started_at(project_id, story_id)
|
115
|
+
events = Hash.new
|
116
|
+
current_started_at = nil
|
117
|
+
current_accepted_at = nil
|
118
|
+
activity = PivotalService.activity(project_id, story_id, 40)
|
119
|
+
activity.each do |event|
|
120
|
+
case event[:highlight]
|
121
|
+
when 'started'
|
122
|
+
started_at = event[:occurred_at]
|
123
|
+
if current_started_at.nil? || current_started_at < started_at || current_accepted_at === started_at
|
124
|
+
current_started_at = started_at
|
125
|
+
events[:started_at] = current_started_at
|
126
|
+
end
|
127
|
+
when 'accepted'
|
128
|
+
accepted_at = event[:occurred_at]
|
129
|
+
if current_accepted_at.nil? || current_accepted_at < accepted_at
|
130
|
+
current_accepted_at = accepted_at
|
131
|
+
events[:accepted_at] = current_accepted_at
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
events
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.get_story_status(event_times, points, current_state)
|
139
|
+
status = {status: 'ok', hours: -1}
|
140
|
+
if !event_times.nil? && !event_times[:started_at].nil? && points > -1 && current_state != 'unstarted'
|
141
|
+
|
142
|
+
# Times
|
143
|
+
started_at_time = Time.parse(event_times[:started_at])
|
144
|
+
|
145
|
+
# Due Dates
|
146
|
+
due_date = (points.to_i.business_hours.after(started_at_time)).to_datetime
|
147
|
+
almost_due_date = ((points - 1).to_i.business_hours.after(started_at_time)).to_datetime
|
148
|
+
|
149
|
+
if current_state == 'accepted' && !event_times[:accepted_at].nil?
|
150
|
+
accepted_at = event_times[:accepted_at]
|
151
|
+
hours = get_hours_between_times(started_at_time, Time.parse(accepted_at))
|
152
|
+
if accepted_at.to_datetime > due_date || hours >= points
|
153
|
+
status = {status: 'overdue', hours: hours}
|
154
|
+
elsif accepted_at >= almost_due_date || hours == (points - 1)
|
155
|
+
status = {status: 'almost_due', hours: hours}
|
156
|
+
else
|
157
|
+
status = {status: 'ok', hours: hours}
|
158
|
+
end
|
159
|
+
else
|
160
|
+
now = DateTime.now
|
161
|
+
hours = get_hours_between_times(started_at_time, Time.parse(now.to_s))
|
162
|
+
if now >= due_date || hours >= points
|
163
|
+
status = {status: 'overdue', hours: hours}
|
164
|
+
elsif now >= almost_due_date || hours == (points - 1)
|
165
|
+
status = {status: 'almost_due', hours: hours}
|
166
|
+
else
|
167
|
+
status = {status: 'ok', hours: hours}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
status
|
173
|
+
end
|
174
|
+
|
175
|
+
protected
|
176
|
+
|
177
|
+
def self.get_hours_between_times(time1, time2)
|
178
|
+
|
179
|
+
# Check to see if both times occurred outside of business hours.
|
180
|
+
# If so, calculate the total time in between each time
|
181
|
+
if Time::roll_forward(time1) == Time::roll_forward(time2)
|
182
|
+
return (((time2 - time1) / 60) / 60).round
|
183
|
+
end
|
184
|
+
|
185
|
+
((time1.business_time_until(time2) / 60) / 60).round
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.est_time_zone(time)
|
189
|
+
time.in_time_zone("Eastern Time (US & Canada)")
|
190
|
+
end
|
191
|
+
|
192
|
+
def update_attributes(attrs)
|
193
|
+
attrs.each do |key, value|
|
194
|
+
self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Scorer
|
2
|
+
class Task
|
3
|
+
|
4
|
+
attr_accessor :project_id, :story_id, :story, :id, :description, :position, :complete, :created_at
|
5
|
+
|
6
|
+
def initialize(attributes={})
|
7
|
+
if attributes[:owner]
|
8
|
+
self.story = attributes.delete(:owner)
|
9
|
+
self.project_id = self.story.project_id
|
10
|
+
self.story_id = self.story.id
|
11
|
+
end
|
12
|
+
|
13
|
+
update_attributes(attributes)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def update_attributes(attrs)
|
19
|
+
attrs.each do |key, value|
|
20
|
+
self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'client')
|
4
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'project')
|
5
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'attachment')
|
6
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'story')
|
7
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'task')
|
8
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'activity')
|
9
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'iteration')
|
10
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'comment')
|
11
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'person')
|
12
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker-api', 'pivotal_service')
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'pivotal-tracker-api'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pivotal-tracker-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- jfox
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rest-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.6.7
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.6.7
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: shoulda
|
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: rdoc
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.12'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: jeweler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.8.7
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.8.7
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: A ruby gem to communicate with the Picotal Tracker API v5
|
98
|
+
email: atljeremy@me.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files:
|
102
|
+
- LICENSE.txt
|
103
|
+
- README.rdoc
|
104
|
+
files:
|
105
|
+
- .document
|
106
|
+
- .idea/.name
|
107
|
+
- .idea/.rakeTasks
|
108
|
+
- .idea/encodings.xml
|
109
|
+
- .idea/misc.xml
|
110
|
+
- .idea/modules.xml
|
111
|
+
- .idea/pivotal-tracker-api.iml
|
112
|
+
- .idea/scopes/scope_settings.xml
|
113
|
+
- .idea/vcs.xml
|
114
|
+
- Gemfile
|
115
|
+
- Gemfile.lock
|
116
|
+
- LICENSE.txt
|
117
|
+
- README.rdoc
|
118
|
+
- Rakefile
|
119
|
+
- VERSION
|
120
|
+
- lib/pivotal-tracker-api.rb
|
121
|
+
- lib/pivotal-tracker-api/activity.rb
|
122
|
+
- lib/pivotal-tracker-api/attachment.rb
|
123
|
+
- lib/pivotal-tracker-api/client.rb
|
124
|
+
- lib/pivotal-tracker-api/comment.rb
|
125
|
+
- lib/pivotal-tracker-api/iteration.rb
|
126
|
+
- lib/pivotal-tracker-api/person.rb
|
127
|
+
- lib/pivotal-tracker-api/pivotal_service.rb
|
128
|
+
- lib/pivotal-tracker-api/project.rb
|
129
|
+
- lib/pivotal-tracker-api/story.rb
|
130
|
+
- lib/pivotal-tracker-api/task.rb
|
131
|
+
- test/helper.rb
|
132
|
+
- test/test_pivotal-tracker-api.rb
|
133
|
+
homepage: http://github.com/atljeremy/pivotal-tracker-api
|
134
|
+
licenses:
|
135
|
+
- MIT
|
136
|
+
metadata: {}
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - '>='
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
requirements: []
|
152
|
+
rubyforge_project:
|
153
|
+
rubygems_version: 2.0.8
|
154
|
+
signing_key:
|
155
|
+
specification_version: 4
|
156
|
+
summary: A ruby gem to communicate with the Picotal Tracker API v5
|
157
|
+
test_files: []
|