pivotal-tracker 0.0.9.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.
- data/.gitignore +7 -0
- data/Gemfile +16 -0
- data/LICENSE +20 -0
- data/README.rdoc +60 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/lib/pivotal-tracker/activity.rb +45 -0
- data/lib/pivotal-tracker/client.rb +34 -0
- data/lib/pivotal-tracker/extensions.rb +11 -0
- data/lib/pivotal-tracker/iteration.rb +20 -0
- data/lib/pivotal-tracker/membership.rb +20 -0
- data/lib/pivotal-tracker/note.rb +17 -0
- data/lib/pivotal-tracker/project.rb +42 -0
- data/lib/pivotal-tracker/proxy.rb +65 -0
- data/lib/pivotal-tracker/story.rb +87 -0
- data/lib/pivotal-tracker/task.rb +48 -0
- data/lib/pivotal-tracker.rb +40 -0
- data/lib/pivotal_tracker.rb +2 -0
- data/pivotal-tracker.gemspec +95 -0
- data/spec/fixtures/activity.xml +176 -0
- data/spec/fixtures/created_story.xml +14 -0
- data/spec/fixtures/memberships.xml +29 -0
- data/spec/fixtures/project.xml +42 -0
- data/spec/fixtures/project_activity.xml +170 -0
- data/spec/fixtures/projects.xml +396 -0
- data/spec/fixtures/stale_fish.yml +58 -0
- data/spec/fixtures/stories.xml +66 -0
- data/spec/fixtures/tasks.xml +24 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/stale_fish_fixtures.rb +43 -0
- data/spec/unit/pivotal-tracker/activity_spec.rb +23 -0
- data/spec/unit/pivotal-tracker/iteration_spec.rb +8 -0
- data/spec/unit/pivotal-tracker/membership_spec.rb +20 -0
- data/spec/unit/pivotal-tracker/project_spec.rb +47 -0
- data/spec/unit/pivotal-tracker/story_spec.rb +27 -0
- data/spec/unit/pivotal-tracker/task_spec.rb +21 -0
- metadata +162 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source :gemcutter
|
2
|
+
|
3
|
+
group :runtime do
|
4
|
+
gem 'rest-client', '~> 1.4.1'
|
5
|
+
gem 'happymapper', '>= 0.2.4'
|
6
|
+
gem 'builder'
|
7
|
+
gem 'nokogiri', '~> 1.4.1'
|
8
|
+
end
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
gem 'rspec', :require => 'spec'
|
12
|
+
gem 'rake'
|
13
|
+
gem 'bundler', '~> 0.9.5'
|
14
|
+
gem 'jeweler'
|
15
|
+
gem 'stale_fish', '~> 1.3.0'
|
16
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Justin Smestad, Josh Nichols
|
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,60 @@
|
|
1
|
+
= pivotal-tracker.rb
|
2
|
+
|
3
|
+
Ruby wrapper for Pivotal Tracker API, no frameworks required. Simply Ruby.
|
4
|
+
|
5
|
+
== Note
|
6
|
+
|
7
|
+
Version 0.0.8 and above are incompatible with previous versions.
|
8
|
+
|
9
|
+
== Features
|
10
|
+
|
11
|
+
* Compatible with Pivotal Tracker API version 3
|
12
|
+
* ActiveRecord-style Wrapper API
|
13
|
+
* Support for SSL protected repositories
|
14
|
+
|
15
|
+
== Overview
|
16
|
+
|
17
|
+
PivotalTracker::Client.token('myusername@email.com', 'secretpassword') # Automatically fetch API Token
|
18
|
+
PivotalTracker::Client.token = 'jkfduisj97823974j2kl24899234' # Manually set API Token
|
19
|
+
|
20
|
+
@projects = PivotalTracker::Project.all # return all projects
|
21
|
+
@a_project = PivotalTracker::Project.find(84739) # find project with a given ID
|
22
|
+
|
23
|
+
@a_project.stories.all # return all stories for "a_project"
|
24
|
+
@a_project.stories.all(:label => 'overdue', :story_type => ['bug', 'chore']) # return all stories that match the passed filters
|
25
|
+
@a_project.stories.find(847762630) # find story with a given ID
|
26
|
+
|
27
|
+
@a_project.stories.create(:name => 'My Story', :story_type => 'feature') # create a story for this project
|
28
|
+
|
29
|
+
# all tracker defined filters are allowed, as well as :limit & :offset for pagination
|
30
|
+
|
31
|
+
# The below are planned to be added to the final release:
|
32
|
+
|
33
|
+
@a_project.stories << PivotalTracker::Story.new(84739, :name => 'Ur Story') # same as above, useful for copying/cloning from proj
|
34
|
+
|
35
|
+
|
36
|
+
The API is based on the following this gist: http://gist.github.com/283120
|
37
|
+
|
38
|
+
== Getting Started
|
39
|
+
|
40
|
+
* Installing:
|
41
|
+
|
42
|
+
$ gem install pivotal-tracker
|
43
|
+
|
44
|
+
* Contributing (requires Bundler >= 0.9.7):
|
45
|
+
|
46
|
+
$ git clone git://github.com/jsmestad/pivotal-tracker
|
47
|
+
$ cd pivotal-tracker
|
48
|
+
$ bundle install
|
49
|
+
$ bundle exec rake
|
50
|
+
|
51
|
+
== Additional Information
|
52
|
+
|
53
|
+
Wiki: http://wiki.github.com/jsmestad/pivotal-tracker
|
54
|
+
Documentation: http://rdoc.info/projects/jsmestad/pivotal-tracker
|
55
|
+
|
56
|
+
== Contributers
|
57
|
+
|
58
|
+
* Justin Smestad (http://github.com/jsmestad)
|
59
|
+
* Josh Nichols (http://github.com/technicalpickles)
|
60
|
+
* Terence Lee (http://github.com/hone)
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "pivotal-tracker"
|
8
|
+
gem.summary = %Q{Ruby wrapper for the Pivotal Tracker API}
|
9
|
+
gem.email = "justin.smestad@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/jsmestad/pivotal-tracker"
|
11
|
+
gem.authors = ["Justin Smestad", "Josh Nichols", "Terence Lee"]
|
12
|
+
|
13
|
+
bundle = Bundler::Definition.from_gemfile('Gemfile')
|
14
|
+
bundle.dependencies.each do |dep|
|
15
|
+
next unless dep.groups.include?(:runtime)
|
16
|
+
gem.add_dependency(dep.name, dep.requirement.to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'spec/rake/spectask'
|
25
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
31
|
+
spec.libs << 'lib' << 'spec'
|
32
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
33
|
+
spec.rcov = true
|
34
|
+
end
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'yard'
|
40
|
+
YARD::Rake::YardocTask.new
|
41
|
+
rescue LoadError
|
42
|
+
task :yardoc do
|
43
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.9.1
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Activity
|
3
|
+
include HappyMapper
|
4
|
+
class << self
|
5
|
+
def all(project=nil, options={})
|
6
|
+
params = self.encode_options(options)
|
7
|
+
if project
|
8
|
+
parse(Client.connection["/projects/#{project.id}/activities#{params}"].get)
|
9
|
+
else
|
10
|
+
parse(Client.connection["/activities#{params}"].get)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def encode_options(options)
|
17
|
+
return nil if !options.is_a?(Hash) || options.empty?
|
18
|
+
|
19
|
+
options_string = []
|
20
|
+
options_string << "limit=#{options.delete(:limit)}" if options[:limit]
|
21
|
+
options_string << "newer_than_version=#{options.delete(:newer_than_version)}" if options[:newer_than_version]
|
22
|
+
|
23
|
+
if options[:occurred_since]
|
24
|
+
options_string << "occurred_since_date=#{options[:occurred_since].utc}"
|
25
|
+
elsif options[:occurred_since_date]
|
26
|
+
options_string << "occurred_since_date=#{options[:occurred_since]}"
|
27
|
+
end
|
28
|
+
|
29
|
+
return "?#{options_string.join('&')}"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
element :id, Integer
|
35
|
+
element :version, Integer
|
36
|
+
element :event_type, String
|
37
|
+
element :occurred_at, DateTime
|
38
|
+
element :author, String
|
39
|
+
element :project_id, Integer
|
40
|
+
element :description, String
|
41
|
+
|
42
|
+
has_many :stories, Story
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Client
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_writer :use_ssl, :token
|
6
|
+
|
7
|
+
def use_ssl
|
8
|
+
@use_ssl || false
|
9
|
+
end
|
10
|
+
|
11
|
+
def token(username, password, method='post')
|
12
|
+
response = if method == 'post'
|
13
|
+
RestClient.post 'https://www.pivotaltracker.com/services/v3/tokens/active', :username => username, :password => password
|
14
|
+
else
|
15
|
+
RestClient.get "https://#{username}:#{password}@www.pivotaltracker.com/services/v3/tokens/active"
|
16
|
+
end
|
17
|
+
@token ||= Nokogiri::XML(response.body).search('guid').inner_html
|
18
|
+
end
|
19
|
+
|
20
|
+
# this is your connection for the entire module
|
21
|
+
def connection(options={})
|
22
|
+
@connection ||= RestClient::Resource.new("#{protocol}://www.pivotaltracker.com/services/v3", :headers => {'X-TrackerToken' => @token, 'Content-Type' => 'application/xml'})
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def protocol
|
28
|
+
use_ssl ? 'https' : 'http'
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Happymapper patch for RestClient API Change (response => response.body)
|
2
|
+
|
3
|
+
module HappyMapper
|
4
|
+
module ClassMethods
|
5
|
+
alias_method :orig_parse, :parse
|
6
|
+
def parse(xml, options={})
|
7
|
+
xml = xml.to_s if xml.is_a?(RestClient::Response)
|
8
|
+
orig_parse(xml, options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Iteration
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all(project, options={})
|
7
|
+
params = PivotalTracker.encode_options(options)
|
8
|
+
parse(Client.connection["/projects/#{project.id}/iterations#{params}"].get)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
element :id, Integer
|
13
|
+
element :number, Integer
|
14
|
+
element :start, DateTime
|
15
|
+
element :finish, DateTime
|
16
|
+
|
17
|
+
has_many :stories, Story
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Membership
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all(project, options={})
|
7
|
+
parse(Client.connection["/projects/#{project.id}/memberships"].get)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
element :id, Integer
|
12
|
+
element :role, String
|
13
|
+
|
14
|
+
# Flattened Attributes from <person>...</person>
|
15
|
+
element :name, String, :deep => true
|
16
|
+
element :email, String, :deep => true
|
17
|
+
element :initials, String, :deep => true
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Note
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# def all(story, options={})
|
7
|
+
# parse(Client.connection["/projects/#{project_id}/stories/#{story_id}/notes"].get)
|
8
|
+
# end
|
9
|
+
end
|
10
|
+
|
11
|
+
element :id, Integer
|
12
|
+
element :text, String
|
13
|
+
element :author, String
|
14
|
+
element :noted_at, DateTime
|
15
|
+
# has_one :story, Story
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Project
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all
|
7
|
+
@found = parse(Client.connection['/projects'].get)
|
8
|
+
end
|
9
|
+
|
10
|
+
def find(id)
|
11
|
+
if @found
|
12
|
+
@found.detect { |document| document.id == id }
|
13
|
+
else
|
14
|
+
parse(Client.connection["/projects/#{id}"].get)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
element :id, Integer
|
20
|
+
element :name, String
|
21
|
+
element :iteration_length, Integer
|
22
|
+
element :week_start_day, String
|
23
|
+
element :point_scale, String
|
24
|
+
|
25
|
+
def activities
|
26
|
+
@activities ||= Proxy.new(self, Activity)
|
27
|
+
end
|
28
|
+
|
29
|
+
def iterations
|
30
|
+
@iterations ||= Proxy.new(self, Iteration)
|
31
|
+
end
|
32
|
+
|
33
|
+
def stories
|
34
|
+
@stories ||= Proxy.new(self, Story)
|
35
|
+
end
|
36
|
+
|
37
|
+
def memberships
|
38
|
+
@memberships ||= Proxy.new(self, Membership)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class BasicObject #:nodoc:
|
2
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|instance_eval|proxy_|^object_id$)/ }
|
3
|
+
end unless defined?(BasicObject)
|
4
|
+
|
5
|
+
module PivotalTracker
|
6
|
+
class Proxy < BasicObject
|
7
|
+
|
8
|
+
def initialize(owner, target)
|
9
|
+
@owner = owner
|
10
|
+
@target = target
|
11
|
+
@opts = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def all(options={})
|
15
|
+
proxy_found(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(param, options={})
|
19
|
+
return all(options) if param == :all
|
20
|
+
return proxy_found(options).detect { |document| document.id == param }
|
21
|
+
end
|
22
|
+
|
23
|
+
def <<(*objects)
|
24
|
+
objects.flatten.each do |object|
|
25
|
+
if obj = object.create
|
26
|
+
return obj
|
27
|
+
else
|
28
|
+
return object
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create(args)
|
34
|
+
object = @target.new(args.merge({:owner => @owner}))
|
35
|
+
if obj = object.create
|
36
|
+
return obj
|
37
|
+
else
|
38
|
+
return object
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def proxy_found(options)
|
45
|
+
# Check to see if options have changed
|
46
|
+
if @opts == options
|
47
|
+
@found ||= load_found(options)
|
48
|
+
else
|
49
|
+
load_found(options)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def method_missing(method, *args, &block)
|
56
|
+
@target.send(method, *args, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_found(options)
|
60
|
+
@opts = options
|
61
|
+
@target.all(@owner, @opts)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Story
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all(project, options={})
|
7
|
+
params = PivotalTracker.encode_options(options)
|
8
|
+
stories = parse(Client.connection["/projects/#{project.id}/stories#{params}"].get)
|
9
|
+
stories.each { |s| s.project_id = project.id }
|
10
|
+
return stories
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :project_id
|
15
|
+
|
16
|
+
element :id, Integer
|
17
|
+
element :story_type, String
|
18
|
+
element :url, String
|
19
|
+
element :estimate, Integer
|
20
|
+
element :current_state, String
|
21
|
+
element :name, String
|
22
|
+
element :requested_by, String
|
23
|
+
element :owned_by, String
|
24
|
+
element :created_at, DateTime
|
25
|
+
element :accepted_at, DateTime
|
26
|
+
element :labels, String
|
27
|
+
element :description, String
|
28
|
+
element :jira_id, Integer
|
29
|
+
element :jira_url, String
|
30
|
+
|
31
|
+
def initialize(attributes={})
|
32
|
+
self.project_id = attributes.delete(:owner).id if attributes[:owner]
|
33
|
+
|
34
|
+
update_attributes(attributes)
|
35
|
+
end
|
36
|
+
|
37
|
+
def create
|
38
|
+
return self if project_id.nil?
|
39
|
+
response = Client.connection["/projects/#{project_id}/stories"].post(self.to_xml, :content_type => 'application/xml')
|
40
|
+
return Story.parse(response)
|
41
|
+
end
|
42
|
+
|
43
|
+
def update(attrs={})
|
44
|
+
update_attributes(attrs)
|
45
|
+
response = Client.connection["/projects/#{project_id}/stories/#{id}"].put(self.to_xml, :content_type => 'application/xml')
|
46
|
+
return Story.parse(response)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete
|
50
|
+
Client.connection["/projects/#{project_id}/stories/#{id}"].delete
|
51
|
+
end
|
52
|
+
|
53
|
+
def tasks
|
54
|
+
@tasks ||= Proxy.new(self, Task)
|
55
|
+
end
|
56
|
+
|
57
|
+
def project=(proj_id)
|
58
|
+
self.project_id = proj_id
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def to_xml
|
64
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
65
|
+
xml.story {
|
66
|
+
xml.name "#{name}"
|
67
|
+
xml.story_type "#{story_type}"
|
68
|
+
xml.estimate "#{estimate}"
|
69
|
+
xml.current_state "#{current_state}"
|
70
|
+
xml.requested_by "#{requested_by}"
|
71
|
+
xml.owned_by "#{owned_by}"
|
72
|
+
xml.labels "#{labels}"
|
73
|
+
xml.description "#{description}"
|
74
|
+
# xml.jira_id "#{jira_id}"
|
75
|
+
# xml.jira_url "#{jira_url}"
|
76
|
+
}
|
77
|
+
end
|
78
|
+
return builder.to_xml
|
79
|
+
end
|
80
|
+
|
81
|
+
def update_attributes(attrs)
|
82
|
+
attrs.each do |key, value|
|
83
|
+
self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Task
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all(story, options={})
|
7
|
+
tasks = parse(Client.connection["/projects/#{story.project_id}/stories/#{story.id}/tasks"].get)
|
8
|
+
tasks.each { |t| t.project_id, t.story_id = story.project_id, story.id }
|
9
|
+
return tasks
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :project_id, :story_id
|
14
|
+
|
15
|
+
element :id, Integer
|
16
|
+
element :description, String
|
17
|
+
element :position, Integer
|
18
|
+
element :complete, Boolean
|
19
|
+
element :created_at, DateTime
|
20
|
+
|
21
|
+
def create
|
22
|
+
response = Client.connection["/projects/#{project_id}/stories/#{story_id}/tasks"].post(self.to_xml, :content_type => 'application/xml')
|
23
|
+
return Task.parse(response)
|
24
|
+
end
|
25
|
+
|
26
|
+
def update
|
27
|
+
response = Client.connection["/projects/#{project_id}/stories/#{story_id}/tasks/#{id}"].put(self.to_xml, :content_type => 'application/xml')
|
28
|
+
return Task.parse(response)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete
|
32
|
+
Client.connection["/projects/#{project_id}/stories/#{story_id}/tasks/#{id}"].delete
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def to_xml
|
38
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
39
|
+
xml.task {
|
40
|
+
xml.description "#{description}"
|
41
|
+
# xml.position "#{position}"
|
42
|
+
xml.complete "#{complete}"
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'happymapper'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
|
7
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'extensions')
|
8
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'proxy')
|
9
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'client')
|
10
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'project')
|
11
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'story')
|
12
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'task')
|
13
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'membership')
|
14
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'activity')
|
15
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'iteration')
|
16
|
+
require File.join(File.dirname(__FILE__), 'pivotal-tracker', 'note')
|
17
|
+
|
18
|
+
module PivotalTracker
|
19
|
+
|
20
|
+
# define error types
|
21
|
+
class ProjectNotSpecified < StandardError; end
|
22
|
+
|
23
|
+
def self.encode_options(options)
|
24
|
+
return nil if !options.is_a?(Hash) || options.empty?
|
25
|
+
|
26
|
+
options_string = []
|
27
|
+
options_string << "limit=#{options.delete(:limit)}" if options[:limit]
|
28
|
+
options_string << "offset=#{options.delete(:offset)}" if options[:offset]
|
29
|
+
|
30
|
+
filters = []
|
31
|
+
options.each do |key, value|
|
32
|
+
values = value.is_a?(Array) ? value.map {|x| CGI.escape(x) }.join(',') : CGI.escape(value)
|
33
|
+
filters << "#{key}%3A#{values}" # %3A => :
|
34
|
+
end
|
35
|
+
options_string << "filter=#{filters.join('%20')}" unless filters.empty? # %20 => &
|
36
|
+
|
37
|
+
return "?#{options_string.join('&')}"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|