napcs-pivotalrecord 0.0.1 → 0.0.3
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/README.rdoc +52 -2
- data/Rakefile +1 -1
- data/lib/pivotal/base.rb +158 -37
- data/lib/pivotal/collection_proxy.rb +66 -0
- data/lib/pivotal/iteration.rb +10 -11
- data/lib/pivotal/project.rb +5 -6
- data/lib/pivotal/story.rb +2 -20
- data/lib/pivotal.rb +9 -5
- data/spec/pivotal/base_spec.rb +39 -0
- data/spec/pivotal/iteration_spec.rb +15 -2
- data/spec/pivotal/project_spec.rb +5 -0
- data/spec/pivotal/story_spec.rb +85 -7
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -2,9 +2,49 @@
|
|
2
2
|
|
3
3
|
Provides a simple interface to PivotalTracker using syntax similar to ActiveRecord.
|
4
4
|
|
5
|
-
@projects = Pivotal::Project.find
|
5
|
+
@projects = Pivotal::Project.find :all
|
6
|
+
@project = Pivotal::Project.find 24
|
7
|
+
|
8
|
+
There's some basic association support built in.
|
9
|
+
|
6
10
|
@stories = @projects.first.stories
|
11
|
+
@iterations = @projects.first.iterations
|
12
|
+
|
13
|
+
|
14
|
+
==Working with Stories
|
15
|
+
|
16
|
+
Getting all stories for a project
|
17
|
+
|
18
|
+
@project = Pivotal::Project.find 24
|
19
|
+
@stories = @project.stories
|
20
|
+
|
21
|
+
@stories = Pivotal::Story.find_all_by_project_id 24
|
22
|
+
|
23
|
+
Getting a specific story for a project
|
24
|
+
|
25
|
+
@story = Pivotal::Story.find_by_project_id_and_id 24, 14
|
26
|
+
|
27
|
+
or
|
28
|
+
|
29
|
+
@story = Pivotal::Story.find 14, :project_id => 24
|
7
30
|
|
31
|
+
Getting a story from a project by name
|
32
|
+
@project = Pivotal::Project.find(246)
|
33
|
+
p.stories.find {|s| s.name = "Deal with web thumbnails"}
|
34
|
+
|
35
|
+
|
36
|
+
==Filtering
|
37
|
+
|
38
|
+
Getting all features for a project
|
39
|
+
|
40
|
+
Pivotal::Story.find :all, :project_id => 4860, :filter => {:type => "feature"}
|
41
|
+
|
42
|
+
Filters can be <tt>bug</tt>, <tt>release</tt>, <tt>feature</tt>.
|
43
|
+
|
44
|
+
===Limits
|
45
|
+
|
46
|
+
Pivotal::Story.find :all, :project_id => 4860, :filter => {:type => "feature"}, :limit => 2
|
47
|
+
|
8
48
|
== Configuring
|
9
49
|
|
10
50
|
Require the library in your project and then configure it with your API key.
|
@@ -13,14 +53,24 @@ Require the library in your project and then configure it with your API key.
|
|
13
53
|
Pivotal::Configuration.options[:api_key] = "asdfagjasd"
|
14
54
|
|
15
55
|
|
56
|
+
|
16
57
|
==Known Issues
|
17
58
|
|
18
59
|
This is extremely rudimentary and not meant for any sort of production use.
|
19
|
-
There is no write support yet
|
60
|
+
There is no write support yet.
|
61
|
+
|
20
62
|
|
21
63
|
|
22
64
|
== Changelog
|
23
65
|
|
66
|
+
===0.0.3
|
67
|
+
* Refactored finders
|
68
|
+
* Added CollectionProxy to allow story filtering
|
69
|
+
* Added finders for locating individual projects and stories
|
70
|
+
* refactored models to reduce duplicated code
|
71
|
+
* preserved original hash
|
72
|
+
===0.0.2
|
73
|
+
* Fixed requires
|
24
74
|
===0.0.1
|
25
75
|
* Basic record retrieval from PivotalTracker.
|
26
76
|
|
data/Rakefile
CHANGED
@@ -22,7 +22,7 @@ spec = Gem::Specification.new do |s|
|
|
22
22
|
|
23
23
|
# Change these as appropriate
|
24
24
|
s.name = "pivotalrecord"
|
25
|
-
s.version = "0.0.
|
25
|
+
s.version = "0.0.3"
|
26
26
|
s.summary = "Simple ActiveRecord-style interface for PivotalTracker"
|
27
27
|
s.author = "Brian Hogan"
|
28
28
|
s.email = "brianhogan@naopcs.com"
|
data/lib/pivotal/base.rb
CHANGED
@@ -6,17 +6,32 @@ module Pivotal
|
|
6
6
|
self.send("#{key}=", value) if self.respond_to?("#{key}=")
|
7
7
|
end
|
8
8
|
end
|
9
|
+
|
9
10
|
|
10
11
|
class << self
|
11
12
|
|
12
13
|
# parses the collection from Pivotal. Invokes the class's parse_result method
|
13
14
|
# which the class should implement.
|
14
|
-
def parse_results(results)
|
15
|
+
def parse_results(results, options)
|
15
16
|
return [] if results[self.resource].nil?
|
16
17
|
results[self.resource].collect do |item|
|
17
18
|
klass = self.name.constantize
|
18
|
-
klass.parse_result(item)
|
19
|
+
klass.parse_result(item, options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_result(data, options)
|
24
|
+
klass = self.name.constantize
|
25
|
+
attributes = {}
|
26
|
+
self.fields.each do |field|
|
27
|
+
attributes[field.to_sym] = get_content_for(data[field.to_s])
|
19
28
|
end
|
29
|
+
attributes[:original_hash] = data
|
30
|
+
result = klass.new attributes
|
31
|
+
result.project_id = options[:project_id] if result.respond_to?("project_id") && !options[:project_id].blank?
|
32
|
+
result.story_id = options[:story_id] if result.respond_to?("story_id") && !options[:story_id].blank?
|
33
|
+
|
34
|
+
result
|
20
35
|
end
|
21
36
|
|
22
37
|
|
@@ -56,6 +71,7 @@ module Pivotal
|
|
56
71
|
def resource
|
57
72
|
name.split('::').last.downcase
|
58
73
|
end
|
74
|
+
|
59
75
|
|
60
76
|
# Declares the relationship between the models.
|
61
77
|
#
|
@@ -63,12 +79,15 @@ module Pivotal
|
|
63
79
|
#
|
64
80
|
# Creates a <tt>stories</tt> method to retrive the related records.
|
65
81
|
def has_many(things)
|
66
|
-
|
82
|
+
parent_id = "#{resource}_id"
|
67
83
|
object = things.to_s.classify
|
84
|
+
|
85
|
+
|
68
86
|
class_eval <<-EOF
|
69
87
|
|
70
|
-
def #{things}
|
71
|
-
|
88
|
+
def #{things}(options ={})
|
89
|
+
options.merge!(:#{parent_id} => id)
|
90
|
+
@#{things} = Pivotal::#{object}.find_all(options)
|
72
91
|
end
|
73
92
|
|
74
93
|
EOF
|
@@ -77,21 +96,49 @@ module Pivotal
|
|
77
96
|
# Creates a reverse association, making it easy to grab
|
78
97
|
# the parent object.
|
79
98
|
def belongs_to(parent)
|
99
|
+
|
80
100
|
object = parent.to_s.classify
|
81
101
|
parent_id = "#{parent.to_s.downcase}_id"
|
102
|
+
|
103
|
+
if parent.to_s == "project"
|
104
|
+
attr_accessor parent_id.to_sym
|
105
|
+
else
|
106
|
+
attr_accessor parent_id.to_sym, :project_id
|
107
|
+
end
|
108
|
+
|
82
109
|
class_eval <<-EOF
|
83
|
-
|
110
|
+
|
84
111
|
def #{parent.to_s}
|
85
|
-
Pivotal::#{object}.find_by_id(#{parent_id})
|
112
|
+
@#{parent.to_s} = Pivotal::#{object}.find_by_id(#{parent_id})
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def self.find_all_by_#{parent.to_s}_id(id, options={})
|
117
|
+
options.merge!(:#{parent_id} => id)
|
118
|
+
find_all(options)
|
86
119
|
end
|
87
120
|
|
88
|
-
def self.
|
89
|
-
|
121
|
+
def self.find_by_#{parent.to_s}_id_and_id(parent_id, id, options = {})
|
122
|
+
options.merge!(:#{parent_id} => parent_id)
|
123
|
+
find_by_id(id, options)
|
90
124
|
end
|
125
|
+
|
126
|
+
|
91
127
|
|
92
128
|
EOF
|
93
129
|
end
|
94
130
|
|
131
|
+
def set_fields(*fields)
|
132
|
+
attr_accessor :id, :original_hash
|
133
|
+
fields.each {|field| attr_accessor field }
|
134
|
+
@fields = fields
|
135
|
+
@fields << :id
|
136
|
+
end
|
137
|
+
|
138
|
+
def fields
|
139
|
+
@fields
|
140
|
+
end
|
141
|
+
|
95
142
|
# Retrives the token from the configuration
|
96
143
|
def token
|
97
144
|
@@token = Pivotal::Configuration.options[:api_key]
|
@@ -108,46 +155,120 @@ module Pivotal
|
|
108
155
|
@@url = Pivotal::Configuration.options[:url] || "http://www.pivotaltracker.com/services/v2"
|
109
156
|
end
|
110
157
|
|
111
|
-
#
|
112
|
-
def
|
158
|
+
# Convenience method for finding all objects. Options are supported
|
159
|
+
def all(options = {})
|
160
|
+
self.find :all, options
|
161
|
+
end
|
162
|
+
|
163
|
+
# Convenience method for finding the first object. Options are supported
|
164
|
+
def first(options = {})
|
165
|
+
self.find :first, options
|
166
|
+
end
|
167
|
+
|
168
|
+
# Find records. See find_by_id for options.
|
169
|
+
#
|
170
|
+
# Pivotal::Project.find :all
|
171
|
+
# Pivotal::Project.find :first
|
172
|
+
# Pivotal::Project.find 2552
|
173
|
+
def find(type, options = {})
|
113
174
|
if type == :all
|
114
|
-
find_all
|
175
|
+
find_all options
|
176
|
+
elsif type == :first
|
177
|
+
find_one options.merge(:limit => 1)
|
115
178
|
else
|
116
|
-
find_by_id(type)
|
179
|
+
find_by_id(type, options)
|
117
180
|
end
|
118
181
|
end
|
119
182
|
|
120
|
-
# find a specific record by id
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
results = RestClient.get query
|
135
|
-
results = XmlSimple.xml_in(results.to_s)
|
136
|
-
parse_results(results)
|
183
|
+
# find a specific record by id.
|
184
|
+
# Options:
|
185
|
+
# * <tt>:filter</tt> - a hash for finding records by label and type.
|
186
|
+
#
|
187
|
+
# :filter => {:label => "Needs approval", :type => "feature"}
|
188
|
+
# * <tt>:limit</tt> - limit the number of records that come back.
|
189
|
+
# * <tt>:offest</tt> - for pagination
|
190
|
+
#
|
191
|
+
# Additonally, you may need to provide a hierarchy if you're referencing a Story or an Iteration.
|
192
|
+
#
|
193
|
+
# Pivotal::Story.find_by_id 25, :project_id => 2534
|
194
|
+
|
195
|
+
def find_by_id(id, options = {})
|
196
|
+
get_result(id, options)
|
137
197
|
end
|
138
|
-
|
198
|
+
|
139
199
|
# find all records for the given object.
|
140
|
-
def find_all
|
141
|
-
|
142
|
-
|
200
|
+
def find_all(options ={})
|
201
|
+
get_results(options)
|
202
|
+
end
|
203
|
+
|
204
|
+
def find_one(options ={})
|
205
|
+
# get_results is going to return a proxy. I am only expecting one record.
|
206
|
+
get_results(options).first
|
207
|
+
end
|
208
|
+
|
209
|
+
def base_path(options)
|
210
|
+
path = "projects"
|
211
|
+
path << "/#{options[:project_id]}/stories" if options[:project_id]
|
212
|
+
path << "/stories/#{options[:story_id]}/tasks" if options[:story_id]
|
213
|
+
path
|
214
|
+
end
|
215
|
+
|
216
|
+
def get_results(options={})
|
217
|
+
path = base_path(options)
|
218
|
+
opts = parse_options(options)
|
219
|
+
query ="#{self.url}/#{path}#{opts}"
|
220
|
+
puts "query : #{query}" #if self.debugging?
|
143
221
|
results = RestClient.get query
|
144
|
-
puts results.inspect if self.debugging?
|
145
222
|
results = XmlSimple.xml_in(results.to_s)
|
146
|
-
parse_results(results)
|
223
|
+
results = parse_results(results, options)
|
224
|
+
CollectionProxy.new(self.name.constantize, results)
|
225
|
+
|
147
226
|
end
|
148
|
-
|
227
|
+
|
228
|
+
def get_result(id, options ={})
|
229
|
+
path = base_path(options)
|
230
|
+
opts = parse_options(options)
|
231
|
+
query = "#{self.url}/#{path}/#{id}#{opts}"
|
232
|
+
puts "query : #{query}" #if self.debugging?
|
233
|
+
results = RestClient.get query
|
234
|
+
result = XmlSimple.xml_in(results.to_s)
|
235
|
+
parse_result(result, options)
|
236
|
+
end
|
237
|
+
|
238
|
+
def parse_options(options)
|
239
|
+
if options[:iteration].blank?
|
240
|
+
opts = ["?token=#{self.token}"]
|
241
|
+
else
|
242
|
+
opts = ["#{options[:iteration].to_s}?token=#{self.token}"]
|
243
|
+
end
|
244
|
+
|
245
|
+
if options[:limit]
|
246
|
+
opts << "limit=#{options[:limit]}"
|
247
|
+
end
|
248
|
+
|
249
|
+
if options[:offset]
|
250
|
+
opts << "offset=#{options[:offset]}"
|
251
|
+
end
|
252
|
+
|
253
|
+
if options[:filter]
|
254
|
+
opts << parse_filter(options[:filter])
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
return opts.join("&")
|
259
|
+
|
260
|
+
end
|
261
|
+
|
149
262
|
|
263
|
+
def parse_filter(opts)
|
264
|
+
opts = opts.map{|key, value| "#{key}:\"#{value}\""}.join " "
|
265
|
+
opts = URI.escape(opts, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
266
|
+
opts = "filter=#{opts}"
|
267
|
+
|
268
|
+
end
|
269
|
+
|
150
270
|
|
271
|
+
|
151
272
|
|
152
273
|
end
|
153
274
|
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Pivotal
|
2
|
+
|
3
|
+
module ProjectFinders
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
module IterationFinders
|
8
|
+
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
module StoryFinders
|
13
|
+
|
14
|
+
def accepted
|
15
|
+
find_all_by_current_state "accepted"
|
16
|
+
end
|
17
|
+
|
18
|
+
def bugs
|
19
|
+
find_all_by_story_type "bug"
|
20
|
+
end
|
21
|
+
|
22
|
+
def features
|
23
|
+
find_all_by_story_type "feature"
|
24
|
+
end
|
25
|
+
|
26
|
+
def chores
|
27
|
+
find_all_by_story_type "chore"
|
28
|
+
end
|
29
|
+
|
30
|
+
def releases
|
31
|
+
find_all_by_story_type "releoase"
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_all_by_story_type(type)
|
35
|
+
Pivotal::CollectionProxy.new Story, @records.select{|s| s.story_type == type.to_s}
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_all_by_current_state(current_state)
|
39
|
+
Pivotal::CollectionProxy.new Story, @records.select{|s| s.current_state == current_state.to_s}
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class CollectionProxy
|
45
|
+
def initialize(source, records)
|
46
|
+
@source = source
|
47
|
+
@records = records
|
48
|
+
self.send :extend, "#{source}Finders".constantize
|
49
|
+
end
|
50
|
+
|
51
|
+
def inspect
|
52
|
+
@records.inspect
|
53
|
+
end
|
54
|
+
|
55
|
+
def all
|
56
|
+
@records
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(name, *args, &block)
|
60
|
+
@records.send(name, *args, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/pivotal/iteration.rb
CHANGED
@@ -4,18 +4,17 @@ module Pivotal
|
|
4
4
|
# An iteration maps to a Pivotal Project and represents the sprint.
|
5
5
|
class Iteration < Pivotal::Base
|
6
6
|
|
7
|
-
belongs_to :project
|
7
|
+
belongs_to :project
|
8
8
|
|
9
|
-
|
9
|
+
set_fields :start, :finish, :number
|
10
10
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
11
|
+
def stories
|
12
|
+
@stories ||= self.original_hash["stories"].nil? ? [] : Pivotal::Story.parse_results(original_hash["stories"].first)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def self.backlog
|
17
|
+
|
18
|
+
end
|
20
19
|
end
|
21
20
|
end
|
data/lib/pivotal/project.rb
CHANGED
@@ -4,16 +4,15 @@ module Pivotal
|
|
4
4
|
# and to get its stories.
|
5
5
|
class Project < Pivotal::Base
|
6
6
|
|
7
|
-
|
7
|
+
set_fields :name, :original_hash
|
8
8
|
|
9
9
|
has_many :stories
|
10
|
-
|
10
|
+
has_many :iterations
|
11
11
|
|
12
|
-
def self.parse_result(project)
|
13
|
-
Pivotal::Project.new( :id => get_content_for(project["id"]),
|
14
|
-
:name => get_content_for(project["name"]) )
|
15
|
-
end
|
16
12
|
|
13
|
+
def backlog()
|
14
|
+
Pivotal::Iteration.backlog_for(self.id)
|
15
|
+
end
|
17
16
|
|
18
17
|
end
|
19
18
|
end
|
data/lib/pivotal/story.rb
CHANGED
@@ -1,30 +1,12 @@
|
|
1
1
|
module Pivotal
|
2
2
|
class Story < Pivotal::Base
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
set_fields :name, :description, :url, :estimate, :story_type, :accepted_at
|
7
5
|
|
8
6
|
belongs_to :project
|
9
7
|
|
10
|
-
|
8
|
+
|
11
9
|
|
12
10
|
|
13
|
-
def self.parse_result(story)
|
14
|
-
Pivotal::Story.new(
|
15
|
-
:id => get_content_for(story["id"]),
|
16
|
-
:name => get_content_for(story["name"]),
|
17
|
-
:description => get_content_for(story["description"]),
|
18
|
-
:url => get_content_for(story["url"]),
|
19
|
-
:estimate => get_content_for(story["estimate"]),
|
20
|
-
:story_type => get_content_for(story["story_type"]),
|
21
|
-
:accepted_at => get_content_for(story["accepted_at"]),
|
22
|
-
:current_state => get_content_for(story["current_state"])
|
23
|
-
)
|
24
|
-
end
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
11
|
end
|
30
12
|
end
|
data/lib/pivotal.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
1
3
|
require 'rubygems'
|
2
4
|
require 'activesupport'
|
3
5
|
require 'rest_client'
|
4
6
|
require 'xmlsimple'
|
7
|
+
require 'uri'
|
5
8
|
|
6
|
-
require '
|
7
|
-
require '
|
8
|
-
require '
|
9
|
-
require '
|
10
|
-
require '
|
9
|
+
require 'pivotal/base'
|
10
|
+
require 'pivotal/configuration'
|
11
|
+
require 'pivotal/project'
|
12
|
+
require 'pivotal/story'
|
13
|
+
require 'pivotal/iteration'
|
14
|
+
require 'pivotal/collection_proxy'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../spec_helper")
|
2
|
+
|
3
|
+
describe Pivotal::Base do
|
4
|
+
before(:each) do
|
5
|
+
@service = "http://www.pivotaltracker.com/services/v2"
|
6
|
+
@token = "abcd"
|
7
|
+
Pivotal::Configuration.options[:api_key] = @token
|
8
|
+
end
|
9
|
+
describe "with options" do
|
10
|
+
it "should return just the token" do
|
11
|
+
Pivotal::Base.parse_options(Hash.new).should == "?token=#{@token}"
|
12
|
+
end
|
13
|
+
it "should return backlog with token" do
|
14
|
+
Pivotal::Base.parse_options(:iteration => :backlog).should == "backlog?token=#{@token}"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return backlog with token and limit" do
|
18
|
+
Pivotal::Base.parse_options(:limit => 10, :iteration => :backlog).should == "backlog?token=#{@token}&limit=10"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return backlog with token and limit and offset" do
|
22
|
+
Pivotal::Base.parse_options(:limit => 10, :offset => 2, :iteration => :backlog).should == "backlog?token=#{@token}&limit=10&offset=2"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return with token and limit and offset but no backlog" do
|
26
|
+
Pivotal::Base.parse_options(:limit => 10, :offset => 2).should == "?token=#{@token}&limit=10&offset=2"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should create a filter for features that need feedback" do
|
30
|
+
Pivotal::Base.parse_filter(:type => "feature", :label => "needs feedback").should == "filter=type%3A%22feature%22%20label%3A%22needs%20feedback%22"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should return all stories that are features, limited by 5" do
|
34
|
+
Pivotal::Base.parse_options(:limit => 10, :filter => {:type => "feature", :label => "needs feedback"}).should == "?token=#{@token}&limit=10&filter=type%3A%22feature%22%20label%3A%22needs%20feedback%22"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -7,6 +7,7 @@ describe Pivotal::Iteration do
|
|
7
7
|
Pivotal::Configuration.options[:api_key] = @token
|
8
8
|
FakeWeb.clean_registry
|
9
9
|
FakeWeb.allow_net_connect = false
|
10
|
+
@project = Pivotal::Project.new("id" => "4860", :name => "FeelMySkills")
|
10
11
|
end
|
11
12
|
|
12
13
|
|
@@ -343,15 +344,27 @@ describe Pivotal::Iteration do
|
|
343
344
|
</iterations>}
|
344
345
|
url = "#{@service}/projects/4860/iterations?token=#{@token}"
|
345
346
|
FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
|
346
|
-
@project = Pivotal::Iteration.new("id" => "4860", :name => "FeelMySkills")
|
347
347
|
end
|
348
348
|
it "should get iterations" do
|
349
349
|
iterations = Pivotal::Iteration.find_all_by_project_id(@project.id)
|
350
350
|
iterations.each{|i| i.should be_a(Pivotal::Iteration)}
|
351
351
|
iterations.detect{|i| i.number == 28}.should be_true
|
352
|
-
|
353
352
|
end
|
353
|
+
|
354
|
+
|
354
355
|
end
|
355
356
|
|
357
|
+
describe "with an iteration" do
|
358
|
+
before(:each) do
|
359
|
+
url = "#{@service}/projects/4860/iterations?token=#{@token}"
|
360
|
+
FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
|
361
|
+
@project = Pivotal::Iteration.new("id" => "4860", :name => "FeelMySkills")
|
362
|
+
end
|
363
|
+
it "should get an iteration by project and id" do
|
364
|
+
iterations = Pivotal::Iteration.find_all_by_project_id(@project.id)
|
365
|
+
iterations.each{|i| i.should be_a(Pivotal::Iteration)}
|
366
|
+
iterations.detect{|i| i.number == 28}.should be_true
|
367
|
+
end
|
368
|
+
end
|
356
369
|
|
357
370
|
end
|
@@ -39,6 +39,11 @@ describe Pivotal::Project do
|
|
39
39
|
p = Pivotal::Project.find_by_id(4860)
|
40
40
|
p.name.should == "FeelMySkills"
|
41
41
|
end
|
42
|
+
it "should still have the raw attributes" do
|
43
|
+
p = Pivotal::Project.find_by_id(4860)
|
44
|
+
p.original_hash.should == {"name"=>["FeelMySkills"], "iteration_length"=>[{"type"=>"integer", "content"=>"1"}], "week_start_day"=>["Monday"], "point_scale"=>["0,1,2,3"], "id"=>["4860"]}
|
45
|
+
|
46
|
+
end
|
42
47
|
|
43
48
|
describe "stories" do
|
44
49
|
before(:each) do
|
data/spec/pivotal/story_spec.rb
CHANGED
@@ -15,6 +15,31 @@ describe Pivotal::Story do
|
|
15
15
|
FakeWeb.allow_net_connect = true
|
16
16
|
end
|
17
17
|
|
18
|
+
describe "With a story" do
|
19
|
+
before(:each) do
|
20
|
+
xml=%Q{<?xml version="1.0" encoding="UTF-8"?>
|
21
|
+
<story>
|
22
|
+
<id type="integer">300970</id>
|
23
|
+
<story_type>release</story_type>
|
24
|
+
<url>http://www.pivotaltracker.com/story/show/300970</url>
|
25
|
+
<current_state>accepted</current_state>
|
26
|
+
<description></description>
|
27
|
+
<name>rel_1-1-5</name>
|
28
|
+
<requested_by>Brian Hogan</requested_by>
|
29
|
+
<created_at type="datetime">2008/12/04 03:00:57 UTC</created_at>
|
30
|
+
<accepted_at type="datetime">2009/01/14 06:32:15 UTC</accepted_at>
|
31
|
+
<deadline type="datetime">2008/12/12 20:00:00 UTC</deadline>
|
32
|
+
</story>
|
33
|
+
}
|
34
|
+
url = "#{@service}/projects/4860/stories/300970?token=#{@token}"
|
35
|
+
FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
|
36
|
+
@project = Pivotal::Project.new("id" => "4860", :name => "FeelMySkills")
|
37
|
+
end
|
38
|
+
it "should load the story" do
|
39
|
+
@story = Pivotal::Story.find 300970, :project_id => "4860"
|
40
|
+
@story.id.should == 300970
|
41
|
+
end
|
42
|
+
end
|
18
43
|
|
19
44
|
describe "with many stories" do
|
20
45
|
before(:each) do
|
@@ -213,7 +238,7 @@ describe Pivotal::Story do
|
|
213
238
|
</story>
|
214
239
|
<story>
|
215
240
|
<id type="integer">300472</id>
|
216
|
-
<story_type>
|
241
|
+
<story_type>bug</story_type>
|
217
242
|
<url>http://www.pivotaltracker.com/story/show/300472</url>
|
218
243
|
<estimate type="integer">1</estimate>
|
219
244
|
<current_state>unscheduled</current_state>
|
@@ -227,16 +252,69 @@ describe Pivotal::Story do
|
|
227
252
|
FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
|
228
253
|
@project = Pivotal::Project.new("id" => "4860", :name => "FeelMySkills")
|
229
254
|
end
|
230
|
-
it "should get stories" do
|
255
|
+
it "should get stories using find_all_by_project_id" do
|
231
256
|
stories = Pivotal::Story.find_all_by_project_id(@project.id)
|
232
257
|
stories.each{|s| s.should be_a(Pivotal::Story)}
|
233
|
-
stories.detect{|s| s.name == "Deal with web thumbnails"}
|
234
|
-
stories.detect{|s| s.name == "Add Recommendations"}
|
235
|
-
stories.detect{|s| s.name == "Implement Exception Notification plugin"}
|
236
|
-
stories.detect{|s| s.name == "Implement Pro account subscription"}
|
237
|
-
stories.detect{|s| s.name == "iPhone interface for viewing profiles"}
|
258
|
+
stories.detect{|s| s.name == "Deal with web thumbnails"}.should_not be_nil
|
259
|
+
stories.detect{|s| s.name == "Add Recommendations"}.should_not be_nil
|
260
|
+
stories.detect{|s| s.name == "Implement Exception Notification plugin"}.should_not be_nil
|
261
|
+
stories.detect{|s| s.name == "Implement Pro account subscription"}.should_not be_nil
|
262
|
+
stories.detect{|s| s.name == "iPhone interface for viewing profiles"}.should_not be_nil
|
263
|
+
|
264
|
+
end
|
265
|
+
it "should get stories using the project_id option" do
|
266
|
+
stories = Pivotal::Story.find :all, :project_id => @project.id
|
267
|
+
|
268
|
+
stories.each{|s| s.should be_a(Pivotal::Story)}
|
269
|
+
stories.detect{|s| s.name == "Deal with web thumbnails"}.should_not be_nil
|
270
|
+
stories.detect{|s| s.name == "Add Recommendations"}.should_not be_nil
|
271
|
+
stories.detect{|s| s.name == "Implement Exception Notification plugin"}.should_not be_nil
|
272
|
+
stories.detect{|s| s.name == "Implement Pro account subscription"}.should_not be_nil
|
273
|
+
stories.detect{|s| s.name == "iPhone interface for viewing profiles"}.should_not be_nil
|
274
|
+
|
275
|
+
end
|
276
|
+
|
277
|
+
describe "with filter and limit" do
|
278
|
+
before(:each) do
|
279
|
+
xml = %Q{<stories type="array" count="1" limit="1" total="10" filter="type:'feature'">
|
280
|
+
<story>
|
281
|
+
<id type="integer">300939</id>
|
282
|
+
<story_type>feature</story_type>
|
283
|
+
<url>http://www.pivotaltracker.com/story/show/300939</url>
|
284
|
+
<estimate type="integer">1</estimate>
|
285
|
+
<current_state>started</current_state>
|
286
|
+
<description>Images need to be linked and modalbox needs testing in other browsers besides FF and Safari.</description>
|
287
|
+
<name>Finish modalbox implementation</name>
|
288
|
+
<requested_by>Brian Hogan</requested_by>
|
289
|
+
<owned_by>Brian Hogan</owned_by>
|
290
|
+
<created_at type="datetime">2008/12/04 02:33:33 UTC</created_at>
|
291
|
+
</story>
|
292
|
+
</stories>}
|
293
|
+
|
294
|
+
url = "#{@service}/projects/4860/stories?token=#{@token}&limit=1&filter=type%3A%22feature%22"
|
295
|
+
FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
|
296
|
+
@project = Pivotal::Project.new("id" => "4860", :name => "FeelMySkills")
|
297
|
+
end
|
298
|
+
|
299
|
+
it "should get the story via limit and filter" do
|
300
|
+
@stories = Pivotal::Story.find :all, :project_id => 4860, :filter => {:type => "feature"}, :limit => 1
|
301
|
+
@stories.length.should ==1
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
it "should get bugs through the proxy" do
|
307
|
+
stories = Pivotal::Story.find :all, :project_id => @project.id
|
308
|
+
stories = stories.bugs
|
309
|
+
stories.each{|s| s.should be_a(Pivotal::Story)}
|
310
|
+
stories.detect{|s| s.name == "Deal with web thumbnails"}.should_not be_nil
|
311
|
+
stories.detect{|s| s.name == "Add Recommendations"}.should be_nil
|
312
|
+
stories.detect{|s| s.name == "Implement Exception Notification plugin"}.should be_nil
|
313
|
+
stories.detect{|s| s.name == "Implement Pro account subscription"}.should be_nil
|
314
|
+
stories.detect{|s| s.name == "iPhone interface for viewing profiles"}.should be_nil
|
238
315
|
|
239
316
|
end
|
317
|
+
|
240
318
|
end
|
241
319
|
|
242
320
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: napcs-pivotalrecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Hogan
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-08-
|
12
|
+
date: 2009-08-13 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -63,6 +63,7 @@ extra_rdoc_files:
|
|
63
63
|
files:
|
64
64
|
- Rakefile
|
65
65
|
- spec/pivotal
|
66
|
+
- spec/pivotal/base_spec.rb
|
66
67
|
- spec/pivotal/iteration_spec.rb
|
67
68
|
- spec/pivotal/project_spec.rb
|
68
69
|
- spec/pivotal/story_spec.rb
|
@@ -70,6 +71,7 @@ files:
|
|
70
71
|
- spec/spec_helper.rb
|
71
72
|
- lib/pivotal
|
72
73
|
- lib/pivotal/base.rb
|
74
|
+
- lib/pivotal/collection_proxy.rb
|
73
75
|
- lib/pivotal/configuration.rb
|
74
76
|
- lib/pivotal/iteration.rb
|
75
77
|
- lib/pivotal/project.rb
|