confuddle 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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