topprospect-pivotal-tracker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +9 -0
  2. data/Gemfile +15 -0
  3. data/Gemfile.lock +40 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +77 -0
  6. data/Rakefile +49 -0
  7. data/VERSION +1 -0
  8. data/lib/pivotal-tracker.rb +38 -0
  9. data/lib/pivotal-tracker/activity.rb +45 -0
  10. data/lib/pivotal-tracker/attachment.rb +16 -0
  11. data/lib/pivotal-tracker/client.rb +35 -0
  12. data/lib/pivotal-tracker/extensions.rb +11 -0
  13. data/lib/pivotal-tracker/iteration.rb +34 -0
  14. data/lib/pivotal-tracker/membership.rb +20 -0
  15. data/lib/pivotal-tracker/note.rb +58 -0
  16. data/lib/pivotal-tracker/project.rb +58 -0
  17. data/lib/pivotal-tracker/proxy.rb +66 -0
  18. data/lib/pivotal-tracker/story.rb +149 -0
  19. data/lib/pivotal-tracker/task.rb +53 -0
  20. data/lib/pivotal-tracker/validation.rb +68 -0
  21. data/lib/pivotal_tracker.rb +2 -0
  22. data/pivotal-tracker.gemspec +120 -0
  23. data/spec/fixtures/activity.xml +177 -0
  24. data/spec/fixtures/created_note.xml +14 -0
  25. data/spec/fixtures/created_story.xml +14 -0
  26. data/spec/fixtures/iterations_all.xml +237 -0
  27. data/spec/fixtures/iterations_backlog.xml +163 -0
  28. data/spec/fixtures/iterations_current.xml +47 -0
  29. data/spec/fixtures/iterations_done.xml +33 -0
  30. data/spec/fixtures/memberships.xml +42 -0
  31. data/spec/fixtures/notes.xml +33 -0
  32. data/spec/fixtures/project.xml +51 -0
  33. data/spec/fixtures/project_activity.xml +177 -0
  34. data/spec/fixtures/projects.xml +103 -0
  35. data/spec/fixtures/stale_fish.yml +100 -0
  36. data/spec/fixtures/stories.xml +293 -0
  37. data/spec/fixtures/tasks.xml +24 -0
  38. data/spec/spec.opts +1 -0
  39. data/spec/spec_helper.rb +31 -0
  40. data/spec/support/stale_fish_fixtures.rb +67 -0
  41. data/spec/unit/pivotal-tracker/activity_spec.rb +23 -0
  42. data/spec/unit/pivotal-tracker/attachment_spec.rb +62 -0
  43. data/spec/unit/pivotal-tracker/iteration_spec.rb +52 -0
  44. data/spec/unit/pivotal-tracker/membership_spec.rb +20 -0
  45. data/spec/unit/pivotal-tracker/note_spec.rb +61 -0
  46. data/spec/unit/pivotal-tracker/project_spec.rb +55 -0
  47. data/spec/unit/pivotal-tracker/story_spec.rb +185 -0
  48. data/spec/unit/pivotal-tracker/task_spec.rb +21 -0
  49. metadata +246 -0
@@ -0,0 +1,58 @@
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 :week_start_day, 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
+
32
+ def activities
33
+ @activities ||= Proxy.new(self, Activity)
34
+ end
35
+
36
+ def iterations
37
+ @iterations ||= Proxy.new(self, Iteration)
38
+ end
39
+
40
+ def stories
41
+ @stories ||= Proxy.new(self, Story)
42
+ end
43
+
44
+ def memberships
45
+ @memberships ||= Proxy.new(self, Membership)
46
+ end
47
+
48
+ def iteration(group)
49
+ case group.to_sym
50
+ when :done then Iteration.done(self)
51
+ when :current then Iteration.current(self)
52
+ when :backlog then Iteration.backlog(self)
53
+ else
54
+ raise ArgumentError, "Invalid group. Use :done, :current or :backlog instead."
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,66 @@
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 @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
+ # Check to see if options have changed
47
+ if @opts == options
48
+ @found ||= load_found(options)
49
+ else
50
+ load_found(options)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def method_missing(method, *args, &block)
57
+ @target.send(method, *args, &block)
58
+ end
59
+
60
+ def load_found(options)
61
+ @opts = options
62
+ @target.all(@owner, @opts)
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,149 @@
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, Integer
43
+ element :integration_id, Integer
44
+ element :deadline, DateTime # Only available for Release stories
45
+
46
+ has_many :attachments, Attachment, :tag => '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 delete
70
+ Client.connection["/projects/#{project_id}/stories/#{id}"].delete
71
+ end
72
+
73
+ def notes
74
+ @notes ||= Proxy.new(self, Note)
75
+ end
76
+
77
+ def tasks
78
+ @tasks ||= Proxy.new(self, Task)
79
+ end
80
+
81
+ def upload_attachment(filename)
82
+ Attachment.parse(Client.connection["/projects/#{project_id}/stories/#{id}/attachments"].post(:Filedata => File.new(filename)))
83
+ end
84
+
85
+ def move_to_project(new_project)
86
+ move = true
87
+ old_project_id = self.project_id
88
+ target_project = -1
89
+ case new_project.class.to_s
90
+ when 'PivotalTracker::Story'
91
+ target_project = new_project.project_id
92
+ when 'PivotalTracker::Project'
93
+ target_project = new_project.id
94
+ when 'String'
95
+ target_project = new_project.to_i
96
+ when 'Fixnum', 'Integer'
97
+ target_project = new_project
98
+ else
99
+ move = false
100
+ end
101
+ if move
102
+ move_builder = Nokogiri::XML::Builder.new do |story|
103
+ story.story {
104
+ story.project_id "#{target_project}"
105
+ }
106
+ end
107
+ Story.parse(Client.connection["/projects/#{old_project_id}/stories/#{id}"].put(move_builder.to_xml, :content_type => 'application/xml'))
108
+ end
109
+ end
110
+
111
+ protected
112
+
113
+ def to_xml
114
+ builder = Nokogiri::XML::Builder.new do |xml|
115
+ xml.story {
116
+ xml.name "#{name}"
117
+ xml.description "#{description}"
118
+ xml.story_type "#{story_type}"
119
+ xml.estimate "#{estimate}"
120
+ xml.current_state "#{current_state}"
121
+ xml.requested_by "#{requested_by}"
122
+ xml.owned_by "#{owned_by}"
123
+ xml.labels "#{labels}"
124
+ xml.project_id "#{project_id}"
125
+ # See spec
126
+ # xml.jira_id "#{jira_id}"
127
+ # xml.jira_url "#{jira_url}"
128
+ xml.other_id "#{other_id}"
129
+ xml.integration_id "#{integration_id}"
130
+ xml.created_at DateTime.parse(created_at.to_s).to_s if created_at
131
+ # PivotalTracker API ignores everything after the date.
132
+ # xml.accepted_at DateTime.parse(accepted_at.to_s).to_s if accepted_at
133
+ xml.deadline DateTime.parse(deadline.to_s).to_s if deadline
134
+ }
135
+ end
136
+ return builder.to_xml
137
+ end
138
+
139
+ def update_attributes(attrs)
140
+ attrs.each do |key, value|
141
+ self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
142
+ end
143
+ end
144
+ end
145
+
146
+ class Story
147
+ include Validation
148
+ end
149
+ end
@@ -0,0 +1,53 @@
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
+ return builder.to_xml
46
+ end
47
+
48
+ end
49
+
50
+ class Task
51
+ include Validation
52
+ end
53
+ end
@@ -0,0 +1,68 @@
1
+ module PivotalTracker
2
+
3
+ class Errors
4
+ include Enumerable
5
+ attr_reader :errors
6
+
7
+ alias :messages :errors
8
+
9
+ def initialize
10
+ @errors = []
11
+ end
12
+
13
+ def each
14
+ @errors.each do |error|
15
+ yield error
16
+ end
17
+ end
18
+
19
+ def empty?
20
+ @errors.empty?
21
+ end
22
+
23
+ def add_from_xml(xml)
24
+ Nokogiri::XML(xml).xpath("/errors/error").each do |error|
25
+ @errors << error.text
26
+ end
27
+ end
28
+ end
29
+
30
+ module Validation
31
+
32
+ def self.included(klass)
33
+ klass.class_eval do
34
+ if klass.instance_methods.include?(:create)
35
+ alias_method :create_without_validations, :create
36
+ alias_method :create, :create_with_validations
37
+ end
38
+
39
+ if klass.instance_methods.include?(:update)
40
+ alias_method :update_without_validations, :update
41
+ alias_method :update, :update_with_validations
42
+ end
43
+ end
44
+ end
45
+
46
+ def create_with_validations
47
+ begin
48
+ create_without_validations
49
+ rescue RestClient::UnprocessableEntity => e
50
+ errors.add_from_xml e.response
51
+ self
52
+ end
53
+ end
54
+
55
+ def update_with_validations(attrs={})
56
+ begin
57
+ update_without_validations attrs
58
+ rescue RestClient::UnprocessableEntity => e
59
+ errors.add_from_xml e.response
60
+ self
61
+ end
62
+ end
63
+
64
+ def errors
65
+ @errors ||= Errors.new
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,2 @@
1
+ require "pivotal-tracker"
2
+
@@ -0,0 +1,120 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{topprospect-pivotal-tracker}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Justin Smestad", "Josh Nichols", "Terence Lee"]
12
+ s.date = %q{2010-12-15}
13
+ s.email = %q{justin.smestad@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/pivotal-tracker.rb",
27
+ "lib/pivotal-tracker/activity.rb",
28
+ "lib/pivotal-tracker/attachment.rb",
29
+ "lib/pivotal-tracker/client.rb",
30
+ "lib/pivotal-tracker/extensions.rb",
31
+ "lib/pivotal-tracker/iteration.rb",
32
+ "lib/pivotal-tracker/membership.rb",
33
+ "lib/pivotal-tracker/note.rb",
34
+ "lib/pivotal-tracker/project.rb",
35
+ "lib/pivotal-tracker/proxy.rb",
36
+ "lib/pivotal-tracker/story.rb",
37
+ "lib/pivotal-tracker/task.rb",
38
+ "lib/pivotal-tracker/validation.rb",
39
+ "lib/pivotal_tracker.rb",
40
+ "pivotal-tracker.gemspec",
41
+ "spec/fixtures/activity.xml",
42
+ "spec/fixtures/created_note.xml",
43
+ "spec/fixtures/created_story.xml",
44
+ "spec/fixtures/iterations_all.xml",
45
+ "spec/fixtures/iterations_backlog.xml",
46
+ "spec/fixtures/iterations_current.xml",
47
+ "spec/fixtures/iterations_done.xml",
48
+ "spec/fixtures/memberships.xml",
49
+ "spec/fixtures/notes.xml",
50
+ "spec/fixtures/project.xml",
51
+ "spec/fixtures/project_activity.xml",
52
+ "spec/fixtures/projects.xml",
53
+ "spec/fixtures/stale_fish.yml",
54
+ "spec/fixtures/stories.xml",
55
+ "spec/fixtures/tasks.xml",
56
+ "spec/spec.opts",
57
+ "spec/spec_helper.rb",
58
+ "spec/support/stale_fish_fixtures.rb",
59
+ "spec/unit/pivotal-tracker/activity_spec.rb",
60
+ "spec/unit/pivotal-tracker/attachment_spec.rb",
61
+ "spec/unit/pivotal-tracker/iteration_spec.rb",
62
+ "spec/unit/pivotal-tracker/membership_spec.rb",
63
+ "spec/unit/pivotal-tracker/note_spec.rb",
64
+ "spec/unit/pivotal-tracker/project_spec.rb",
65
+ "spec/unit/pivotal-tracker/story_spec.rb",
66
+ "spec/unit/pivotal-tracker/task_spec.rb"
67
+ ]
68
+ s.homepage = %q{http://github.com/jsmestad/pivotal-tracker}
69
+ s.rdoc_options = ["--charset=UTF-8"]
70
+ s.require_paths = ["lib"]
71
+ s.rubygems_version = %q{1.3.7}
72
+ s.summary = %q{Ruby wrapper for the Pivotal Tracker API}
73
+ s.test_files = [
74
+ "spec/spec_helper.rb",
75
+ "spec/support/stale_fish_fixtures.rb",
76
+ "spec/unit/pivotal-tracker/activity_spec.rb",
77
+ "spec/unit/pivotal-tracker/attachment_spec.rb",
78
+ "spec/unit/pivotal-tracker/iteration_spec.rb",
79
+ "spec/unit/pivotal-tracker/membership_spec.rb",
80
+ "spec/unit/pivotal-tracker/note_spec.rb",
81
+ "spec/unit/pivotal-tracker/project_spec.rb",
82
+ "spec/unit/pivotal-tracker/story_spec.rb",
83
+ "spec/unit/pivotal-tracker/task_spec.rb"
84
+ ]
85
+
86
+ if s.respond_to? :specification_version then
87
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
88
+ s.specification_version = 3
89
+
90
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
91
+ s.add_runtime_dependency(%q<rest-client>, ["~> 1.6.0"])
92
+ s.add_runtime_dependency(%q<happymapper>, [">= 0.3.2"])
93
+ s.add_runtime_dependency(%q<builder>, [">= 0"])
94
+ s.add_runtime_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
95
+ s.add_development_dependency(%q<rspec>, [">= 0"])
96
+ s.add_development_dependency(%q<bundler>, [">= 0.9.26"])
97
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
98
+ s.add_development_dependency(%q<stale_fish>, ["~> 1.3.0"])
99
+ else
100
+ s.add_dependency(%q<rest-client>, ["~> 1.6.0"])
101
+ s.add_dependency(%q<happymapper>, [">= 0.3.2"])
102
+ s.add_dependency(%q<builder>, [">= 0"])
103
+ s.add_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
104
+ s.add_dependency(%q<rspec>, [">= 0"])
105
+ s.add_dependency(%q<bundler>, [">= 0.9.26"])
106
+ s.add_dependency(%q<jeweler>, [">= 0"])
107
+ s.add_dependency(%q<stale_fish>, ["~> 1.3.0"])
108
+ end
109
+ else
110
+ s.add_dependency(%q<rest-client>, ["~> 1.6.0"])
111
+ s.add_dependency(%q<happymapper>, [">= 0.3.2"])
112
+ s.add_dependency(%q<builder>, [">= 0"])
113
+ s.add_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
114
+ s.add_dependency(%q<rspec>, [">= 0"])
115
+ s.add_dependency(%q<bundler>, [">= 0.9.26"])
116
+ s.add_dependency(%q<jeweler>, [">= 0"])
117
+ s.add_dependency(%q<stale_fish>, ["~> 1.3.0"])
118
+ end
119
+ end
120
+