austinmoody-fogbugz-api 0.0.2

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 (6) hide show
  1. data/LICENSE +22 -0
  2. data/README.rdoc +28 -0
  3. data/TODO +4 -0
  4. data/fogbugz-api.rb +1 -0
  5. data/lib/fogbugz-api.rb +375 -0
  6. metadata +71 -0
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2008 Austin Moody
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,28 @@
1
+ = Ruby FogBugz API Wrapper
2
+
3
+ My attempt at creating a wrapper for the FogBugz API in Ruby.
4
+
5
+ More about FogBugz: http://www.fogbugz.com
6
+ More about FogBugz API: http://www.fogcreek.com/FogBugz/docs/60/topics/advanced/API.html
7
+
8
+ This is very much a work in progress. Any questions, concerns, or if you want to help out please e-mail me: austin.moody@gmail.com
9
+
10
+ == Installation
11
+
12
+ GEM coming soon hopefully. For now nab the fogbugz-api.rb file and require in your Ruby script.
13
+
14
+ == Requirements
15
+
16
+ * Hpricot (http://code.whytheluckystiff.net/hpricot/)
17
+ * net/https
18
+ * A login to a FogBugz server
19
+
20
+ == Example Usage
21
+
22
+ fb = FogBugz.new("my.fogbugz.com",true)
23
+
24
+ fb.logon("me@email.com","mypassword")
25
+
26
+ case_search = fb.search("API errors")
27
+
28
+ fb.logoff
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ == FogBugz API Todo
2
+
3
+ * Finish it! At the moment it is very incomplete. Pretty much just the list functions, search, and newCase.
4
+ * Create tests and make sure they work.
data/fogbugz-api.rb ADDED
@@ -0,0 +1 @@
1
+ require "lib/fogbugz-api"
@@ -0,0 +1,375 @@
1
+ require 'rubygems' rescue nil
2
+ require 'hpricot'
3
+ require 'net/https'
4
+ require 'cgi'
5
+
6
+ class FogBugzError < StandardError; end
7
+
8
+ # FogBugz class
9
+ class FogBugz
10
+
11
+ # Version of the FogBuz API this was written for. If the minversion returned
12
+ # by FogBugz is greater than this value this library will not function.
13
+ # TODO
14
+ # 1. If API mismatch... destroy Object?
15
+ API_VERSION = 5
16
+
17
+ # This is an array of all possible values that can be returned on a case.
18
+ # For methods that ask for cols wanted for a case this array will be used if
19
+ # their is nothing else specified.
20
+ CASE_COLUMNS = %w(ixBug fOpen sTitle sLatestTextSummary ixBugEventLatestText
21
+ ixProject sProject ixArea sArea ixGroup ixPersonAssignedTo sPersonAssignedTo
22
+ sEmailAssignedTo ixPersonOpenedBy ixPersonResolvedBy ixPersonClosedBy
23
+ ixPersonLastEditedBy ixStatus sStatus ixPriority sPriority ixFixFor sFixFor
24
+ dtFixFor sVersion sComputer hrsOrigEst hrsCurrEst hrsElapsed c sCustomerEmail
25
+ ixMailbox ixCategory sCategory dtOpened dtResolved dtClosed ixBugEventLatest
26
+ dtLastUpdated fReplied fForwarded sTicket ixDiscussTopic dtDue sReleaseNotes
27
+ ixBugEventLastView dtLastView ixRelatedBugs sScoutDescription sScoutMessage
28
+ fScoutStopReporting fSubscribed events)
29
+
30
+ attr_reader :url, :token, :use_ssl, :api_version, :api_minversion, :api_url
31
+
32
+ # Creates an instance of the FogBugz class.
33
+ #
34
+ # * url: URL to your FogBugz installation. URL only as in my.fogbugz.com
35
+ # without the http or https.
36
+ # * use_ssl: Does this server use SSL? true/false
37
+ # * token: Already have a token for the server? You can provide that here.
38
+ #
39
+ # Connects to the specified FogBugz installation and grabs the api.xml file
40
+ # to get other information such as API version, API minimum version, and the
41
+ # API endpoint. Also sets http/https connection to the server and sets the
42
+ # token if provided. FogBugzError will be raise if the minimum API version
43
+ # returned by FogBugz is greater than API_VERSION of this class.
44
+ #
45
+ # Example Usage:
46
+ #
47
+ # fb = FogBugz.new("my.fogbugz.com",true)
48
+ #
49
+ def initialize(url,use_ssl=false,token=nil)
50
+ @url = url
51
+ @use_ssl = use_ssl
52
+ connect
53
+
54
+ # Attempt to grap api.xml file from the server specified by url. Will let
55
+ # us know API is functional and verion matches this class
56
+ result = Hpricot.XML(@connection.get("/api.xml").body)
57
+
58
+ @api_version = (result/"version").inner_html.to_i
59
+ @api_minversion = (result/"version").inner_html.to_i
60
+ @api_url = "/" + (result/"url").inner_html
61
+
62
+ # Make sure this class will work w/ API version
63
+ raise FogBugzError, "API version mismatch" if (API_VERSION < @api_minversion)
64
+
65
+ @token = token ? token : ""
66
+ end
67
+
68
+ # Validates a user with FogBugz. Saves the returned token for use with other
69
+ # commands.
70
+ #
71
+ # If a token was already specified with new it will be overwritten with the
72
+ # token picked up by a successful authentication.
73
+ def logon(email,password)
74
+ cmd = {"cmd" => "logon", "email" => email, "password" => password}
75
+
76
+ result = Hpricot.XML(@connection.post(@api_url, to_params(cmd)).body)
77
+
78
+ if (result/"error").length >= 1
79
+ # error code 1 = bad login
80
+ # error code 2 = ambiguous name
81
+ case (result/"error").first["code"]
82
+ when "1"
83
+ raise FogBugzError, (result/"error").inner_html
84
+ when "2"
85
+ ambiguous_users = []
86
+ (result/"person").each do |person|
87
+ ambiguous_users << CDATA_REGEXP.match(person.inner_html)[1]
88
+ end
89
+ raise FogBugzError, (result/"error").inner_html + " " + ambiguous_users.join(", ")
90
+ end # case
91
+ elsif (result/"token").length == 1
92
+ # successful login
93
+ @token = CDATA_REGEXP.match((result/"token").inner_html)[1]
94
+ end
95
+ end
96
+
97
+ def logoff
98
+ cmd = {"cmd" => "logoff", "token" => @token}
99
+ result = Hpricot.XML(@connection.post(@api_url, to_params(cmd)).body)
100
+ @token = ""
101
+ end
102
+
103
+ def filters
104
+ cmd = {"cmd" => "listFilters", "token" => @token}
105
+ result = Hpricot.XML(@connection.post(@api_url, to_params(cmd)).body)
106
+ return_value = Hash.new
107
+ (result/"filter").each do |filter|
108
+ # create hash for each new project
109
+ filter_name = filter.inner_html
110
+ return_value[filter_name] = Hash.new
111
+ return_value[filter_name]["name"] = filter_name
112
+ return_value[filter_name] = filter.attributes.merge(return_value[filter_name])
113
+ end
114
+ return_value
115
+ end
116
+
117
+ def projects(fWrite=false, ixProject=nil)
118
+ return_value = Hash.new
119
+ cmd = {"cmd" => "listProjects", "token" => @token}
120
+ {"fWrite"=>"1"}.merge(cmd) if fWrite
121
+ {"ixProject"=>ixProject}.merge(cmd) if ixProject
122
+ result = Hpricot.XML(@connection.post(@api_url,to_params(cmd)).body)
123
+ return list_process(result,"project","sProject")
124
+ end
125
+
126
+ # Returns an integer, which is ixProject for the project created
127
+ # TODO - change to accept Has of parameters?
128
+ def new_project(sProject, ixPersonPrimaryContact, fAllowPublicSubmit, ixGroup, fInbox)
129
+
130
+ # I would have thought that the fAllowPublicSubmit would have been
131
+ # true/false... instead seems to need to be 0 or 1.
132
+ cmd = {
133
+ "cmd" => "newProject",
134
+ "token" => @token,
135
+ "sProject" => sProject,
136
+ "ixPersonPrimaryContact" => ixPersonPrimaryContact.to_s,
137
+ "fAllowPublicSubmit" => fAllowPublicSubmit.to_s,
138
+ "ixGroup" => ixGroup.to_s,
139
+ "fInbox" => fInbox.to_s
140
+ }
141
+ result = Hpricot.XML(@connection.post(@api_url,to_params(cmd)).body)
142
+ return (result/"ixProject").inner_html.to_i
143
+ end
144
+
145
+ def areas(fWrite=false, ixProject=nil, ixArea=nil)
146
+ return_value = Hash.new
147
+ cmd = {"cmd" => "listAreas", "token" => @token}
148
+ cmd = {"fWrite"=>"1"}.merge(cmd) if fWrite
149
+ cmd = {"ixProject"=>ixProject}.merge(cmd) if ixProject
150
+ cmd = {"ixArea" => ixArea}.merge(cmd) if ixArea
151
+ result = Hpricot.XML(@connection.post(@api_url,to_params(cmd)).body)
152
+ return list_process(result,"area","sArea")
153
+ end
154
+
155
+ def fix_fors(ixProject=nil,ixFixFor=nil)
156
+ return_value = Hash.new
157
+ cmd = {"cmd" => "listFixFors", "token" => @token}
158
+ {"ixProject"=>ixProject}.merge(cmd) if ixProject
159
+ {"ixFixFor" => ixFixFor}.merge(cmd) if ixFixFor
160
+ result = Hpricot.XML(@connection.post(@api_url,to_params(cmd)).body)
161
+ return list_process(result,"fixfor","sFixFor")
162
+ end
163
+
164
+ def categories
165
+ cmd = {"cmd" => "listCategories", "token" => @token}
166
+ result = Hpricot.XML(@connection.post(@api_url,to_params(cmd)).body)
167
+ return list_process(result,"category","sCategory")
168
+ end
169
+
170
+ def listPriorities
171
+ cmd = {"cmd" => "listPriorities", "token" => @token}
172
+ result = Hpricot.XML(@connection.post(@api_url,to_params(cmd)).body)
173
+ return list_process(result,"priority","sPriority")
174
+ end
175
+
176
+ # Returns list of people in corresponding categories.
177
+ #
178
+ # * fIncludeNormal: Only include Normal users. If no options specified,
179
+ # fIncludeNormal=1 is assumed.
180
+ # * fIncludeCommunity: true/false Will include Community users in return.
181
+ # * fIncludeVirtual: true/false Will include Virtual users in return.
182
+ def people(fIncludeNormal="1", fIncludeCommunity=nil, fIncludeVirtual=nil)
183
+ cmd = {
184
+ "cmd" => "listPeople",
185
+ "token" => @token,
186
+ "fIncludeNormal" => fIncludeNormal
187
+ }
188
+ cmd = {"fIncludeCommunity" => "1"}.merge(cmd) if fIncludeCommunity
189
+ cmd = {"fIncludeVirtual" => "1"}.merge(cmd) if fIncludeVirtual
190
+ result = Hpricot.XML(@connection.post(@api_url, to_params(cmd)).body)
191
+ return list_process(result,"person","sFullName")
192
+ end
193
+
194
+ # Returns a list of statuses for a particular category.
195
+ #
196
+ # * ixCategory => category to return statuses for. If not specified, then all are returned.
197
+ # * fResolved => If = 1 then only resolved statuses are returned.
198
+ def statuses(ixCategory=nil,fResolved=nil)
199
+ cmd = {
200
+ "cmd" => "listStatuses",
201
+ "token" => @token
202
+ }
203
+ cmd = {"ixCategory"=>ixCategory}.merge(cmd) if ixCategory
204
+ cmd = {"fResolved"=>fResolved}.merge(cmd) if fResolved
205
+ result = Hpricot.XML(@connection.post(@api_url,to_params(cmd)).body)
206
+ return list_process(result,"status","sStatus")
207
+ end
208
+
209
+ # Returns a list of mailboxes that you have access to.
210
+ def mailboxes
211
+ cmd = {
212
+ "cmd" => "listMailboxes",
213
+ "token" => @token
214
+ }
215
+ result = Hpricot.XML(@connection.post(@api_url,to_params(cmd)).body)
216
+ # usually lists were keyed w/ a name field. Mailboxes just
217
+ # weren't working for me so I'm going with ixMailbox value
218
+ return list_process(result,"mailbox","ixMailbox")
219
+ end
220
+
221
+ # Searches for FogBugz cases
222
+ #
223
+ # * q: Query for searching. Should hopefully work just like the Search box
224
+ # within the FogBugz application.
225
+ # * cols: Columns of information to be returned for each case found. Consult
226
+ # FogBugz API documentation for a list. If this is not specified the
227
+ # CASE_COLUMNS will be used. This will request every possible datapoint (as
228
+ # of API version 5) for each case.
229
+ # * max: Maximum number of cases to be returned for your search. Will return
230
+ # all if not specified.
231
+ def search(q, cols=CASE_COLUMNS, max=nil)
232
+ # TODO - shoudl I worry about the "operations" returned
233
+ # in the <case>?
234
+
235
+ cmd = {
236
+ "cmd" => "search",
237
+ "token" => @token,
238
+ "q" => q,
239
+ "cols" => cols.join(",")
240
+ }
241
+ cmd = {"max" => max}.merge(cmd) if max
242
+ result = Hpricot.XML(@connection.post(@api_url,to_params(cmd)).body)
243
+ return_value = list_process(result,"case","ixBug")
244
+ # if one of the returned cols = events, then process
245
+ # this list and replace its spot in the Hash
246
+ # with this instead of a string of XML
247
+ return_value.each do |key,value|
248
+ return_value[key]["events"] = list_process(Hpricot.XML(return_value[key]["events"]),"event","ixBugEvent") if return_value[key].has_key?("events")
249
+ end
250
+ return_value
251
+ end
252
+
253
+ # Creates a FogBugz case.
254
+ #
255
+ # * params: must be a hash keyed with values from the FogBugz API docs.
256
+ # sTitle, ixProject (or sProject), etc...
257
+ # * cols: columns to be returned about the case which gets created. Ff not
258
+ # passed will use constant list (all) provided with Class
259
+ def new_case(params, cols=CASE_COLUMNS)
260
+ case_process("new",params,cols)
261
+ end
262
+
263
+ protected
264
+
265
+ CDATA_REGEXP = /<!\[CDATA\[(.*?)\]\]>/
266
+
267
+ # Makes connection to the FogBugz server
268
+ #
269
+ # Assumes port 443 for SSL connections and 80 for non-SSL connections.
270
+ # Possibly should provide a way to override this.
271
+ def connect
272
+ @connection = Net::HTTP.new(@url, @use_ssl ? 443 : 80)
273
+ @connection.use_ssl = @use_ssl
274
+ @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE if @use_ssl
275
+ end
276
+
277
+ def case_process(cmd,params,cols)
278
+ cmd = {
279
+ "cmd" => cmd,
280
+ "token" => @token,
281
+ "cols" => cols.join(",")
282
+ }.merge(params)
283
+ result = Hpricot.XML(@connection.post(@api_url, to_params(cmd)).body)
284
+ return_value = list_process(result,"case","ixBug")
285
+ # if one of the returned cols = events, then process
286
+ # this list and replace its spot in the Hash
287
+ # with this instead of a string of XML
288
+ return_value.each do |key,value|
289
+ return_value[key]["events"] = list_process(Hpricot.XML(return_value[key]["events"]),"event","ixBugEvent") if return_value[key].has_key?("events")
290
+ end
291
+ return_value[return_value.keys[0]]
292
+ end
293
+
294
+ # method used by other list methods to process the XML returned by FogBugz API.
295
+ #
296
+ # * xml => XML to process
297
+ # * element => individual elements within the XML to create Hashes for within the returned value
298
+ # * element_name => key for each individual Hash within the return value.
299
+ #
300
+ # EXAMPLE XML
301
+ #<response>
302
+ # <categories>
303
+ # <category>
304
+ # <ixCategory>1</ixCategory>
305
+ # <sCategory><![CDATA[Bug]]></sCategory>
306
+ # <sPlural><![CDATA[Bugs]]></sPlural>
307
+ # <ixStatusDefault>2</ixStatusDefault>
308
+ # <fIsScheduleItem>false</fIsScheduleItem>
309
+ # </category>
310
+ # <category>
311
+ # <ixCategory>2</ixCategory>
312
+ # <sCategory><![CDATA[Feature]]></sCategory>
313
+ # <sPlural><![CDATA[Features]]></sPlural>
314
+ # <ixStatusDefault>8</ixStatusDefault>
315
+ # <fIsScheduleItem>false</fIsScheduleItem>
316
+ # </category>
317
+ # </categories>
318
+ #</response>
319
+ #
320
+ # EXAMPLE CAll
321
+ # list_process(xml, "category", "sCategory")
322
+ #
323
+ # EXAMPLE HASH RETURN
324
+ #
325
+ # {
326
+ # "Bug" => {
327
+ # "ixCategory" => 1,
328
+ # "sCategory" => "Bug",
329
+ # "sPlural" => "Bugs",
330
+ # "ixStatusDefault" => 2,
331
+ # "fIsScheduleItem" => false
332
+ # },
333
+ # "Feature" => {
334
+ # "ixCategory" => 2,
335
+ # "sCategory" => "Feature",
336
+ # "sPlural" => "Features",
337
+ # "ixStatusDefault" => 2,
338
+ # "fIsScheduleItem" => false
339
+ # }
340
+ # }
341
+ def list_process(xml, element, element_name)
342
+ return_value = Hash.new
343
+ (xml/"#{element}").each do |item|
344
+ if element_name[0,1] == "s"
345
+ item_name = CDATA_REGEXP.match((item/"#{element_name}").inner_html)[1]
346
+ else
347
+ item_name = (item/"#{element_name}").inner_html
348
+ end
349
+ return_value[item_name] = Hash.new
350
+
351
+ item.each_child do |attribute|
352
+ return_value[item_name][attribute.name] = attribute.inner_html
353
+ # convert values to proper types
354
+ return_value[item_name][attribute.name] = CDATA_REGEXP.match(attribute.inner_html)[1] if (attribute.name[0,1] == "s" or attribute.name[0,3] == "evt") and attribute.inner_html != "" and CDATA_REGEXP.match(attribute.inner_html) != nil
355
+ return_value[item_name][attribute.name] = return_value[item_name][attribute.name].to_i if (attribute.name[0,2] == "ix" or attribute.name[0,1] == "n")
356
+ return_value[item_name][attribute.name] = (return_value[item_name][attribute.name] == "true") ? true : false if attribute.name[0,1] == "f"
357
+
358
+ end
359
+ end
360
+
361
+ return_value
362
+ end
363
+
364
+ # Converts a hash such as
365
+ # {
366
+ # :cmd => "logon",
367
+ # :email => "austin.moody@gmail.com",
368
+ # :password => "yeahwhatever",
369
+ # }
370
+ # to
371
+ # "cmd=logon&email=austin.moody@gmail.com&password=yeahwhatever"
372
+ def to_params(hash)
373
+ hash.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
374
+ end
375
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: austinmoody-fogbugz-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Austin Moody
8
+ - Gregory McIntyre
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-07-03 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: hpricot
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0.6"
24
+ version:
25
+ description:
26
+ email: austin.moody@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.rdoc
33
+ - LICENSE
34
+ - TODO
35
+ files:
36
+ - README.rdoc
37
+ - LICENSE
38
+ - TODO
39
+ - fogbugz-api.rb
40
+ - lib
41
+ - lib/fogbugz-api.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/austinmoody/ruby-fogbugz-api/wikis
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --main
47
+ - README.rdoc
48
+ - --inline-source
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.2.0
67
+ signing_key:
68
+ specification_version: 2
69
+ summary: Ruby wrapper for FogBugz API
70
+ test_files: []
71
+