fiddler 0.0.1 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +70 -0
- data/lib/fiddler.rb +5 -1
- data/lib/fiddler/attachment.rb +142 -0
- data/lib/fiddler/configuration.rb +7 -2
- data/lib/fiddler/connection_manager.rb +62 -20
- data/lib/fiddler/errors.rb +1 -0
- data/lib/fiddler/extensions.rb +2 -0
- data/lib/fiddler/extensions/file.rb +5 -0
- data/lib/fiddler/extensions/hash.rb +15 -0
- data/lib/fiddler/formatters/base_formatter.rb +3 -0
- data/lib/fiddler/formatters/search_request_formatter.rb +2 -2
- data/lib/fiddler/history.rb +32 -0
- data/lib/fiddler/parsers.rb +3 -1
- data/lib/fiddler/parsers/attachment_parser.rb +54 -0
- data/lib/fiddler/parsers/base_parser.rb +41 -3
- data/lib/fiddler/parsers/history_parser.rb +72 -0
- data/lib/fiddler/parsers/ticket_parser.rb +52 -21
- data/lib/fiddler/ticket.rb +141 -43
- data/lib/fiddler/version.rb +1 -1
- data/spec/attachment_spec.rb +26 -0
- data/spec/cassettes/change-ownership-take.yml +98 -0
- data/spec/cassettes/get-tickets.yml +89087 -0
- data/spec/cassettes/reply-to-tickets.yml +232 -0
- data/spec/cassettes/root-request.yml +79 -0
- data/spec/cassettes/search-tickets.yml +38249 -0
- data/spec/cassettes/ticket-histories-count.yml +2195 -0
- data/spec/cassettes/ticket-histories.yml +954 -0
- data/spec/connection_manager_spec.rb +5 -1
- data/spec/formatters/search_request_formatter_spec.rb +13 -0
- data/spec/parsers/attachment_parser_spec.rb +29 -0
- data/spec/parsers/base_parser_spec.rb +16 -0
- data/spec/parsers/history_parser_spec.rb +32 -0
- data/spec/parsers/ticket_parser_spec.rb +5 -1
- data/spec/spec_helper.rb +9 -0
- data/spec/ticket_spec.rb +188 -6
- metadata +80 -9
data/lib/fiddler/parsers.rb
CHANGED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Fiddler
|
2
|
+
module Parsers
|
3
|
+
class AttachmentParser < BaseParser
|
4
|
+
def self.parse_single(response)
|
5
|
+
response = check_response_code(response)
|
6
|
+
response = check_for_errors(response)
|
7
|
+
attachment_from_response(response)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.parse_content(response)
|
11
|
+
response = check_response_code(response,false,false)
|
12
|
+
return response.join("\n")
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def self.check_for_errors(response)
|
18
|
+
if response.length == 1
|
19
|
+
raise Fiddler::FiddlerError, response.first
|
20
|
+
end
|
21
|
+
response
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.attachment_from_response(response)
|
25
|
+
result = {}
|
26
|
+
collect_headers = false
|
27
|
+
headers = []
|
28
|
+
response.each do |line|
|
29
|
+
matches = /^(\S*?):\s(.*)/.match(line)
|
30
|
+
if(matches)
|
31
|
+
key = matches[1].underscore
|
32
|
+
result[key] = matches[2]
|
33
|
+
|
34
|
+
if key == "headers"
|
35
|
+
collect_headers = true
|
36
|
+
next
|
37
|
+
elsif key == "content"
|
38
|
+
break
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
spaced_content_matches = /^\s{9}(.*)$/.match(line)
|
43
|
+
if spaced_content_matches and collect_headers
|
44
|
+
headers << spaced_content_matches[1]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
result.delete("content")
|
48
|
+
headers.unshift(result["headers"])
|
49
|
+
result["headers"] = headers
|
50
|
+
attachment = Fiddler::Attachment.new(result)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -5,19 +5,57 @@ module Fiddler
|
|
5
5
|
SUCCESS_CODES = (200..299).to_a
|
6
6
|
ERROR_CODES = (400..499).to_a
|
7
7
|
|
8
|
-
def self.check_response_code(response)
|
9
|
-
|
8
|
+
def self.check_response_code(response, reject_blank_lines=true, raise_error_for_empty_response=true)
|
9
|
+
response = safe_encode(response)
|
10
|
+
lines = response.split("\n")
|
11
|
+
lines = lines.delete_if { |l| l.nil? or l == "" } if reject_blank_lines
|
10
12
|
if lines.count == 0
|
11
|
-
|
13
|
+
if raise_error_for_empty_response
|
14
|
+
raise RequestError, "Empty Response"
|
15
|
+
else
|
16
|
+
return []
|
17
|
+
end
|
12
18
|
else
|
13
19
|
status_line = lines.shift
|
14
20
|
version, status_code, status_text = status_line.split(/\s+/,2)
|
15
21
|
unless SUCCESS_CODES.include?(status_code.to_i)
|
16
22
|
raise RequestError, status_text
|
17
23
|
end
|
24
|
+
lines.shift unless reject_blank_lines
|
18
25
|
lines
|
19
26
|
end
|
20
27
|
end
|
28
|
+
|
29
|
+
def self.tokenize_response(response)
|
30
|
+
tokens = Array.new
|
31
|
+
current = Array.new
|
32
|
+
response.each do |item|
|
33
|
+
if(item == "--")
|
34
|
+
tokens << current
|
35
|
+
current = Array.new
|
36
|
+
next
|
37
|
+
end
|
38
|
+
current << item
|
39
|
+
end
|
40
|
+
tokens << current unless current.empty?
|
41
|
+
tokens
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def self.safe_encode(response)
|
47
|
+
require 'iconv' unless String.method_defined?(:encode)
|
48
|
+
if String.method_defined?(:encode)
|
49
|
+
response.encode!('UTF-8', 'UTF-8', :invalid => :replace)
|
50
|
+
else
|
51
|
+
begin
|
52
|
+
Iconv.iconv("UTF-8//IGNORE", "UTF-8", response).join("")
|
53
|
+
rescue Exception => e
|
54
|
+
raise IllegalCharacterError, "Attachment contains illegal characters"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
response
|
58
|
+
end
|
21
59
|
end
|
22
60
|
end
|
23
61
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Fiddler
|
2
|
+
module Parsers
|
3
|
+
class HistoryParser < BaseParser
|
4
|
+
def self.parse_single(response)
|
5
|
+
response = check_response_code(response)
|
6
|
+
response = check_for_errors(response)
|
7
|
+
history_from_response(response)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.parse_multiple(response)
|
11
|
+
response = check_response_code(response)
|
12
|
+
response = check_for_errors(response)
|
13
|
+
token_responses = tokenize_response(response)
|
14
|
+
histories = Array.new
|
15
|
+
token_responses.each do |token_response|
|
16
|
+
histories << history_from_response(token_response)
|
17
|
+
end
|
18
|
+
histories
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def self.check_for_errors(response)
|
24
|
+
if response.length == 1
|
25
|
+
raise Fiddler::FiddlerError, response.first
|
26
|
+
else
|
27
|
+
response.shift
|
28
|
+
end
|
29
|
+
response
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.history_from_response(response)
|
33
|
+
result = {}
|
34
|
+
content_lines = []
|
35
|
+
result["attachment_ids"] = []
|
36
|
+
collect_content = false
|
37
|
+
collect_attachments = false
|
38
|
+
response.each do |line|
|
39
|
+
matches = /^(\S*?):\s(.*)/.match(line)
|
40
|
+
if(matches)
|
41
|
+
key = matches[1].underscore
|
42
|
+
result[key] = matches[2]
|
43
|
+
if key == "content"
|
44
|
+
collect_content = true
|
45
|
+
elsif key == "attachments"
|
46
|
+
collect_content = false
|
47
|
+
collect_attachments = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if collect_content
|
52
|
+
content_matches = /^\s{9}(.*)$/.match(line)
|
53
|
+
if(content_matches)
|
54
|
+
content_lines << content_matches[1]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if collect_attachments
|
59
|
+
attachment_matches = /^\s{13}(.*?):\s(.*)/.match(line)
|
60
|
+
if(attachment_matches)
|
61
|
+
result["attachment_ids"] << attachment_matches[1]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
result["attachment_ids"].count.times { content_lines.pop }
|
66
|
+
result["content"] += content_lines.join("\n")
|
67
|
+
result.delete("attachments")
|
68
|
+
history = Fiddler::History.new(result)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -10,12 +10,39 @@ module Fiddler
|
|
10
10
|
def self.parse_multiple(response)
|
11
11
|
response = check_response_code(response)
|
12
12
|
response = check_for_errors(response)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
if response.count == 0
|
14
|
+
[]
|
15
|
+
else
|
16
|
+
ticket_token_responses = tokenize_response(response)
|
17
|
+
tickets = Array.new
|
18
|
+
ticket_token_responses.each do |token_response|
|
19
|
+
tickets << ticket_from_response(token_response)
|
20
|
+
end
|
21
|
+
tickets
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.parse_reply_response(response)
|
26
|
+
response = check_response_code(response)
|
27
|
+
return !response.first.match(/^# Message recorded/).nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_change_ownership_response(response)
|
31
|
+
response = check_response_code(response)
|
32
|
+
if response.first =~ /^# Owner changed from (\S+) to (\S+)/
|
33
|
+
return $2
|
34
|
+
else
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.parse_update_response(response, method)
|
40
|
+
response = check_response_code(response)
|
41
|
+
if method == :create
|
42
|
+
return response.first =~ /^# Ticket (\S+) created/ ? $1 : nil
|
43
|
+
elsif method == :update
|
44
|
+
return response.first =~ /^# Ticket (\S+) updated/ ? $1 : nil
|
17
45
|
end
|
18
|
-
tickets
|
19
46
|
end
|
20
47
|
|
21
48
|
protected
|
@@ -24,35 +51,39 @@ module Fiddler
|
|
24
51
|
message = response.first.strip
|
25
52
|
if message =~ /^#/
|
26
53
|
raise Fiddler::TicketNotFoundError, message
|
54
|
+
elsif message == "No matching results."
|
55
|
+
response = []
|
27
56
|
end
|
28
57
|
response
|
29
58
|
end
|
30
59
|
|
31
60
|
def self.ticket_from_response(response)
|
32
61
|
result = {}
|
62
|
+
prev_key = nil
|
33
63
|
response.each do |line|
|
34
|
-
matches = /^(
|
64
|
+
matches = /^(\S*?):\s(.*)/.match(line)
|
35
65
|
if(matches)
|
36
66
|
key = matches[1].underscore
|
67
|
+
prev_key = key
|
37
68
|
result[key] = matches[2]
|
69
|
+
else
|
70
|
+
whitespace_matches = /^\s{12}(.*)/.match(line)
|
71
|
+
if whitespace_matches and prev_key
|
72
|
+
values = result[prev_key]
|
73
|
+
values = values.split(",") unless values.is_a?(Array)
|
74
|
+
result[prev_key] = values.concat(whitespace_matches[1].split(",")).collect { |x| x.strip }
|
75
|
+
end
|
38
76
|
end
|
39
77
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
tokens = Array.new
|
45
|
-
current = Array.new
|
46
|
-
response.each do |item|
|
47
|
-
if(item == "--")
|
48
|
-
tokens << current
|
49
|
-
current = Array.new
|
50
|
-
next
|
51
|
-
end
|
52
|
-
current << item
|
78
|
+
begin
|
79
|
+
id = result['id'].scan(/^ticket\/(\d*)$/).first.first.to_i
|
80
|
+
rescue
|
81
|
+
raise RequestError, "Unexpected response for id : #{result['id']}"
|
53
82
|
end
|
54
|
-
|
83
|
+
ticket = Fiddler::Ticket.new(result)
|
84
|
+
ticket.id = id
|
85
|
+
return ticket
|
55
86
|
end
|
56
87
|
end
|
57
88
|
end
|
58
|
-
end
|
89
|
+
end
|
data/lib/fiddler/ticket.rb
CHANGED
@@ -1,42 +1,147 @@
|
|
1
|
+
require 'active_attr'
|
1
2
|
module Fiddler
|
2
3
|
class Ticket
|
4
|
+
include ActiveAttr::Model
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
+
attribute :id, :default => 'ticket/new'
|
7
|
+
attribute :queue
|
8
|
+
attribute :owner
|
9
|
+
attribute :creator
|
10
|
+
attribute :subject
|
11
|
+
attribute :status
|
12
|
+
attribute :priority
|
13
|
+
attribute :initial_priority
|
14
|
+
attribute :final_priority
|
15
|
+
attribute :requestors
|
16
|
+
attribute :cc
|
17
|
+
attribute :admin_cc
|
18
|
+
attribute :created
|
19
|
+
attribute :starts
|
20
|
+
attribute :started
|
21
|
+
attribute :due
|
22
|
+
attribute :resolved
|
23
|
+
attribute :told
|
24
|
+
attribute :last_updated
|
25
|
+
attribute :time_estimated
|
26
|
+
attribute :time_worked
|
27
|
+
attribute :time_left
|
28
|
+
attribute :text
|
6
29
|
|
7
|
-
|
8
|
-
|
9
|
-
|
30
|
+
validates_presence_of :id, :queue, :subject
|
31
|
+
|
32
|
+
def histories
|
33
|
+
if @histories == nil
|
34
|
+
url = "ticket/#{id}/history"
|
35
|
+
response = Fiddler::ConnectionManager.get(url, {:format => "l"})
|
36
|
+
@histories = Fiddler::Parsers::HistoryParser.parse_multiple(response)
|
37
|
+
end
|
38
|
+
@histories
|
39
|
+
end
|
40
|
+
|
41
|
+
# make the setting and getting of requestors easier
|
42
|
+
def requestor_array
|
43
|
+
self.requestors.split(",").collect { |x| x.strip }
|
44
|
+
end
|
45
|
+
|
46
|
+
def requestor_array=(requestor_array)
|
47
|
+
if requestor_array.is_a?(Array)
|
48
|
+
self.requestors = requestor_array.join(", ")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add a comment to a ticket
|
53
|
+
# Example:
|
54
|
+
# tix = Ticket.get(1000)
|
55
|
+
# tix.comment("This is a comment", :time_worked => 45, :cc => 'someone@example.com')
|
56
|
+
#
|
57
|
+
# Attachments
|
58
|
+
# tix.comment("This is a comment", :attachments => "/tmp/filename.txt")
|
59
|
+
#
|
60
|
+
# Attachment as a file descriptor
|
61
|
+
# tix.correspond("This is a comment", :attachments => File.new("/tmp/filename.txt"))
|
10
62
|
#
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
63
|
+
# Attachment as a ActionDispatch::Http::UploadedFile instance
|
64
|
+
# tix.comment("This is a comment", :attachments => [params[:attachment_1], params[:attachment_2]])
|
65
|
+
#
|
66
|
+
def comment(comment, opt = {})
|
67
|
+
reply('Comment', comment, opt)
|
68
|
+
end
|
69
|
+
|
70
|
+
def correspond(comment, opt = {})
|
71
|
+
reply('Correspond', comment, opt)
|
72
|
+
end
|
73
|
+
|
74
|
+
def steal
|
75
|
+
change_ownership "Steal"
|
76
|
+
end
|
77
|
+
|
78
|
+
def take
|
79
|
+
change_ownership "Take"
|
80
|
+
end
|
81
|
+
|
82
|
+
def untake
|
83
|
+
change_ownership "Untake"
|
84
|
+
end
|
85
|
+
|
86
|
+
def save
|
87
|
+
id == "ticket/new" ? create : update
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def reply(method, comment, opt)
|
93
|
+
# make sure the options are only the valid ones.
|
94
|
+
valid_options = [:cc, :bcc, :time_worked, :attachments, :status]
|
95
|
+
opt.delete_if { |key,value| !valid_options.include?(key) }
|
96
|
+
|
97
|
+
payload = { :text => comment, :action => method}.merge(opt)
|
98
|
+
payload.delete(:attachments)
|
99
|
+
|
100
|
+
attachments = Fiddler::AttachmentCollection.fill(opt[:attachments])
|
101
|
+
payload.merge!(:attachment => attachments.map(&:name).join(",")) unless attachments.empty?
|
102
|
+
|
103
|
+
response = Fiddler::ConnectionManager.post_content("ticket/#{id}/comment", { :content => payload.to_content_format }.merge(attachments.to_payload) )
|
104
|
+
|
105
|
+
result = Fiddler::Parsers::TicketParser.parse_reply_response(response)
|
106
|
+
|
107
|
+
# update the ticket parameters if the action was successful
|
108
|
+
if result and opt.has_key?(:status)
|
109
|
+
self.status = opt[:status]
|
110
|
+
result
|
15
111
|
else
|
16
|
-
|
112
|
+
result
|
17
113
|
end
|
18
|
-
@attributes.update(:id => 'ticket/new')
|
19
|
-
@saved = false
|
20
|
-
@histories = []
|
21
|
-
@new_record = true
|
22
|
-
add_methods!
|
23
114
|
end
|
24
115
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
116
|
+
def change_ownership(method)
|
117
|
+
payload = { "Action" => method }
|
118
|
+
response = Fiddler::ConnectionManager.post("ticket/#{id}/take", :content => payload.to_content_format)
|
119
|
+
new_owner = Fiddler::Parsers::TicketParser.parse_change_ownership_response(response)
|
120
|
+
if new_owner
|
121
|
+
self.owner = new_owner
|
122
|
+
return self
|
123
|
+
else
|
124
|
+
return nil
|
33
125
|
end
|
34
126
|
end
|
35
127
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
128
|
+
def create
|
129
|
+
return false unless valid?
|
130
|
+
response = Fiddler::ConnectionManager.post("ticket/new", :content => @attributes.to_content_format)
|
131
|
+
id = Fiddler::Parsers::TicketParser.parse_update_response(response, :create)
|
132
|
+
return false unless id
|
133
|
+
self.id = id.to_i
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
def update
|
138
|
+
return false unless valid?
|
139
|
+
payload = @attributes.clone
|
140
|
+
payload.delete("text")
|
141
|
+
payload.delete("id")
|
142
|
+
response = Fiddler::ConnectionManager.post("ticket/#{id}/edit", :content => payload.to_content_format)
|
143
|
+
id = Fiddler::Parsers::TicketParser.parse_update_response(response, :update)
|
144
|
+
return !id.nil?
|
40
145
|
end
|
41
146
|
|
42
147
|
# Class methods
|
@@ -49,6 +154,7 @@ module Fiddler
|
|
49
154
|
url = "ticket/#{id}"
|
50
155
|
response = Fiddler::ConnectionManager.get(url)
|
51
156
|
ticket = Fiddler::Parsers::TicketParser.parse_single(response)
|
157
|
+
ticket
|
52
158
|
end
|
53
159
|
|
54
160
|
# Search the tickets with the given conditions
|
@@ -56,9 +162,14 @@ module Fiddler
|
|
56
162
|
# @params [Hash] of conditions
|
57
163
|
# @returns [Array<Ticket>] of the tickets matching the criteria
|
58
164
|
def all(conditions={})
|
165
|
+
tickets = []
|
59
166
|
url = "search/ticket"
|
60
|
-
|
61
|
-
|
167
|
+
request_hash = Fiddler::Formatters::SearchRequestFormatter.format(conditions)
|
168
|
+
unless request_hash.empty?
|
169
|
+
response = Fiddler::ConnectionManager.get(url,request_hash)
|
170
|
+
tickets = Fiddler::Parsers::TicketParser.parse_multiple(response)
|
171
|
+
end
|
172
|
+
tickets
|
62
173
|
end
|
63
174
|
|
64
175
|
# Creates a new ticket with the given options, it will not save the ticket
|
@@ -67,19 +178,6 @@ module Fiddler
|
|
67
178
|
# @returns [Ticket] returns new ticket object
|
68
179
|
def create(options)
|
69
180
|
end
|
70
|
-
|
71
|
-
# Saves the changes to the ticket object
|
72
|
-
#
|
73
|
-
# @returns [boolean] save succcessful or not
|
74
|
-
def save
|
75
|
-
end
|
76
|
-
|
77
|
-
# Find the tickets with the given options
|
78
|
-
#
|
79
|
-
# @params [Hash] of options
|
80
|
-
# @returns [Array<Ticket>] of ticket matching the criteria
|
81
|
-
def find(options={})
|
82
|
-
end
|
83
181
|
end
|
84
182
|
end
|
85
|
-
end
|
183
|
+
end
|