lrd-pivotal-tracker 0.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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,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,98 @@
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
+ project = @found.detect { |document| document.id == id }
13
+ if !project
14
+ project = parse(Client.connection["/projects/#{id}"].get)
15
+ end
16
+ project
17
+ else
18
+ parse(Client.connection["/projects/#{id}"].get)
19
+ end
20
+ end
21
+ end
22
+
23
+ element :id, Integer
24
+ element :name, String
25
+ element :account, String
26
+ element :week_start_day, String
27
+ element :point_scale, String
28
+ element :labels, String
29
+ element :velocity_scheme, String
30
+ element :iteration_length, Integer
31
+ element :initial_velocity, Integer
32
+ element :current_velocity, Integer
33
+ element :last_activity_at, DateTime
34
+ element :use_https, Boolean
35
+ element :first_iteration_start_time, DateTime
36
+ element :current_iteration_number, Integer
37
+
38
+ def initialize(attributes={})
39
+ update_attributes(attributes)
40
+ end
41
+
42
+ def create
43
+ response = Client.connection["/projects"].post(self.to_xml, :content_type => 'application/xml')
44
+ project = Project.parse(response)
45
+ return project
46
+ end
47
+
48
+ def activities
49
+ @activities ||= Proxy.new(self, Activity)
50
+ end
51
+
52
+ def iterations
53
+ @iterations ||= Proxy.new(self, Iteration)
54
+ end
55
+
56
+ def stories
57
+ @stories ||= Proxy.new(self, Story)
58
+ end
59
+
60
+ def memberships
61
+ @memberships ||= Proxy.new(self, Membership)
62
+ end
63
+
64
+ def iteration(group)
65
+ case group.to_sym
66
+ when :done then Iteration.done(self)
67
+ when :current then Iteration.current(self)
68
+ when :backlog then Iteration.backlog(self)
69
+ when :current_backlog then Iteration.current_backlog(self)
70
+ else
71
+ raise ArgumentError, "Invalid group. Use :done, :current, :backlog or :current_backlog instead."
72
+ end
73
+ end
74
+
75
+ protected
76
+
77
+ def to_xml
78
+ builder = Nokogiri::XML::Builder.new do |xml|
79
+ xml.project {
80
+ xml.name "#{name}"
81
+ xml.iteration_length.integer "#{iteration_length}" unless iteration_length.nil?
82
+ xml.point_scale "#{point_scale}" unless point_scale.nil?
83
+ }
84
+ end
85
+ return builder.to_xml
86
+ end
87
+
88
+ def update_attributes(attrs)
89
+ attrs.each do |key, value|
90
+ self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
91
+ end
92
+ end
93
+
94
+ end
95
+ class Project
96
+ include Validation
97
+ end
98
+ 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,153 @@
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
+ end
23
+
24
+ tag "story"
25
+
26
+ element :id, Integer
27
+ element :url, String
28
+ element :created_at, DateTime
29
+ element :accepted_at, DateTime
30
+ element :project_id, Integer
31
+
32
+ element :name, String
33
+ element :description, String
34
+ element :story_type, String
35
+ element :estimate, Integer
36
+ element :current_state, String
37
+ element :requested_by, String
38
+ element :owned_by, String
39
+ element :labels, String
40
+ element :jira_id, Integer
41
+ element :jira_url, String
42
+ element :other_id, String
43
+ element :integration_id, Integer
44
+ element :deadline, DateTime # Only available for Release stories
45
+
46
+ has_many :attachments, Attachment, :tag => 'attachment', :xpath => '//attachments'
47
+
48
+ def initialize(attributes={})
49
+ if attributes[:owner]
50
+ self.project_id = attributes.delete(:owner).id
51
+ end
52
+ update_attributes(attributes)
53
+ end
54
+
55
+ def create
56
+ return self if project_id.nil?
57
+ response = Client.connection["/projects/#{project_id}/stories"].post(self.to_xml, :content_type => 'application/xml')
58
+ new_story = Story.parse(response)
59
+ new_story.project_id = project_id
60
+ return new_story
61
+ end
62
+
63
+ def update(attrs={})
64
+ update_attributes(attrs)
65
+ response = Client.connection["/projects/#{project_id}/stories/#{id}"].put(self.to_xml, :content_type => 'application/xml')
66
+ return Story.parse(response)
67
+ end
68
+
69
+ def move(position, story)
70
+ raise ArgumentError, "Can only move :before or :after" unless [:before, :after].include? position
71
+ Story.parse(Client.connection["/projects/#{project_id}/stories/#{id}/moves?move\[move\]=#{position}&move\[target\]=#{story.id}"].post(''))
72
+ end
73
+
74
+ def delete
75
+ Client.connection["/projects/#{project_id}/stories/#{id}"].delete
76
+ end
77
+
78
+ def notes
79
+ @notes ||= Proxy.new(self, Note)
80
+ end
81
+
82
+ def tasks
83
+ @tasks ||= Proxy.new(self, Task)
84
+ end
85
+
86
+ def upload_attachment(filename)
87
+ Attachment.parse(Client.connection["/projects/#{project_id}/stories/#{id}/attachments"].post(:Filedata => File.new(filename)))
88
+ end
89
+
90
+ def move_to_project(new_project)
91
+ move = true
92
+ old_project_id = self.project_id
93
+ target_project = -1
94
+ case new_project.class.to_s
95
+ when 'PivotalTracker::Story'
96
+ target_project = new_project.project_id
97
+ when 'PivotalTracker::Project'
98
+ target_project = new_project.id
99
+ when 'String'
100
+ target_project = new_project.to_i
101
+ when 'Fixnum', 'Integer'
102
+ target_project = new_project
103
+ else
104
+ move = false
105
+ end
106
+ if move
107
+ move_builder = Nokogiri::XML::Builder.new do |story|
108
+ story.story {
109
+ story.project_id "#{target_project}"
110
+ }
111
+ end
112
+ Story.parse(Client.connection["/projects/#{old_project_id}/stories/#{id}"].put(move_builder.to_xml, :content_type => 'application/xml'))
113
+ end
114
+ end
115
+
116
+ protected
117
+
118
+ def to_xml
119
+ builder = Nokogiri::XML::Builder.new do |xml|
120
+ xml.story {
121
+ xml.name "#{name}"
122
+ xml.description "#{description}"
123
+ xml.story_type "#{story_type}"
124
+ xml.estimate "#{estimate}"
125
+ xml.current_state "#{current_state}"
126
+ xml.requested_by "#{requested_by}"
127
+ xml.owned_by "#{owned_by}"
128
+ xml.labels "#{labels}"
129
+ xml.project_id "#{project_id}"
130
+ # See spec
131
+ # xml.jira_id "#{jira_id}"
132
+ # xml.jira_url "#{jira_url}"
133
+ xml.other_id "#{other_id}" if other_id
134
+ xml.integration_id "#{integration_id}" if integration_id
135
+ xml.created_at DateTime.parse(created_at.to_s).to_s if created_at
136
+ xml.accepted_at DateTime.parse(accepted_at.to_s).to_s if accepted_at
137
+ xml.deadline DateTime.parse(deadline.to_s).to_s if deadline
138
+ }
139
+ end
140
+ return builder.to_xml
141
+ end
142
+
143
+ def update_attributes(attrs)
144
+ attrs.each do |key, value|
145
+ self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
146
+ end
147
+ end
148
+ end
149
+
150
+ class Story
151
+ include Validation
152
+ end
153
+ end