confuddle 0.0.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.
Files changed (65) hide show
  1. data/.gitignore +18 -0
  2. data/.passwd_to_unfuddle.example.yml +7 -0
  3. data/README.md +40 -0
  4. data/bin/un +833 -0
  5. data/bin/un.cmd +1 -0
  6. data/confuddle.gemspec +22 -0
  7. data/lib/graft/README.rdoc +138 -0
  8. data/lib/graft/Rakefile +43 -0
  9. data/lib/graft/lib/graft/core_ext/hash.rb +9 -0
  10. data/lib/graft/lib/graft/json.rb +14 -0
  11. data/lib/graft/lib/graft/json/attribute.rb +18 -0
  12. data/lib/graft/lib/graft/json/model.rb +28 -0
  13. data/lib/graft/lib/graft/model.rb +43 -0
  14. data/lib/graft/lib/graft/version.rb +13 -0
  15. data/lib/graft/lib/graft/xml.rb +19 -0
  16. data/lib/graft/lib/graft/xml/attribute.rb +55 -0
  17. data/lib/graft/lib/graft/xml/model.rb +49 -0
  18. data/lib/graft/lib/graft/xml/type.rb +91 -0
  19. data/lib/graft/test/test_helper.rb +38 -0
  20. data/lib/graft/test/unit/core_ext/hash_test.rb +29 -0
  21. data/lib/graft/test/unit/json/attribute_test.rb +51 -0
  22. data/lib/graft/test/unit/json/model_test.rb +86 -0
  23. data/lib/graft/test/unit/xml/attribute_test.rb +161 -0
  24. data/lib/graft/test/unit/xml/model_test.rb +173 -0
  25. data/lib/graft/test/unit/xml/type_test.rb +65 -0
  26. data/lib/unfuzzle/.gitignore +4 -0
  27. data/lib/unfuzzle/README.rdoc +129 -0
  28. data/lib/unfuzzle/Rakefile +39 -0
  29. data/lib/unfuzzle/lib/unfuzzle.rb +87 -0
  30. data/lib/unfuzzle/lib/unfuzzle/comment.rb +37 -0
  31. data/lib/unfuzzle/lib/unfuzzle/component.rb +31 -0
  32. data/lib/unfuzzle/lib/unfuzzle/milestone.rb +54 -0
  33. data/lib/unfuzzle/lib/unfuzzle/person.rb +20 -0
  34. data/lib/unfuzzle/lib/unfuzzle/priority.rb +30 -0
  35. data/lib/unfuzzle/lib/unfuzzle/project.rb +62 -0
  36. data/lib/unfuzzle/lib/unfuzzle/request.rb +75 -0
  37. data/lib/unfuzzle/lib/unfuzzle/response.rb +25 -0
  38. data/lib/unfuzzle/lib/unfuzzle/severity.rb +31 -0
  39. data/lib/unfuzzle/lib/unfuzzle/ticket.rb +156 -0
  40. data/lib/unfuzzle/lib/unfuzzle/ticket_report.rb +29 -0
  41. data/lib/unfuzzle/lib/unfuzzle/time_entry.rb +75 -0
  42. data/lib/unfuzzle/lib/unfuzzle/version.rb +13 -0
  43. data/lib/unfuzzle/test/fixtures/component.xml +8 -0
  44. data/lib/unfuzzle/test/fixtures/components.xml +17 -0
  45. data/lib/unfuzzle/test/fixtures/milestone.xml +12 -0
  46. data/lib/unfuzzle/test/fixtures/milestones.xml +25 -0
  47. data/lib/unfuzzle/test/fixtures/project.xml +17 -0
  48. data/lib/unfuzzle/test/fixtures/projects.xml +35 -0
  49. data/lib/unfuzzle/test/fixtures/severities.xml +24 -0
  50. data/lib/unfuzzle/test/fixtures/severity.xml +8 -0
  51. data/lib/unfuzzle/test/fixtures/ticket.xml +25 -0
  52. data/lib/unfuzzle/test/fixtures/tickets.xml +51 -0
  53. data/lib/unfuzzle/test/test_helper.rb +60 -0
  54. data/lib/unfuzzle/test/unit/unfuzzle/component_test.rb +36 -0
  55. data/lib/unfuzzle/test/unit/unfuzzle/milestone_test.rb +100 -0
  56. data/lib/unfuzzle/test/unit/unfuzzle/priority_test.rb +25 -0
  57. data/lib/unfuzzle/test/unit/unfuzzle/project_test.rb +87 -0
  58. data/lib/unfuzzle/test/unit/unfuzzle/request_test.rb +104 -0
  59. data/lib/unfuzzle/test/unit/unfuzzle/response_test.rb +37 -0
  60. data/lib/unfuzzle/test/unit/unfuzzle/severity_test.rb +36 -0
  61. data/lib/unfuzzle/test/unit/unfuzzle/ticket_test.rb +181 -0
  62. data/lib/unfuzzle/test/unit/unfuzzle_test.rb +39 -0
  63. data/lib/unfuzzle/unfuzzle.gemspec +31 -0
  64. data/lib/version.rb +3 -0
  65. metadata +176 -0
@@ -0,0 +1,37 @@
1
+ module Unfuzzle
2
+
3
+ #
4
+ class Comment
5
+
6
+ include Graft
7
+
8
+ attribute :id
9
+ attribute :created_at
10
+ attribute :body
11
+ attribute :author_id, :from => "author-id", :type => :integer
12
+
13
+ # Hash representation of this ticket's data (for updating)
14
+ def to_hash
15
+ {
16
+ 'id' => id,
17
+ 'body' => body,
18
+ 'author-id' => author_id
19
+ }
20
+ end
21
+
22
+ # Return a list of all tickets for an individual project
23
+ def self.all_by_project_and_ticket(project_id, ticket_id)
24
+ response = Request.get("/projects/{id}/tickets/{id}/comments")
25
+ collection_from(response.body, 'comments/comment')
26
+ end
27
+
28
+ # Create a comment
29
+ def create(project_id, ticket_id)
30
+ resource_path = "/projects/#{project_id}/tickets/#{ticket_id}/comments"
31
+ Request.post(resource_path, self.to_xml('comment'))
32
+ end
33
+
34
+ end
35
+ end
36
+
37
+
@@ -0,0 +1,31 @@
1
+ module Unfuzzle
2
+
3
+ # = Component
4
+ #
5
+ # Represents a Component in an Unfuddle project. These are user-configurable
6
+ # and are custom for each project you have. Examples include 'Administration',
7
+ # 'User Registration', etc.. A component has the following attributes:
8
+ #
9
+ # [id] The unique id for this component
10
+ # [name] The name of this component (e.g User Registration)
11
+ # [created_at] The date/time that this component was created
12
+ # [updated_at] The date/time that this component was last updated
13
+ #
14
+ class Component
15
+
16
+ include Graft
17
+
18
+ attribute :id, :type => :integer
19
+ attribute :name
20
+ attribute :project_id, :from => 'project-id', :type => :integer
21
+ attribute :created_at, :from => 'created-at', :type => :time
22
+ attribute :updated_at, :from => 'updated-at', :type => :time
23
+
24
+ # Find a component by ID for a given project
25
+ def self.find_by_project_id_and_component_id(project_id, component_id)
26
+ response = Request.get("/projects/#{project_id}/components/#{component_id}")
27
+ new response.body
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ module Unfuzzle
2
+
3
+ # = Milestone
4
+ #
5
+ # A representation of an Unfuddle Milestone. Has the following attributes:
6
+ #
7
+ # [id] Unique identifier for this milestone
8
+ # [name] Name of the milestone
9
+ # [archived] The archived status of this milestone (see Milestone#archived?)
10
+ # [due_on] The due date for this milestone
11
+ # [created_at] The date/time that this milestone was created
12
+ # [updated_at] The date/time that this milestone was last updated
13
+ #
14
+ class Milestone
15
+
16
+ include Graft
17
+
18
+ attribute :id, :type => :integer
19
+ attribute :project_id, :from => 'project-id', :type => :integer
20
+ attribute :archived, :type => :boolean
21
+ attribute :name, :from => 'title'
22
+ attribute :created_at, :from => 'created-at', :type => :time
23
+ attribute :updated_at, :from => 'updated-at', :type => :time
24
+ attribute :due_on, :from => 'due-on', :type => :date
25
+
26
+ # Return a list of all milestones for a given project
27
+ def self.find_all_by_project_id(project_id)
28
+ response = Request.get("/projects/#{project_id}/milestones")
29
+ collection_from(response.body, 'milestones/milestone')
30
+ end
31
+
32
+ # Find a milestone by ID for a given project
33
+ def self.find_by_project_id_and_milestone_id(project_id, milestone_id)
34
+ response = Request.get("/projects/#{project_id}/milestones/#{milestone_id}")
35
+ new response.body
36
+ end
37
+
38
+ # Has this milestone been archived?
39
+ def archived?
40
+ archived == true
41
+ end
42
+
43
+ # Does this milestone occur in the past?
44
+ def past?
45
+ due_on < Date.today
46
+ end
47
+
48
+ # The collection of Tickets associated to this milestone
49
+ def tickets
50
+ Ticket.find_all_by_project_id_and_milestone_id(project_id, id)
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,20 @@
1
+ module Unfuzzle
2
+ class Person
3
+ include Graft
4
+ attribute :id, :type => :integer
5
+ attribute :first_name, :from => 'first-name'
6
+ attribute :last_name, :from => 'last-name'
7
+ attribute :username, :from => 'username'
8
+
9
+ def self.all
10
+ response = Request.get("/people")
11
+ collection_from(response.body, 'people/person')
12
+ end
13
+
14
+ def self.all_for_project(project_id)
15
+ response = Request.get("/projects/#{project_id}/people")
16
+ collection_from(response.body, 'people/person')
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ module Unfuzzle
2
+
3
+ # = Priority
4
+ #
5
+ # Represents a priority for a ticket.
6
+ #
7
+ class Priority
8
+
9
+ def initialize(id)
10
+ @id = id
11
+ end
12
+
13
+ # The name of the priority based on the supplied ID
14
+ def name
15
+ mapping[@id]
16
+ end
17
+
18
+ private
19
+ def mapping
20
+ {
21
+ 5 => 'Highest',
22
+ 4 => 'High',
23
+ 3 => 'Normal',
24
+ 2 => 'Low',
25
+ 1 => 'Lowest'
26
+ }
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,62 @@
1
+ module Unfuzzle
2
+
3
+ # = Project
4
+ #
5
+ # Represents an Unfuddle project. Has the following attributes:
6
+ #
7
+ # [id] The unique identifier for this project
8
+ # [slug] The "short name" for this project
9
+ # [name] The name of this project
10
+ # [description] The description for the project
11
+ # [archived] The archived status of this project (see Project#archived?)
12
+ # [created_at] The date/time that this project was created
13
+ # [updated_at] The date/time that this project was last updated
14
+ #
15
+ class Project
16
+
17
+ include Graft
18
+
19
+ attribute :id, :type => :integer
20
+ attribute :slug, :from => 'short-name'
21
+ attribute :archived, :type => :boolean
22
+ attribute :name, :from => 'title'
23
+ attribute :description
24
+ attribute :created_at, :from => 'created-at', :type => :time
25
+ attribute :updated_at, :from => 'updated-at', :type => :time
26
+ attribute :disk_usage, :from => 'disk-usage'
27
+
28
+ # Return a list of all projects to which the current user has access
29
+ def self.all
30
+ response = Request.get('/projects')
31
+ collection_from(response.body, 'projects/project')
32
+ end
33
+
34
+ # Find a single project by its slug (short name)
35
+ def self.find_by_slug(slug)
36
+ response = Request.get("/projects/by_short_name/#{slug}")
37
+ new(response.body)
38
+ end
39
+
40
+ # Find a single project by its ID
41
+ def self.find_by_id(id)
42
+ response = Request.get("/projects/#{id}")
43
+ new(response.body)
44
+ end
45
+
46
+ # Has this project been archived?
47
+ def archived?
48
+ archived == true
49
+ end
50
+
51
+ # The collection of Milestones associated to this project
52
+ def milestones
53
+ Milestone.find_all_by_project_id(id)
54
+ end
55
+
56
+ # The collection of Tickets associated to this project
57
+ def tickets
58
+ Ticket.find_all_by_project_id(id)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,75 @@
1
+ module Unfuzzle
2
+
3
+ # = Request
4
+ #
5
+ # A basic wrapper for GET requests to the Unfuddle API
6
+ #
7
+ class Request
8
+
9
+ # Retrieve a response from the given resource path
10
+ def self.get(resource_path, query = nil)
11
+ request = new(resource_path, nil, query)
12
+ request.get
13
+ end
14
+
15
+ # Send a POST request with data and retrieve a Response
16
+ def self.post(resource_path, payload)
17
+ request = new(resource_path, payload)
18
+ request.post
19
+ end
20
+
21
+ # Send a PUT request with data and retrieve a Response
22
+ def self.put(resource_path, payload)
23
+ request = new(resource_path, payload)
24
+ request.put
25
+ end
26
+
27
+ # Create a new request for the given resource path
28
+ def initialize(resource_path, payload = nil, query = nil)
29
+ @resource_path = resource_path
30
+ @payload = payload
31
+ @query = query
32
+ end
33
+
34
+ def endpoint_uri # :nodoc:
35
+ url = "https://#{Unfuzzle.subdomain}.unfuddle.com/api/v1#{@resource_path}.xml"
36
+ url += @query if @query
37
+ URI.parse(url)
38
+ end
39
+
40
+ def client # :nodoc:
41
+ http = Net::HTTP.new(endpoint_uri.host, endpoint_uri.port)
42
+ if Unfuzzle.use_ssl
43
+ http.use_ssl = true
44
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
45
+ end
46
+ http
47
+ end
48
+
49
+ # Retrieve a response from the current resource path
50
+ def get
51
+ request = Net::HTTP::Get.new(endpoint_uri.request_uri)
52
+ request.basic_auth Unfuzzle.username, Unfuzzle.password
53
+ Response.new(client.request(request))
54
+ end
55
+
56
+ # Send a POST request to the configured endpoint
57
+ def post
58
+ request = Net::HTTP::Post.new(endpoint_uri.request_uri)
59
+ request.basic_auth Unfuzzle.username, Unfuzzle.password
60
+ request.content_type = 'application/xml'
61
+
62
+ Response.new(client.request(request, @payload))
63
+ end
64
+
65
+ # Send a PUT request to the configured endpoint
66
+ def put
67
+ request = Net::HTTP::Put.new(endpoint_uri.request_uri)
68
+ request.basic_auth Unfuzzle.username, Unfuzzle.password
69
+ request.content_type = 'application/xml'
70
+
71
+ Response.new(client.request(request, @payload))
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,25 @@
1
+ module Unfuzzle
2
+
3
+ # = Response
4
+ #
5
+ # A simple wrapper around an HTTP response from the Unfuddle API
6
+ #
7
+ class Response
8
+
9
+ # Create a new response from an HTTP response object
10
+ def initialize(http_response)
11
+ @http_response = http_response
12
+ end
13
+
14
+ # Was there an error produced as part of the request?
15
+ def error?
16
+ !@http_response.is_a?(Net::HTTPSuccess)
17
+ end
18
+
19
+ # Raw body of the HTTP response
20
+ def body
21
+ @http_response.body
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ module Unfuzzle
2
+
3
+ # = Severity
4
+ #
5
+ # Represents a Severity in an Unfuddle project. These are user-configurable
6
+ # and are custom for each project you have. Examples include 'Story',
7
+ # 'Defect', etc.. A severity has the following attributes:
8
+ #
9
+ # [id] The unique identifier for this severity
10
+ # [name] The name of this severity
11
+ # [created_at] The date/time that this severity was created
12
+ # [updated_at] The date/time that this severity was last updated
13
+ #
14
+ class Severity
15
+
16
+ include Graft
17
+
18
+ attribute :id, :type => :integer
19
+ attribute :name
20
+ attribute :project_id, :from => 'project-id', :type => :integer
21
+ attribute :created_at, :from => 'created-at', :type => :time
22
+ attribute :updated_at, :from => 'updated-at', :type => :time
23
+
24
+ # Find the severity by ID for a given project
25
+ def self.find_by_project_id_and_severity_id(project_id, severity_id)
26
+ response = Request.get("/projects/#{project_id}/severities/#{severity_id}")
27
+ new response.body
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,156 @@
1
+ module Unfuzzle
2
+
3
+ # = Ticket
4
+ #
5
+ # Represents a single Unfuddle Ticket - is associated to a project
6
+ # and optionally a project's milestone. Has the following attributes:
7
+ #
8
+ # [id] The unique identifier for this ticket
9
+ # [number] The ticket's number - this is displayed in the web interface
10
+ # [title] The title of the ticket (short)
11
+ # [description] The full description of the ticket
12
+ # [status] The ticket's status (new / accepted / resolved / closed)
13
+ # [due_on] The due date for this ticket
14
+ # [created_at] The date/time that this ticket was created
15
+ # [updated_at] The date/time that this ticket was last updated
16
+ #
17
+ class Ticket
18
+
19
+ include Graft
20
+
21
+ attribute :id, :type => :integer
22
+ attribute :project_id, :from => 'project-id', :type => :integer
23
+ attribute :milestone_id, :from => 'milestone-id', :type => :integer
24
+ attribute :component_id, :from => 'component-id', :type => :integer
25
+ attribute :priority, :type => :integer
26
+ attribute :number, :type => :integer
27
+ attribute :title, :from => 'summary'
28
+ attribute :description
29
+ attribute :due_on, :from => 'due-on', :type => :date
30
+ attribute :created_at, :from => 'created-at', :type => :time
31
+ attribute :updated_at, :from => 'updated-at', :type => :time
32
+ attribute :severity_id, :from => 'severity-id', :type => :integer
33
+ attribute :assignee_id, :from => 'assignee-id', :type => :integer
34
+ attribute :reporter_id, :from => 'reporter-id', :type => :integer
35
+ attribute :status
36
+ attribute :hours, :from => 'hours-estimate-current'
37
+
38
+
39
+ def initialize(*args)
40
+ self.priority = 3
41
+ self.status = "new"
42
+ super(*args)
43
+ end
44
+
45
+ # Return a list of all tickets for an individual project
46
+ def self.find_all_by_project_id(project_id)
47
+ response = Request.get("/projects/#{project_id}/tickets")
48
+ collection_from(response.body, 'tickets/ticket')
49
+ end
50
+
51
+ # Return a list of all tickets for a given milestone as part of a project
52
+ def self.find_all_by_project_id_and_milestone_id(project_id, milestone_id)
53
+ response = Request.get("/projects/#{project_id}/milestones/#{milestone_id}/tickets")
54
+ collection_from(response.body, 'tickets/ticket')
55
+ end
56
+
57
+ # Return a list of all tickets for a given milestone as part of a project
58
+ def self.find_first_by_project_id_and_number(project_id, number)
59
+ response = Request.get("/projects/#{project_id}/tickets/by_number/#{number}")
60
+ new(response.body)
61
+ end
62
+
63
+ # Return a list of all tickets for a given milestone as part of a project
64
+ def self.find_first_by_project_id_and_number_with_comments(project_id, number)
65
+ response = Request.get("/projects/#{project_id}/tickets/by_number/#{number}", "?comments=true")
66
+ [new(response.body), Comment.collection_from(response.body, 'comments/comment')]
67
+ end
68
+
69
+
70
+ def self.find_all_by_project_and_report(project_id, report_id)
71
+ response = Request.get("/projects/#{project_id}/ticket_reports/#{report_id}/generate")
72
+ collection_from(response.body, 'tickets/ticket')
73
+ end
74
+
75
+
76
+ def self.all_by_dinamic_report(with_project_ids = nil, only_my_tickets = false)
77
+ query = "?title=Dynamic&conditions_string=status-neq-closed"
78
+ query += ",assignee-eq-current" if only_my_tickets
79
+ query += "&fields_string=id,number,title,hours,assignee,status,reporter"
80
+
81
+ res = []
82
+
83
+ if with_project_ids.blank?
84
+ response = Request.get("/ticket_reports/dynamic", query)
85
+ res = collection_from(response.body, 'tickets/ticket')
86
+ else
87
+ with_project_ids.each do |id|
88
+ response = Request.get("/projects/#{id}/ticket_reports/dynamic", query)
89
+ res << collection_from(response.body, 'tickets/ticket')
90
+ end
91
+ end
92
+
93
+ res.flatten
94
+ end
95
+
96
+
97
+ # The Milestone associated with this ticket
98
+ def milestone
99
+ Milestone.find_by_project_id_and_milestone_id(project_id, milestone_id)
100
+ end
101
+
102
+ # The Severity associated with this ticket
103
+ def severity
104
+ Severity.find_by_project_id_and_severity_id(project_id, severity_id) unless severity_id.nil?
105
+ end
106
+
107
+ def severity_name
108
+ severity.name unless severity.nil?
109
+ end
110
+
111
+ def priority_name
112
+ # priority.name
113
+ Priority.new(priority).name
114
+ end
115
+
116
+ # The Component associated with this ticket
117
+ def component
118
+ Component.find_by_project_id_and_component_id(project_id, component_id) unless component_id.nil?
119
+ end
120
+
121
+ def component_name
122
+ component.name unless component.nil?
123
+ end
124
+
125
+ # Hash representation of this ticket's data (for updating)
126
+ def to_hash
127
+ {
128
+ 'id' => id,
129
+ 'project-id' => project_id,
130
+ 'milestone-id' => milestone_id,
131
+ 'priority' => priority,
132
+ 'severity-id' => severity_id,
133
+ 'number' => number,
134
+ 'summary' => title,
135
+ 'description' => description,
136
+ 'status' => status,
137
+ 'assignee-id' => assignee_id,
138
+ 'reporter-id' => reporter_id#,
139
+ #'hours' => hours
140
+ }
141
+ end
142
+
143
+ # Update the ticket's data in unfuddle
144
+ def update
145
+ resource_path = "/projects/#{project_id}/tickets/#{id}"
146
+ Request.put(resource_path, self.to_xml('ticket'))
147
+ end
148
+
149
+ # Create a ticket in unfuddle
150
+ def create
151
+ resource_path = "/projects/#{project_id}/tickets"
152
+ Request.post(resource_path, self.to_xml('ticket'))
153
+ end
154
+
155
+ end
156
+ end