pivotal-tracker-fox 0.5.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +17 -0
  5. data/Gemfile.lock +57 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +82 -0
  8. data/Rakefile +46 -0
  9. data/VERSION +1 -0
  10. data/lib/pivotal-tracker.rb +40 -0
  11. data/lib/pivotal-tracker/activity.rb +45 -0
  12. data/lib/pivotal-tracker/attachment.rb +16 -0
  13. data/lib/pivotal-tracker/client.rb +81 -0
  14. data/lib/pivotal-tracker/extensions.rb +11 -0
  15. data/lib/pivotal-tracker/iteration.rb +40 -0
  16. data/lib/pivotal-tracker/membership.rb +20 -0
  17. data/lib/pivotal-tracker/note.rb +58 -0
  18. data/lib/pivotal-tracker/project.rb +94 -0
  19. data/lib/pivotal-tracker/proxy.rb +64 -0
  20. data/lib/pivotal-tracker/story.rb +157 -0
  21. data/lib/pivotal-tracker/task.rb +71 -0
  22. data/lib/pivotal-tracker/validation.rb +69 -0
  23. data/lib/pivotal_tracker.rb +2 -0
  24. data/pivotal-tracker.gemspec +131 -0
  25. data/spec/fixtures/activity.xml +177 -0
  26. data/spec/fixtures/bugs.xml +279 -0
  27. data/spec/fixtures/created_note.xml +14 -0
  28. data/spec/fixtures/created_story.xml +14 -0
  29. data/spec/fixtures/features.xml +293 -0
  30. data/spec/fixtures/iterations_all.xml +243 -0
  31. data/spec/fixtures/iterations_backlog.xml +166 -0
  32. data/spec/fixtures/iterations_current.xml +48 -0
  33. data/spec/fixtures/iterations_current_backlog.xml +211 -0
  34. data/spec/fixtures/iterations_done.xml +34 -0
  35. data/spec/fixtures/memberships.xml +42 -0
  36. data/spec/fixtures/notes.xml +33 -0
  37. data/spec/fixtures/project.xml +53 -0
  38. data/spec/fixtures/project_activity.xml +177 -0
  39. data/spec/fixtures/projects.xml +107 -0
  40. data/spec/fixtures/stale_fish.yml +167 -0
  41. data/spec/fixtures/stories.xml +293 -0
  42. data/spec/fixtures/story-4459994.xml +37 -0
  43. data/spec/fixtures/story-4460038.xml +46 -0
  44. data/spec/fixtures/story-4460598.xml +32 -0
  45. data/spec/fixtures/story-4473735.xml +48 -0
  46. data/spec/fixtures/tasks.xml +24 -0
  47. data/spec/fixtures/update_tasks.xml +8 -0
  48. data/spec/pivotal-tracker/activity_spec.rb +32 -0
  49. data/spec/pivotal-tracker/attachment_spec.rb +62 -0
  50. data/spec/pivotal-tracker/client_spec.rb +148 -0
  51. data/spec/pivotal-tracker/iteration_spec.rb +76 -0
  52. data/spec/pivotal-tracker/membership_spec.rb +20 -0
  53. data/spec/pivotal-tracker/note_spec.rb +61 -0
  54. data/spec/pivotal-tracker/project_spec.rb +98 -0
  55. data/spec/pivotal-tracker/story_spec.rb +247 -0
  56. data/spec/pivotal-tracker/task_spec.rb +33 -0
  57. data/spec/spec.opts +1 -0
  58. data/spec/spec_helper.rb +46 -0
  59. data/spec/support/stale_fish_fixtures.rb +71 -0
  60. metadata +287 -0
@@ -0,0 +1,40 @@
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
+
11
+ def current(project)
12
+ array = parse(Client.connection["projects/#{project.id}/iterations/current"].get)
13
+ array.first if array
14
+ end
15
+
16
+ def done(project, options={})
17
+ params = PivotalTracker.encode_options(options)
18
+ parse(Client.connection["/projects/#{project.id}/iterations/done#{params}"].get)
19
+ end
20
+
21
+ def backlog(project, options={})
22
+ params = PivotalTracker.encode_options(options)
23
+ parse(Client.connection["/projects/#{project.id}/iterations/backlog#{params}"].get)
24
+ end
25
+
26
+ def current_backlog(project, options={})
27
+ params = PivotalTracker.encode_options(options)
28
+ parse(Client.connection["/projects/#{project.id}/iterations/current_backlog#{params}"].get)
29
+ end
30
+ end
31
+
32
+ element :id, Integer
33
+ element :number, Integer
34
+ element :start, DateTime
35
+ element :finish, DateTime
36
+ element :team_strength, Float
37
+ has_many :stories, Story
38
+
39
+ end
40
+ 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,58 @@
1
+ module PivotalTracker
2
+ class Note
3
+ include HappyMapper
4
+
5
+ class << self
6
+ def all(story, options={})
7
+ notes = parse(Client.connection["/projects/#{story.project_id}/stories/#{story.id}/notes"].get)
8
+ notes.each { |n| n.project_id, n.story_id = story.project_id, story.id }
9
+ return notes
10
+ end
11
+ end
12
+
13
+ attr_accessor :project_id, :story_id
14
+
15
+ element :id, Integer
16
+ element :text, String
17
+ element :author, String
18
+ element :noted_at, DateTime
19
+ has_one :story, Story
20
+
21
+ def initialize(attributes={})
22
+ if attributes[:owner]
23
+ self.story = attributes.delete(:owner)
24
+ self.project_id = self.story.project_id
25
+ self.story_id = self.story.id
26
+ end
27
+
28
+ update_attributes(attributes)
29
+ end
30
+
31
+ def create
32
+ response = Client.connection["/projects/#{project_id}/stories/#{story_id}/notes"].post(self.to_xml, :content_type => 'application/xml')
33
+ return Note.parse(response)
34
+ end
35
+
36
+ # Pivotal Tracker API doesn't seem to support updating or deleting notes at this time.
37
+
38
+ protected
39
+
40
+ def to_xml
41
+ builder = Nokogiri::XML::Builder.new do |xml|
42
+ xml.note {
43
+ #xml.author "#{author}"
44
+ xml.text_ "#{text}"
45
+ xml.noted_at "#{noted_at}"
46
+ }
47
+ end
48
+ return builder.to_xml
49
+ end
50
+
51
+ def update_attributes(attrs)
52
+ attrs.each do |key, value|
53
+ self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,94 @@
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 :account, String
22
+ element :week_start_day, String
23
+ element :point_scale, String
24
+ element :labels, String
25
+ element :velocity_scheme, String
26
+ element :iteration_length, Integer
27
+ element :initial_velocity, Integer
28
+ element :current_velocity, Integer
29
+ element :last_activity_at, DateTime
30
+ element :use_https, Boolean
31
+ element :first_iteration_start_time, DateTime
32
+ element :current_iteration_number, Integer
33
+
34
+ def initialize(attributes={})
35
+ update_attributes(attributes)
36
+ end
37
+
38
+ def create
39
+ response = Client.connection["/projects"].post(self.to_xml, :content_type => 'application/xml')
40
+ project = Project.parse(response)
41
+ return project
42
+ end
43
+
44
+ def activities
45
+ @activities ||= Proxy.new(self, Activity)
46
+ end
47
+
48
+ def iterations
49
+ @iterations ||= Proxy.new(self, Iteration)
50
+ end
51
+
52
+ def stories
53
+ @stories ||= Proxy.new(self, Story)
54
+ end
55
+
56
+ def memberships
57
+ @memberships ||= Proxy.new(self, Membership)
58
+ end
59
+
60
+ def iteration(group)
61
+ case group.to_sym
62
+ when :done then Iteration.done(self)
63
+ when :current then Iteration.current(self)
64
+ when :backlog then Iteration.backlog(self)
65
+ when :current_backlog then Iteration.current_backlog(self)
66
+ else
67
+ raise ArgumentError, "Invalid group. Use :done, :current, :backlog or :current_backlog instead."
68
+ end
69
+ end
70
+
71
+ protected
72
+
73
+ def to_xml
74
+ builder = Nokogiri::XML::Builder.new do |xml|
75
+ xml.project {
76
+ xml.name "#{name}"
77
+ xml.iteration_length.integer "#{iteration_length}" unless iteration_length.nil?
78
+ xml.point_scale "#{point_scale}" unless point_scale.nil?
79
+ }
80
+ end
81
+ return builder.to_xml
82
+ end
83
+
84
+ def update_attributes(attrs)
85
+ attrs.each do |key, value|
86
+ self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
87
+ end
88
+ end
89
+
90
+ end
91
+ class Project
92
+ include Validation
93
+ end
94
+ end
@@ -0,0 +1,64 @@
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 = {}
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 @target.find(param, @owner.id) if @target.respond_to?("find")
21
+ return proxy_found(options).detect { |document| document.id == param }
22
+ end
23
+
24
+ def <<(*objects)
25
+ objects.flatten.each do |object|
26
+ if obj = object.create
27
+ return obj
28
+ else
29
+ return object
30
+ end
31
+ end
32
+ end
33
+
34
+ def create(args = {})
35
+ object = @target.new(args.merge({:owner => @owner}))
36
+ if obj = object.create
37
+ return obj
38
+ else
39
+ return object
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ def proxy_found(options)
46
+ if @found.nil? or @opts != options
47
+ @opts = options
48
+ @found = load_found()
49
+ end
50
+ @found
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()
60
+ @target.all(@owner, @opts)
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,157 @@
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
+
13
+ def find(param, project_id)
14
+ begin
15
+ story = parse(Client.connection["/projects/#{project_id}/stories/#{param}"].get)
16
+ story.project_id = project_id
17
+ rescue RestClient::ExceptionWithResponse
18
+ story = nil
19
+ end
20
+ return story
21
+ end
22
+
23
+ def parse_stories(xml_string)
24
+ parse(xml_string)
25
+ end
26
+ end
27
+
28
+ tag "story"
29
+
30
+ element :id, Integer
31
+ element :url, String
32
+ element :created_at, DateTime
33
+ element :accepted_at, DateTime
34
+ element :project_id, Integer
35
+
36
+ element :name, String
37
+ element :description, String
38
+ element :story_type, String
39
+ element :estimate, Integer
40
+ element :current_state, String
41
+ element :requested_by, String
42
+ element :owned_by, String
43
+ element :labels, String
44
+ element :jira_id, Integer
45
+ element :jira_url, String
46
+ element :other_id, String
47
+ element :integration_id, Integer
48
+ element :deadline, DateTime # Only available for Release stories
49
+
50
+ has_many :attachments, Attachment, :tag => 'attachments', :xpath => '//attachments'
51
+
52
+ def initialize(attributes={})
53
+ if attributes[:owner]
54
+ self.project_id = attributes.delete(:owner).id
55
+ end
56
+ update_attributes(attributes)
57
+ end
58
+
59
+ def create
60
+ return self if project_id.nil?
61
+ response = Client.connection["/projects/#{project_id}/stories"].post(self.to_xml, :content_type => 'application/xml')
62
+ new_story = Story.parse(response)
63
+ new_story.project_id = project_id
64
+ return new_story
65
+ end
66
+
67
+ def update(attrs={})
68
+ update_attributes(attrs)
69
+ response = Client.connection["/projects/#{project_id}/stories/#{id}"].put(self.to_xml, :content_type => 'application/xml')
70
+ return Story.parse(response)
71
+ end
72
+
73
+ def move(position, story)
74
+ raise ArgumentError, "Can only move :before or :after" unless [:before, :after].include? position
75
+ Story.parse(Client.connection["/projects/#{project_id}/stories/#{id}/moves?move\[move\]=#{position}&move\[target\]=#{story.id}"].post(''))
76
+ end
77
+
78
+ def delete
79
+ Client.connection["/projects/#{project_id}/stories/#{id}"].delete
80
+ end
81
+
82
+ def notes
83
+ @notes ||= Proxy.new(self, Note)
84
+ end
85
+
86
+ def tasks
87
+ @tasks ||= Proxy.new(self, Task)
88
+ end
89
+
90
+ def upload_attachment(filename)
91
+ Attachment.parse(Client.connection["/projects/#{project_id}/stories/#{id}/attachments"].post(:Filedata => File.new(filename)))
92
+ end
93
+
94
+ def move_to_project(new_project)
95
+ move = true
96
+ old_project_id = self.project_id
97
+ target_project = -1
98
+ case new_project.class.to_s
99
+ when 'PivotalTracker::Story'
100
+ target_project = new_project.project_id
101
+ when 'PivotalTracker::Project'
102
+ target_project = new_project.id
103
+ when 'String'
104
+ target_project = new_project.to_i
105
+ when 'Fixnum', 'Integer'
106
+ target_project = new_project
107
+ else
108
+ move = false
109
+ end
110
+ if move
111
+ move_builder = Nokogiri::XML::Builder.new do |story|
112
+ story.story {
113
+ story.project_id "#{target_project}"
114
+ }
115
+ end
116
+ Story.parse(Client.connection["/projects/#{old_project_id}/stories/#{id}"].put(move_builder.to_xml, :content_type => 'application/xml'))
117
+ end
118
+ end
119
+
120
+ protected
121
+
122
+ def to_xml
123
+ builder = Nokogiri::XML::Builder.new do |xml|
124
+ xml.story {
125
+ xml.name "#{name}"
126
+ xml.description "#{description}"
127
+ xml.story_type "#{story_type}"
128
+ xml.estimate "#{estimate}"
129
+ xml.current_state "#{current_state}"
130
+ xml.requested_by "#{requested_by}"
131
+ xml.owned_by "#{owned_by}"
132
+ xml.labels "#{labels}"
133
+ xml.project_id "#{project_id}"
134
+ # See spec
135
+ # xml.jira_id "#{jira_id}"
136
+ # xml.jira_url "#{jira_url}"
137
+ xml.other_id "#{other_id}" if other_id
138
+ xml.integration_id "#{integration_id}" if integration_id
139
+ xml.created_at DateTime.parse(created_at.to_s).to_s if created_at
140
+ xml.accepted_at DateTime.parse(accepted_at.to_s).to_s if accepted_at
141
+ xml.deadline DateTime.parse(deadline.to_s).to_s if deadline
142
+ }
143
+ end
144
+ return builder.to_xml
145
+ end
146
+
147
+ def update_attributes(attrs)
148
+ attrs.each do |key, value|
149
+ self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
150
+ end
151
+ end
152
+ end
153
+
154
+ class Story
155
+ include Validation
156
+ end
157
+ end
@@ -0,0 +1,71 @@
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
+ has_one :story, Story
21
+
22
+ def initialize(attributes={})
23
+ if attributes[:owner]
24
+ self.story = attributes.delete(:owner)
25
+ self.project_id = self.story.project_id
26
+ self.story_id = self.story.id
27
+ end
28
+
29
+ update_attributes(attributes)
30
+ end
31
+
32
+ def create
33
+ response = Client.connection["/projects/#{project_id}/stories/#{story_id}/tasks"].post(self.to_xml, :content_type => 'application/xml')
34
+ return Task.parse(response)
35
+ end
36
+
37
+ def update(attr = {})
38
+ update_attributes(attr)
39
+ response = Client.connection["/projects/#{project_id}/stories/#{story_id}/tasks/#{id}"].put(self.to_xml, :content_type => 'application/xml')
40
+ return Task.parse(response)
41
+ end
42
+
43
+ def delete
44
+ Client.connection["/projects/#{project_id}/stories/#{story_id}/tasks/#{id}"].delete
45
+ end
46
+
47
+ protected
48
+
49
+ def to_xml
50
+ builder = Nokogiri::XML::Builder.new do |xml|
51
+ xml.task {
52
+ xml.description "#{description}"
53
+ # xml.position "#{position}"
54
+ xml.complete "#{complete}"
55
+ }
56
+ end
57
+ return builder.to_xml
58
+ end
59
+
60
+ def update_attributes(attrs)
61
+ attrs.each do |key, value|
62
+ self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ class Task
69
+ include Validation
70
+ end
71
+ end