rt-client 0.5.0 → 0.6.4
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.
- data/rt/client.rb +350 -363
- data/rt/rtxmlsrv.rb +0 -1
- metadata +4 -115
data/rt/client.rb
CHANGED
@@ -2,178 +2,160 @@
|
|
2
2
|
|
3
3
|
require "rubygems"
|
4
4
|
require "rest_client"
|
5
|
-
require "tmail"
|
6
|
-
require "iconv"
|
7
|
-
require 'mime/types' # requires both nokogiri and rcov. Yuck.
|
8
|
-
require 'date'
|
9
|
-
require 'pp'
|
10
5
|
|
11
|
-
##
|
12
|
-
##
|
13
|
-
##
|
14
|
-
##
|
15
|
-
##
|
6
|
+
##A ruby library API to Request Tracker's REST interface. Requires the
|
7
|
+
##rubygems rest-client, mail and mime-types to be installed. You can
|
8
|
+
##create a file name .rtclientrc in the same directory as client.rb with a
|
9
|
+
##default server/user/pass to connect to RT as, so that you don't have to
|
10
|
+
##specify it/update it in lots of different scripts.
|
16
11
|
##
|
17
12
|
## Thanks to Brian McArdle for patch dealing with spaces in Custom Fields.
|
18
13
|
## To reference custom fields in RT that have spaces with rt-client, use an
|
19
|
-
## underscore in the rt-client code
|
20
|
-
|
21
|
-
##
|
14
|
+
## underscore in the rt-client code, e.g. "CF.{Has_Space}"
|
15
|
+
##
|
16
|
+
## Thanks to Robert Vinson for 1.9.x compatibility when I couldn't be buggered
|
17
|
+
## and a great job of refactoring the old mess into something with much fewer
|
18
|
+
## dependencies.
|
19
|
+
##
|
20
|
+
##TODO: Streaming, chunking attachments in compose method
|
22
21
|
#
|
23
22
|
# See each method for sample usage. To use this, "gem install rt-client" and
|
24
23
|
#
|
25
24
|
# require "rt/client"
|
26
|
-
|
27
25
|
class RT_Client
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# Create a new RT_Client object. Load up our stored cookie and check it.
|
33
|
-
# Log into RT again if needed and store the new cookie. You can specify
|
34
|
-
# login and cookie storage directories in 3 different ways:
|
35
|
-
# 1. Explicity during object creation
|
36
|
-
# 2. From a .rtclientrc file in the working directory of your ruby program
|
37
|
-
# 3. From a .rtclientrc file in the same directory as the library itself
|
38
|
-
#
|
39
|
-
# These are listed in order of priority; if you have explicit parameters,
|
40
|
-
# they are always used, even if you have .rtclientrc files. If there
|
41
|
-
# is both an .rtclientrc in your program's working directory and
|
42
|
-
# in the library directory, the one from your program's working directory
|
43
|
-
# is used. If no parameters are specified either explicity or by use
|
44
|
-
# of a .rtclientrc, then the defaults of "rt_user", "rt_pass" are used
|
45
|
-
# with a default server of "http://localhost", and cookies are stored
|
46
|
-
# in the directory where the library resides.
|
47
|
-
#
|
48
|
-
# rt= RT_Client.new( :server => "https://tickets.ambulance.com/",
|
49
|
-
# :user => "rt_user",
|
50
|
-
# :pass => "rt_pass",
|
51
|
-
# :cookies => "/my/cookie/dir" )
|
52
|
-
#
|
53
|
-
# rt= RT_Client.new # use defaults from .rtclientrc
|
54
|
-
#
|
55
|
-
# .rtclientrc format:
|
56
|
-
# server=<RT server>
|
57
|
-
# user=<RT user>
|
58
|
-
# pass=<RT password>
|
59
|
-
# cookies=<directory>
|
60
|
-
def initialize(*params)
|
61
|
-
@boundary = "----xYzZY#{rand(1000000).to_s}xYzZY"
|
62
|
-
@version = "0.4.0"
|
63
|
-
@status = "Not connected"
|
64
|
-
@server = "http://localhost/"
|
65
|
-
@user = "rt_user"
|
66
|
-
@pass = "rt_pass"
|
67
|
-
@cookies = Dir.pwd
|
68
|
-
config_file = Dir.pwd + "/.rtclientrc"
|
69
|
-
config = ""
|
70
|
-
if File.file?(config_file)
|
71
|
-
config = File.read(config_file)
|
72
|
-
else
|
73
|
-
config_file = File.dirname(__FILE__) + "/.rtclientrc"
|
74
|
-
config = File.read(config_file) if File.file?(config_file)
|
75
|
-
end
|
76
|
-
@server = $~[1] if config =~ /\s*server\s*=\s*(.*)$/i
|
77
|
-
@user = $~[1] if config =~ /^\s*user\s*=\s*(.*)$/i
|
78
|
-
@pass = $~[1] if config =~ /^\s*pass\s*=\s*(.*)$/i
|
79
|
-
@cookies = $~[1] if config =~ /\s*cookies\s*=\s*(.*)$/i
|
80
|
-
@resource = "#{@server}REST/1.0/"
|
81
|
-
if params.class == Array && params[0].class == Hash
|
82
|
-
param = params[0]
|
83
|
-
@user = param[:user] if param.has_key? :user
|
84
|
-
@pass = param[:pass] if param.has_key? :pass
|
85
|
-
if param.has_key? :server
|
86
|
-
@server = param[:server]
|
87
|
-
@server += "/" if @server !~ /\/$/
|
88
|
-
@resource = "#{@server}REST/1.0/"
|
89
|
-
end
|
90
|
-
@cookies = param[:cookies] if param.has_key? :cookies
|
91
|
-
end
|
92
|
-
@login = { :user => @user, :pass => @pass }
|
93
|
-
cookiejar = "#{@cookies}/RT_Client.#{@user}.cookie" # cookie location
|
94
|
-
cookiejar.untaint
|
95
|
-
if File.file? cookiejar
|
96
|
-
@cookie = File.read(cookiejar).chomp
|
97
|
-
headers = { 'User-Agent' => UA,
|
98
|
-
'Content-Type' => "application/x-www-form-urlencoded",
|
99
|
-
'Cookie' => @cookie }
|
100
|
-
else
|
101
|
-
headers = { 'User-Agent' => UA,
|
102
|
-
'Content-Type' => "application/x-www-form-urlencoded" }
|
103
|
-
@cookie = ""
|
104
|
-
end
|
105
|
-
|
27
|
+
UA = "Mozilla/5.0 ruby RT Client Interface 0.6.4"
|
28
|
+
attr_reader :status, :site, :version, :cookies, :server, :user, :cookie
|
106
29
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
30
|
+
# Create a new RT_Client object. Load up our stored cookie and check it.
|
31
|
+
# Log into RT again if needed and store the new cookie. You can specify
|
32
|
+
# login and cookie storage directories in 3 different ways:
|
33
|
+
# 1. Explicity during object creation
|
34
|
+
# 2. From a .rtclientrc file in the working directory of your ruby program
|
35
|
+
# 3. From a .rtclientrc file in the same directory as the library itself
|
36
|
+
#
|
37
|
+
# These are listed in order of priority; if you have explicit parameters,
|
38
|
+
# they are always used, even if you have .rtclientrc files. If there
|
39
|
+
# is both an .rtclientrc in your program's working directory and
|
40
|
+
# in the library directory, the one from your program's working directory
|
41
|
+
# is used. If no parameters are specified either explicity or by use
|
42
|
+
# of a .rtclientrc, then the defaults of "rt_user", "rt_pass" are used
|
43
|
+
# with a default server of "http://localhost", and cookies are stored
|
44
|
+
# in the directory where the library resides.
|
45
|
+
#
|
46
|
+
# rt= RT_Client.new( :server => "https://tickets.ambulance.com/",
|
47
|
+
# :user => "rt_user",
|
48
|
+
# :pass => "rt_pass",
|
49
|
+
# :cookies => "/my/cookie/dir" )
|
50
|
+
#
|
51
|
+
# rt= RT_Client.new # use defaults from .rtclientrc
|
52
|
+
#
|
53
|
+
# .rtclientrc format:
|
54
|
+
# server=<RT server>
|
55
|
+
# user=<RT user>
|
56
|
+
# pass=<RT password>
|
57
|
+
# cookies=<directory>
|
58
|
+
def initialize(*params)
|
59
|
+
@boundary = "----xYzZY#{rand(1000000).to_s}xYzZY"
|
60
|
+
@version = "0.4.0"
|
61
|
+
@status = "Not connected"
|
62
|
+
@server = "http://localhost/"
|
63
|
+
@user = "rt_user"
|
64
|
+
@pass = "rt_pass"
|
65
|
+
@cookies = Dir.pwd
|
66
|
+
config_file = Dir.pwd + "/.rtclientrc"
|
67
|
+
config = ""
|
68
|
+
if File.file?(config_file)
|
69
|
+
config = File.read(config_file)
|
70
|
+
else
|
71
|
+
config_file = File.dirname(__FILE__) + "/.rtclientrc"
|
72
|
+
config = File.read(config_file) if File.file?(config_file)
|
73
|
+
end
|
74
|
+
@server = $~[1] if config =~ /\s*server\s*=\s*(.*)$/i
|
75
|
+
@user = $~[1] if config =~ /^\s*user\s*=\s*(.*)$/i
|
76
|
+
@pass = $~[1] if config =~ /^\s*pass\s*=\s*(.*)$/i
|
77
|
+
@cookies = $~[1] if config =~ /\s*cookies\s*=\s*(.*)$/i
|
78
|
+
@resource = "#{@server}REST/1.0/"
|
79
|
+
if params.class == Array && params[0].class == Hash
|
80
|
+
param = params[0]
|
81
|
+
@user = param[:user] if param.has_key? :user
|
82
|
+
@pass = param[:pass] if param.has_key? :pass
|
83
|
+
if param.has_key? :server
|
84
|
+
@server = param[:server]
|
85
|
+
@server += "/" if @server !~ /\/$/
|
86
|
+
@resource = "#{@server}REST/1.0/"
|
87
|
+
end
|
88
|
+
@cookies = param[:cookies] if param.has_key? :cookies
|
89
|
+
end
|
90
|
+
@login = { :user => @user, :pass => @pass }
|
91
|
+
cookiejar = "#{@cookies}/RT_Client.#{@user}.cookie" # cookie location
|
92
|
+
cookiejar.untaint
|
93
|
+
if File.file? cookiejar
|
94
|
+
@cookie = File.read(cookiejar).chomp
|
95
|
+
headers = { 'User-Agent' => UA,
|
96
|
+
'Content-Type' => "application/x-www-form-urlencoded",
|
97
|
+
'Cookie' => @cookie }
|
98
|
+
else
|
99
|
+
headers = { 'User-Agent' => UA,
|
100
|
+
'Content-Type' => "application/x-www-form-urlencoded" }
|
101
|
+
@cookie = ""
|
102
|
+
end
|
128
103
|
|
129
|
-
# gets the detail for a single ticket/user. If its a ticket, its without
|
130
|
-
# history or attachments (to get those use the history method) . If no
|
131
|
-
# type is specified, ticket is assumed. takes a single parameter
|
132
|
-
# containing the ticket/user id, and returns a hash of RT Fields => values
|
133
|
-
#
|
134
|
-
# hash = rt.show(822)
|
135
|
-
# hash = rt.show("822")
|
136
|
-
# hash = rt.show("ticket/822")
|
137
|
-
# hash = rt.show(:id => 822)
|
138
|
-
# hash = rt.show(:id => "822")
|
139
|
-
# hash = rt.show(:id => "ticket/822")
|
140
|
-
# hash = rt.show("user/#{login}")
|
141
|
-
# email = rt.show("user/somebody")["emailaddress"]
|
142
|
-
def show(id)
|
143
|
-
id = id[:id] if id.class == Hash
|
144
|
-
id = id.to_s
|
145
|
-
type = "ticket"
|
146
|
-
sid = id
|
147
|
-
if id =~ /(\w+)\/(.+)/
|
148
|
-
type = $~[1]
|
149
|
-
sid = $~[2]
|
150
|
-
end
|
151
|
-
reply = {}
|
152
|
-
if type.downcase == 'user'
|
153
|
-
resp = @site["#{type}/#{sid}"].get
|
154
|
-
else
|
155
|
-
resp = @site["#{type}/#{sid}/show"].get
|
156
|
-
end
|
157
|
-
resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss the HTTP response
|
158
|
-
resp.gsub!(/\n\n/,"\n") # remove double spacing, TMail stops at a blank line
|
159
|
-
while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
|
160
|
-
resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
|
161
|
-
end
|
162
|
-
return {:error => resp, } if resp =~ /does not exist./
|
163
|
-
th = TMail::Mail.parse(resp)
|
164
|
-
resp += "\nrtclientend:" # to make the pattern match on the last key in the response
|
165
|
-
th.each_header do |k,v|
|
166
|
-
reply["#{k}"] = v.to_s
|
167
|
-
kk = k.gsub(/\{/,"\\\{")
|
168
|
-
kk.gsub!(/\}/,"\\\}")
|
169
|
-
pattern = "^" + kk + ": (.*?)^[\\w\\.\\{\\}]+:"
|
170
|
-
temp = resp.match(/#{pattern}/mi) # TMail strips line breaks
|
171
|
-
reply["#{k}"] = temp[1].rstrip if temp.class != NilClass
|
172
|
-
end
|
173
|
-
reply
|
174
|
-
end
|
175
104
|
|
176
|
-
|
105
|
+
site = RestClient::Resource.new(@resource, :headers => headers, :timeout => 120)
|
106
|
+
data = site.post "" # a null post just to check that we are logged in
|
107
|
+
|
108
|
+
if @cookie.length == 0 or data =~ /401/ # we're not logged in
|
109
|
+
data = site.post @login, :headers => headers
|
110
|
+
# puts data
|
111
|
+
@cookie = data.headers[:set_cookie].first.split('; ')[0]
|
112
|
+
# write the new cookie
|
113
|
+
if @cookie !~ /nil/
|
114
|
+
f = File.new(cookiejar,"w")
|
115
|
+
f.puts @cookie
|
116
|
+
f.close
|
117
|
+
end
|
118
|
+
end
|
119
|
+
headers = { 'User-Agent' => UA,
|
120
|
+
'Content-Type' => "multipart/form-data; boundary=#{@boundary}",
|
121
|
+
'Cookie' => @cookie }
|
122
|
+
@site = RestClient::Resource.new(@resource, :headers => headers)
|
123
|
+
@status = data
|
124
|
+
self.untaint
|
125
|
+
end
|
126
|
+
|
127
|
+
# gets the detail for a single ticket/user. If its a ticket, its without
|
128
|
+
# history or attachments (to get those use the history method) . If no
|
129
|
+
# type is specified, ticket is assumed. takes a single parameter
|
130
|
+
# containing the ticket/user id, and returns a hash of RT Fields => values
|
131
|
+
#
|
132
|
+
# hash = rt.show(822)
|
133
|
+
# hash = rt.show("822")
|
134
|
+
# hash = rt.show("ticket/822")
|
135
|
+
# hash = rt.show(:id => 822)
|
136
|
+
# hash = rt.show(:id => "822")
|
137
|
+
# hash = rt.show(:id => "ticket/822")
|
138
|
+
# hash = rt.show("user/#{login}")
|
139
|
+
# email = rt.show("user/somebody")["emailaddress"]
|
140
|
+
def show(id)
|
141
|
+
id = id[:id] if id.class == Hash
|
142
|
+
id = id.to_s
|
143
|
+
type = "ticket"
|
144
|
+
sid = id
|
145
|
+
if id =~ /(\w+)\/(.+)/
|
146
|
+
type = $~[1]
|
147
|
+
sid = $~[2]
|
148
|
+
end
|
149
|
+
reply = {}
|
150
|
+
if type.downcase == 'user'
|
151
|
+
resp = @site["#{type}/#{sid}"].get
|
152
|
+
else
|
153
|
+
resp = @site["#{type}/#{sid}/show"].get
|
154
|
+
end
|
155
|
+
reply = response_to_h(resp)
|
156
|
+
end
|
157
|
+
|
158
|
+
# gets a list of ticket links for a ticket.
|
177
159
|
# takes a single parameter containing the ticket id,
|
178
160
|
# and returns a hash of RT Fields => values
|
179
161
|
#
|
@@ -194,47 +176,37 @@ class RT_Client
|
|
194
176
|
end
|
195
177
|
reply = {}
|
196
178
|
resp = @site["ticket/#{sid}/links/show"].get
|
197
|
-
resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss the HTTP response
|
198
|
-
resp.gsub!(/\n\n/,"\n") # remove double spacing, TMail stops at a blank line
|
199
|
-
while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
|
200
|
-
resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
|
201
|
-
end
|
202
179
|
return {:error => resp, } if resp =~ /does not exist./
|
203
|
-
|
204
|
-
th.each_header do |k,v|
|
205
|
-
reply["#{k}"] = v.to_s
|
206
|
-
end
|
207
|
-
reply
|
180
|
+
reply = response_to_h(resp)
|
208
181
|
end
|
209
182
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
# create a new user. Requires a hash of RT fields => values. Returns
|
183
|
+
# Creates a new ticket. Requires a hash that contains RT form fields as
|
184
|
+
# the keys. Capitalization is important; use :Queue, not :queue. You
|
185
|
+
# will need at least :Queue to create a ticket. For a full list of fields
|
186
|
+
# you can use, try "/opt/rt3/bin/rt edit ticket/1". Returns the newly
|
187
|
+
# created ticket number, or a complete REST response.
|
188
|
+
#
|
189
|
+
# id = rt.create( :Queue => "Customer Service",
|
190
|
+
# :Cc => "somebody\@email.com",
|
191
|
+
# :Subject => "I've fallen and I can't get up",
|
192
|
+
# :Text => "I think my hip is broken.\nPlease help.",
|
193
|
+
# :"CF.{CustomField}" => "Urgent",
|
194
|
+
def create(field_hash)
|
195
|
+
field_hash[:id] = "ticket/new"
|
196
|
+
payload = compose(field_hash)
|
197
|
+
puts "Payload for new ticket:"
|
198
|
+
puts payload
|
199
|
+
resp = @site['ticket/new/edit'].post payload
|
200
|
+
new_id = resp.match(/Ticket\s*(\d+)/)
|
201
|
+
if new_id.class == MatchData
|
202
|
+
new_ticket = new_id[1]
|
203
|
+
else
|
204
|
+
new_ticket = resp
|
205
|
+
end
|
206
|
+
new_ticket # return the ticket number, or the full REST response
|
207
|
+
end
|
208
|
+
|
209
|
+
# create a new user. Requires a hash of RT fields => values. Returns
|
238
210
|
# the newly created user ID, or the full REST response if there is an error.
|
239
211
|
# For a full list of possible parameters that you can specify, look at
|
240
212
|
# "/opt/rt/bin/rt edit user/1"
|
@@ -331,14 +303,8 @@ class RT_Client
|
|
331
303
|
raise "RT_Client.usersearch requires a user email in the 'EmailAddress' key."
|
332
304
|
end
|
333
305
|
resp = @site["user/#{email}"].get
|
334
|
-
resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss the HTTP response
|
335
|
-
reply = {}
|
336
306
|
return reply if resp =~ /No user named/
|
337
|
-
|
338
|
-
th.each_header do |k,v|
|
339
|
-
reply["#{k}"] = v.to_s
|
340
|
-
end
|
341
|
-
reply
|
307
|
+
reply = response_to_h(resp)
|
342
308
|
end
|
343
309
|
|
344
310
|
# correspond on a ticket. Requires a hash, which must have an :id key
|
@@ -363,98 +329,93 @@ class RT_Client
|
|
363
329
|
payload = compose(field_hash)
|
364
330
|
@site["ticket/#{id}/comment"].post payload
|
365
331
|
end
|
366
|
-
|
367
|
-
# Get a list of tickets matching some criteria.
|
368
|
-
# Takes a string Ticket-SQL query and an optional "order by" parameter.
|
369
|
-
# The order by is an RT field, prefix it with + for ascending
|
370
|
-
# or - for descending.
|
371
|
-
# Returns a nested array of arrays containing [ticket number, subject]
|
372
|
-
# The outer array is in the order requested.
|
373
|
-
#
|
374
|
-
# hash = rt.list(:query => "Queue = 'Sales'")
|
375
|
-
# hash = rt.list("Queue='Sales'")
|
376
|
-
# hash = rt.list(:query => "Queue = 'Sales'", :order => "-Id")
|
377
|
-
# hash = rt.list("Queue='Sales'","-Id")
|
378
|
-
def list(*params)
|
379
|
-
query = params[0]
|
380
|
-
order = ""
|
381
|
-
if params.size > 1
|
382
|
-
order = params[1]
|
383
|
-
end
|
384
|
-
if params[0].class == Hash
|
385
|
-
params = params[0]
|
386
|
-
query = params[:query] if params.has_key? :query
|
387
|
-
order = params[:order] if params.has_key? :order
|
388
|
-
end
|
389
|
-
reply = []
|
390
|
-
resp = @site["search/ticket/?query=#{URI.escape(query)}&orderby=#{order}&format=s"].get
|
391
|
-
raise "Invalid query (#{query})" if resp =~ /Invalid query/
|
392
|
-
resp = resp.split("\n") # convert to array of lines
|
393
|
-
resp.each do |line|
|
394
|
-
f = line.match(/^(\d+):\s*(.*)/)
|
395
|
-
reply.push [f[1],f[2]] if f.class == MatchData
|
396
|
-
end
|
397
|
-
reply
|
398
|
-
end
|
399
332
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
333
|
+
# Get a list of tickets matching some criteria.
|
334
|
+
# Takes a string Ticket-SQL query and an optional "order by" parameter.
|
335
|
+
# The order by is an RT field, prefix it with + for ascending
|
336
|
+
# or - for descending.
|
337
|
+
# Returns a nested array of arrays containing [ticket number, subject]
|
338
|
+
# The outer array is in the order requested.
|
339
|
+
#
|
340
|
+
# hash = rt.list(:query => "Queue = 'Sales'")
|
341
|
+
# hash = rt.list("Queue='Sales'")
|
342
|
+
# hash = rt.list(:query => "Queue = 'Sales'", :order => "-Id")
|
343
|
+
# hash = rt.list("Queue='Sales'","-Id")
|
344
|
+
def list(*params)
|
345
|
+
query = params[0]
|
346
|
+
order = ""
|
347
|
+
if params.size > 1
|
348
|
+
order = params[1]
|
349
|
+
end
|
350
|
+
if params[0].class == Hash
|
351
|
+
params = params[0]
|
352
|
+
query = params[:query] if params.has_key? :query
|
353
|
+
order = params[:order] if params.has_key? :order
|
354
|
+
end
|
355
|
+
reply = []
|
356
|
+
resp = @site["search/ticket/?query=#{URI.escape(query)}&orderby=#{order}&format=s"].get
|
357
|
+
raise "Invalid query (#{query})" if resp =~ /Invalid query/
|
358
|
+
resp = resp.split("\n") # convert to array of lines
|
359
|
+
resp.each do |line|
|
360
|
+
f = line.match(/^(\d+):\s*(.*)/)
|
361
|
+
reply.push [f[1],f[2]] if f.class == MatchData
|
362
|
+
end
|
363
|
+
reply
|
364
|
+
end
|
365
|
+
|
366
|
+
# A more extensive(expensive) query then the list method. Takes the same
|
367
|
+
# parameters as the list method; a string Ticket-SQL query and optional
|
368
|
+
# order, but returns a lot more information. Instead of just the ID and
|
369
|
+
# subject, you get back an array of hashes, where each hash represents
|
370
|
+
# one ticket, indentical to what you get from the show method (which only
|
371
|
+
# acts on one ticket). Use with caution; this can take a long time to
|
372
|
+
# execute.
|
373
|
+
#
|
374
|
+
# array = rt.query("Queue='Sales'")
|
375
|
+
# array = rt.query(:query => "Queue='Sales'",:order => "+Id")
|
376
|
+
# array = rt.query("Queue='Sales'","+Id")
|
377
|
+
# => array[0] = { "id" => "123", "requestors" => "someone@..", etc etc }
|
378
|
+
# => array[1] = { "id" => "126", "requestors" => "someone@else..", etc etc }
|
379
|
+
# => array[0]["id"] = "123"
|
380
|
+
def query(*params)
|
381
|
+
query = params[0]
|
382
|
+
order = ""
|
383
|
+
if params.size > 1
|
384
|
+
order = params[1]
|
385
|
+
end
|
386
|
+
if params[0].class == Hash
|
387
|
+
params = params[0]
|
388
|
+
query = params[:query] if params.has_key? :query
|
389
|
+
order = params[:order] if params.has_key? :order
|
390
|
+
end
|
391
|
+
replies = []
|
392
|
+
resp = @site["search/ticket/?query=#{URI.escape(query)}&orderby=#{order}&format=l"].get
|
393
|
+
return replies if resp =~/No matching results./
|
394
|
+
raise "Invalid query (#{query})" if resp =~ /Invalid query/
|
395
|
+
resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # strip HTTP response
|
396
|
+
tickets = resp.split("\n--\n") # -- occurs between each ticket
|
397
|
+
tickets.each do |ticket|
|
398
|
+
ticket_h = response_to_h(ticket)
|
399
|
+
reply = {}
|
400
|
+
ticket_h.each do |k,v|
|
401
|
+
case k
|
402
|
+
when 'created','due','told','lastupdated','started'
|
403
|
+
begin
|
404
|
+
vv = DateTime.parse(v.to_s)
|
405
|
+
reply["#{k}"] = vv.strftime("%Y-%m-%d %H:%M:%S")
|
406
|
+
rescue ArgumentError
|
407
|
+
reply["#{k}"] = v.to_s
|
408
|
+
end
|
409
|
+
else
|
410
|
+
reply["#{k}"] = v.to_s
|
411
|
+
end
|
412
|
+
end
|
413
|
+
replies.push reply
|
414
|
+
end
|
415
|
+
replies
|
416
|
+
end
|
417
|
+
|
418
|
+
# Get a list of history transactions for a ticket. Takes a ticket ID and
|
458
419
|
# an optional format parameter. If the format is ommitted, the short
|
459
420
|
# format is assumed. If the short format is requested, it returns an
|
460
421
|
# array of 2 element arrays, where each 2-element array is [ticket_id,
|
@@ -496,6 +457,11 @@ class RT_Client
|
|
496
457
|
id = id.to_s
|
497
458
|
id = $~[1] if id =~ /ticket\/(\d+)/
|
498
459
|
resp = @site["ticket/#{id}/history?format=#{format[0,1]}"].get
|
460
|
+
resp.gsub!(0x1C.chr,'[FS]')
|
461
|
+
resp.gsub!(0x1D.chr,'[GS]')
|
462
|
+
resp.gsub!(0x1E.chr,'[RS]')
|
463
|
+
resp.gsub!(0x1F.chr,'[US]')
|
464
|
+
|
499
465
|
if format[0,1] == "s"
|
500
466
|
if comments
|
501
467
|
h = resp.split("\n").select{ |l| l =~ /^\d+:/ }
|
@@ -510,13 +476,14 @@ class RT_Client
|
|
510
476
|
while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
|
511
477
|
resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
|
512
478
|
end
|
479
|
+
|
513
480
|
items = resp.split("\n--\n")
|
514
481
|
list = []
|
515
482
|
items.each do |item|
|
516
|
-
th =
|
483
|
+
th = response_to_h(item)
|
517
484
|
next if not comments and th["type"].to_s =~ /Comment/ # skip comments
|
518
485
|
reply = {}
|
519
|
-
th.
|
486
|
+
th.each do |k,v|
|
520
487
|
attachments = []
|
521
488
|
case k
|
522
489
|
when "attachments"
|
@@ -540,7 +507,7 @@ class RT_Client
|
|
540
507
|
end
|
541
508
|
when "content"
|
542
509
|
reply["content"] = v.to_s
|
543
|
-
temp = item.match(/^Content: (.*?)^\w+:/m)
|
510
|
+
temp = item.match(/^Content: (.*?)^\w+:/m)
|
544
511
|
reply["content"] = temp[1] if temp.class != NilClass
|
545
512
|
else
|
546
513
|
reply["#{k}"] = v.to_s
|
@@ -571,7 +538,6 @@ class RT_Client
|
|
571
538
|
id = params[:id] if params.has_key? :id
|
572
539
|
history = params[:history] if params.has_key? :history
|
573
540
|
end
|
574
|
-
reply = {}
|
575
541
|
resp = @site["ticket/#{id}/history/id/#{history}"].get
|
576
542
|
return reply if resp =~ /not related/ # history id must be related to the ticket id
|
577
543
|
resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss the HTTP response
|
@@ -580,9 +546,9 @@ class RT_Client
|
|
580
546
|
while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
|
581
547
|
resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
|
582
548
|
end
|
583
|
-
th =
|
549
|
+
th = response_to_h(resp)
|
584
550
|
attachments = []
|
585
|
-
th.
|
551
|
+
th.each do |k,v|
|
586
552
|
case k
|
587
553
|
when "attachments"
|
588
554
|
temp = resp.match(/Attachments:\s*(.*)[^\w|$]/m)
|
@@ -605,7 +571,7 @@ class RT_Client
|
|
605
571
|
end
|
606
572
|
when "content"
|
607
573
|
reply["content"] = v.to_s
|
608
|
-
temp = resp.match(/^Content: (.*?)^\w+:/m)
|
574
|
+
temp = resp.match(/^Content: (.*?)^\w+:/m)
|
609
575
|
reply["content"] = temp[1] if temp.class != NilClass
|
610
576
|
else
|
611
577
|
reply["#{k}"] = v.to_s
|
@@ -635,7 +601,7 @@ class RT_Client
|
|
635
601
|
while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
|
636
602
|
resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
|
637
603
|
end
|
638
|
-
th =
|
604
|
+
th = response_to_h(resp)
|
639
605
|
list = []
|
640
606
|
pattern = /(\d+:\s.*?\)),/
|
641
607
|
match = pattern.match(th['attachments'].to_s)
|
@@ -694,9 +660,9 @@ class RT_Client
|
|
694
660
|
while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/) #replace CF spaces with underscores
|
695
661
|
resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
|
696
662
|
end
|
697
|
-
headers =
|
663
|
+
headers = response_to_h(resp)
|
698
664
|
reply = {}
|
699
|
-
headers.
|
665
|
+
headers.each do |k,v|
|
700
666
|
reply["#{k}"] = v.to_s
|
701
667
|
end
|
702
668
|
content = resp.match(/Content:\s+(.*)/m)[1]
|
@@ -765,63 +731,84 @@ class RT_Client
|
|
765
731
|
end
|
766
732
|
end
|
767
733
|
|
768
|
-
# don't give up the password when the object is inspected
|
769
|
-
def inspect # :nodoc:
|
770
|
-
mystr = super()
|
771
|
-
mystr.gsub!(/(.)pass=.*?([,\}])/,"\\1pass=<hidden>\\2")
|
772
|
-
mystr
|
773
|
-
end
|
774
|
-
|
775
|
-
private
|
776
|
-
|
777
|
-
# Private helper for composing RT's "forms". Requires a hash where the
|
778
|
-
# keys are field names for an RT form. If there's a :Text key, the value
|
779
|
-
# is modified to insert whitespace on continuation lines. If there's an
|
780
|
-
# :Attachment key, the value is assumed to be a comma-separated list of
|
781
|
-
# filenames to attach. It returns a multipart MIME body complete
|
782
|
-
# with boundaries and headers, suitable for an HTTP POST.
|
783
|
-
def compose(fields) # :doc:
|
784
|
-
body = ""
|
785
|
-
if fields.class != Hash
|
786
|
-
raise "RT_Client.compose requires parameters as a hash."
|
787
|
-
end
|
788
|
-
|
789
|
-
# fixup Text field for RFC822 compliance
|
790
|
-
if fields.has_key? :Text
|
791
|
-
fields[:Text].gsub!(/\n/,"\n ") # insert a space on continuation lines.
|
792
|
-
end
|
793
734
|
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
735
|
+
# don't give up the password when the object is inspected
|
736
|
+
def inspect # :nodoc:
|
737
|
+
mystr = super()
|
738
|
+
mystr.gsub!(/(.)pass=.*?([,\}])/,"\\1pass=<hidden>\\2")
|
739
|
+
mystr
|
740
|
+
end
|
741
|
+
|
742
|
+
# helper to convert responses from RT REST to a hash
|
743
|
+
def response_to_h(resp)
|
744
|
+
resp.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"") # toss the HTTP response
|
745
|
+
#resp.gsub!(/\n\n/,"\n") # remove double spacing, TMail stops at a blank line
|
746
|
+
|
747
|
+
# unfold folded fields
|
748
|
+
# A newline followed by one or more spaces is treated as a
|
749
|
+
# single space
|
750
|
+
resp.gsub!(/\n +/, " ")
|
751
|
+
|
752
|
+
#replace CF spaces with underscores
|
753
|
+
while resp.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/)
|
754
|
+
resp.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
|
755
|
+
end
|
756
|
+
return {:error => resp } if resp =~ /does not exist./
|
757
|
+
|
758
|
+
# convert fields to key value pairs
|
759
|
+
ret = {}
|
760
|
+
resp.each_line do |ln|
|
761
|
+
next unless ln =~ /^.+?:/
|
762
|
+
ln_a = ln.split(/:/,2)
|
763
|
+
ln_a.map! {|item| item.strip}
|
764
|
+
ln_a[0].downcase!
|
765
|
+
ret[ln_a[0]] = ln_a[1]
|
766
|
+
end
|
767
|
+
|
768
|
+
return ret
|
769
|
+
end
|
770
|
+
|
771
|
+
# Helper for composing RT's "forms". Requires a hash where the
|
772
|
+
# keys are field names for an RT form. If there's a :Text key, the value
|
773
|
+
# is modified to insert whitespace on continuation lines. If there's an
|
774
|
+
# :Attachment key, the value is assumed to be a comma-separated list of
|
775
|
+
# filenames to attach. It returns a hash to be used with rest-client's
|
776
|
+
# payload class
|
777
|
+
def compose(fields) # :doc:
|
778
|
+
if fields.class != Hash
|
779
|
+
raise "RT_Client.compose requires parameters as a hash."
|
780
|
+
end
|
781
|
+
|
782
|
+
payload = { :multipart => true }
|
783
|
+
|
784
|
+
# attachments
|
785
|
+
if fields.has_key? :Attachments
|
786
|
+
fields[:Attachment] = fields[:Attachments]
|
787
|
+
fields.delete :Attachments
|
788
|
+
end
|
789
|
+
if fields.has_key? :Attachment
|
790
|
+
filenames = fields[:Attachment].split(',')
|
791
|
+
attachment_num = 1
|
792
|
+
filenames.each do |f|
|
793
|
+
payload["attachment_#{attachment_num.to_s}"] = File.new(f)
|
794
|
+
attachment_num += 1
|
795
|
+
end
|
796
|
+
# strip paths from filenames
|
797
|
+
fields[:Attachment] = filenames.map {|f| File.basename(f)}.join(',')
|
798
|
+
end
|
799
|
+
|
800
|
+
# fixup Text field for RFC822 compliance
|
801
|
+
if fields.has_key? :Text
|
802
|
+
fields[:Text].gsub!(/\n/,"\n ") # insert a space on continuation lines.
|
803
|
+
end
|
804
|
+
|
805
|
+
field_array = fields.map { |k,v| "#{k}: #{v}" }
|
806
|
+
content = field_array.join("\n") # our form
|
807
|
+
payload["content"] = content
|
808
|
+
|
809
|
+
return payload
|
810
|
+
end
|
811
|
+
|
827
812
|
end
|
813
|
+
|
814
|
+
|
data/rt/rtxmlsrv.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rt-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 5
|
9
|
-
- 0
|
10
|
-
version: 0.5.0
|
4
|
+
prerelease:
|
5
|
+
version: 0.6.4
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Tom Lahti
|
@@ -15,8 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2013-
|
19
|
-
default_executable:
|
13
|
+
date: 2013-07-31 00:00:00 Z
|
20
14
|
dependencies:
|
21
15
|
- !ruby/object:Gem::Dependency
|
22
16
|
name: rest-client
|
@@ -26,105 +20,9 @@ dependencies:
|
|
26
20
|
requirements:
|
27
21
|
- - ">="
|
28
22
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 25
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
- 9
|
33
23
|
version: "0.9"
|
34
24
|
type: :runtime
|
35
25
|
version_requirements: *id001
|
36
|
-
- !ruby/object:Gem::Dependency
|
37
|
-
name: tmail
|
38
|
-
prerelease: false
|
39
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
-
none: false
|
41
|
-
requirements:
|
42
|
-
- - ">="
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
hash: 31
|
45
|
-
segments:
|
46
|
-
- 1
|
47
|
-
- 2
|
48
|
-
- 0
|
49
|
-
version: 1.2.0
|
50
|
-
type: :runtime
|
51
|
-
version_requirements: *id002
|
52
|
-
- !ruby/object:Gem::Dependency
|
53
|
-
name: mime-types
|
54
|
-
prerelease: false
|
55
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
|
-
requirements:
|
58
|
-
- - ">="
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
hash: 47
|
61
|
-
segments:
|
62
|
-
- 1
|
63
|
-
- 16
|
64
|
-
version: "1.16"
|
65
|
-
type: :runtime
|
66
|
-
version_requirements: *id003
|
67
|
-
- !ruby/object:Gem::Dependency
|
68
|
-
name: archive-tar-minitar
|
69
|
-
prerelease: false
|
70
|
-
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
-
none: false
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
hash: 1
|
76
|
-
segments:
|
77
|
-
- 0
|
78
|
-
- 5
|
79
|
-
version: "0.5"
|
80
|
-
type: :runtime
|
81
|
-
version_requirements: *id004
|
82
|
-
- !ruby/object:Gem::Dependency
|
83
|
-
name: nokogiri
|
84
|
-
prerelease: false
|
85
|
-
requirement: &id005 !ruby/object:Gem::Requirement
|
86
|
-
none: false
|
87
|
-
requirements:
|
88
|
-
- - ">="
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
hash: 11
|
91
|
-
segments:
|
92
|
-
- 1
|
93
|
-
- 2
|
94
|
-
version: "1.2"
|
95
|
-
type: :runtime
|
96
|
-
version_requirements: *id005
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: hoe
|
99
|
-
prerelease: false
|
100
|
-
requirement: &id006 !ruby/object:Gem::Requirement
|
101
|
-
none: false
|
102
|
-
requirements:
|
103
|
-
- - ">="
|
104
|
-
- !ruby/object:Gem::Version
|
105
|
-
hash: 51
|
106
|
-
segments:
|
107
|
-
- 1
|
108
|
-
- 9
|
109
|
-
- 0
|
110
|
-
version: 1.9.0
|
111
|
-
type: :runtime
|
112
|
-
version_requirements: *id006
|
113
|
-
- !ruby/object:Gem::Dependency
|
114
|
-
name: simplecov
|
115
|
-
prerelease: false
|
116
|
-
requirement: &id007 !ruby/object:Gem::Requirement
|
117
|
-
none: false
|
118
|
-
requirements:
|
119
|
-
- - ">="
|
120
|
-
- !ruby/object:Gem::Version
|
121
|
-
hash: 5
|
122
|
-
segments:
|
123
|
-
- 0
|
124
|
-
- 7
|
125
|
-
version: "0.7"
|
126
|
-
type: :runtime
|
127
|
-
version_requirements: *id007
|
128
26
|
description: " RT_Client is a ruby object that accesses the REST interface version 1.0\n of a Request Tracker instance. See http://www.bestpractical.com/ for\n Request Tracker.\n"
|
129
27
|
email: tlahti@dmsolutions.com
|
130
28
|
executables: []
|
@@ -136,7 +34,6 @@ extra_rdoc_files: []
|
|
136
34
|
files:
|
137
35
|
- rt/client.rb
|
138
36
|
- rt/rtxmlsrv.rb
|
139
|
-
has_rdoc: true
|
140
37
|
homepage:
|
141
38
|
licenses: []
|
142
39
|
|
@@ -152,25 +49,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
152
49
|
requirements:
|
153
50
|
- - ">="
|
154
51
|
- !ruby/object:Gem::Version
|
155
|
-
hash: 59
|
156
|
-
segments:
|
157
|
-
- 1
|
158
|
-
- 8
|
159
|
-
- 6
|
160
52
|
version: 1.8.6
|
161
53
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
54
|
none: false
|
163
55
|
requirements:
|
164
56
|
- - ">="
|
165
57
|
- !ruby/object:Gem::Version
|
166
|
-
hash: 3
|
167
|
-
segments:
|
168
|
-
- 0
|
169
58
|
version: "0"
|
170
59
|
requirements:
|
171
60
|
- A working installation of RT with the REST 1.0 interface
|
172
61
|
rubyforge_project:
|
173
|
-
rubygems_version: 1.
|
62
|
+
rubygems_version: 1.8.24
|
174
63
|
signing_key:
|
175
64
|
specification_version: 3
|
176
65
|
summary: Ruby object for RT access via REST
|