boblail-unfuddle 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/Rakefile +9 -0
- data/lib/unfuddle.rb +171 -0
- data/lib/unfuddle/base.rb +223 -0
- data/lib/unfuddle/comment.rb +13 -0
- data/lib/unfuddle/component.rb +11 -0
- data/lib/unfuddle/configuration.rb +67 -0
- data/lib/unfuddle/custom_field_value.rb +11 -0
- data/lib/unfuddle/error.rb +59 -0
- data/lib/unfuddle/has_tickets.rb +68 -0
- data/lib/unfuddle/milestone.rb +11 -0
- data/lib/unfuddle/neq.rb +24 -0
- data/lib/unfuddle/person.rb +24 -0
- data/lib/unfuddle/project.rb +136 -0
- data/lib/unfuddle/response.rb +49 -0
- data/lib/unfuddle/severity.rb +11 -0
- data/lib/unfuddle/ticket.rb +13 -0
- data/lib/unfuddle/ticket_report.rb +11 -0
- data/lib/unfuddle/version.rb +3 -0
- data/test/test_helper.rb +6 -0
- data/unfuddle.gemspec +27 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6c4a223f7cf345cfab592d4b71aa877cb04bbbe7
|
4
|
+
data.tar.gz: fb9e1a041a454022f5c779743ef5c6df879f7fd9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7c0da95ccb04a123c6553cfe44a488c8a8f562aa954a79ec0f26277f6ed549a48e2072d49fbbe3f28a33ffd4eb6029892f247e8d8e808d849dc36ce22b1339b7
|
7
|
+
data.tar.gz: 7810194c1b509809f2031c3ba061978936e56d8f2e30483ab56b444e28e2d4ecda9b7697d39bcd248bcad4fda8f924a3301adf1033fb69d18771b89c3aa1dab6
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/lib/unfuddle.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
require 'active_support/core_ext/benchmark'
|
4
|
+
require 'active_support/core_ext/hash'
|
5
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
6
|
+
require 'unfuddle/configuration'
|
7
|
+
require 'unfuddle/error'
|
8
|
+
require 'unfuddle/project'
|
9
|
+
require 'unfuddle/person'
|
10
|
+
require 'unfuddle/response'
|
11
|
+
require 'unfuddle/has_tickets'
|
12
|
+
|
13
|
+
|
14
|
+
class Unfuddle
|
15
|
+
include HasTickets
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
def config(options=nil)
|
20
|
+
configuration = Unfuddle::Configuration.new
|
21
|
+
configuration.from_options(options) if options
|
22
|
+
yield configuration if block_given?
|
23
|
+
instance.configuration = configuration
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_config(options)
|
27
|
+
current_configuration = instance.configuration
|
28
|
+
begin
|
29
|
+
config(current_configuration.merge(options))
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
instance.configuration = current_configuration
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def instance
|
37
|
+
@unfuddle ||= self.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def assert_response!(expected_response_code, response)
|
41
|
+
unless response.status == expected_response_code
|
42
|
+
raise InvalidResponseError.new(response)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Homemade `delegate`
|
47
|
+
[:get, :post, :put, :delete, :configuration].each do |method|
|
48
|
+
module_eval <<-RUBY
|
49
|
+
def #{method}(*args, &block)
|
50
|
+
instance.#{method}(*args, &block)
|
51
|
+
end
|
52
|
+
RUBY
|
53
|
+
end
|
54
|
+
|
55
|
+
[:subdomain, :username, :password, :logger, :include_associations?, :include_closed_on?, :timeout].each do |method|
|
56
|
+
module_eval <<-RUBY
|
57
|
+
def #{method}
|
58
|
+
configuration.#{method}
|
59
|
+
end
|
60
|
+
RUBY
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :configuration
|
66
|
+
|
67
|
+
def configuration=(configuration)
|
68
|
+
@configuration = configuration
|
69
|
+
@http = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
[:subdomain, :username, :password, :logger, :include_associations?, :include_closed_on?, :timeout].each do |method|
|
73
|
+
module_eval <<-RUBY
|
74
|
+
def #{method}
|
75
|
+
configuration.#{method}
|
76
|
+
end
|
77
|
+
RUBY
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
def projects
|
83
|
+
Unfuddle.Project.all
|
84
|
+
end
|
85
|
+
|
86
|
+
def project(project_id)
|
87
|
+
Unfuddle::Project.new("id" => project_id)
|
88
|
+
end
|
89
|
+
|
90
|
+
def people(options={})
|
91
|
+
Unfuddle::Person.all(options)
|
92
|
+
end
|
93
|
+
|
94
|
+
def person(person_id)
|
95
|
+
Unfuddle::Person.new("id" => person_id)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
def get(path)
|
101
|
+
http_send_with_logging :get, path
|
102
|
+
end
|
103
|
+
|
104
|
+
def post(path, object)
|
105
|
+
http_send_with_logging :post, path, object.to_xml
|
106
|
+
end
|
107
|
+
|
108
|
+
def put(path, object)
|
109
|
+
http_send_with_logging :put, path, object.to_xml
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete(path)
|
113
|
+
http_send_with_logging :delete, path
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
def configured?
|
119
|
+
subdomain && username && password
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
protected
|
125
|
+
|
126
|
+
def http_send_with_logging(method, path, body=nil, headers={})
|
127
|
+
path = "/api/v1/#{path}"
|
128
|
+
headers = headers.merge({ # read JSON, write XML
|
129
|
+
"Accept" => "application/json",
|
130
|
+
"Content-type" => "application/xml" })
|
131
|
+
|
132
|
+
response = nil
|
133
|
+
ms = Benchmark.ms do
|
134
|
+
response = http_send(method, path, body, headers)
|
135
|
+
end
|
136
|
+
response
|
137
|
+
ensure
|
138
|
+
message = "[unfuddle:#{method}] #{path}"
|
139
|
+
message << "\n #{body}" if body
|
140
|
+
message = '%s (%.1fms)' % [ message, ms ] if ms
|
141
|
+
logger.info(message)
|
142
|
+
|
143
|
+
url = http.url_prefix.to_s + path
|
144
|
+
logger.warn "[unfuddle:#{method}] URL length exceeded 2000 characters" if url.length > 2000
|
145
|
+
end
|
146
|
+
|
147
|
+
def http_send(method, path, body, headers)
|
148
|
+
response = http.public_send(method, path, body, headers) do |req|
|
149
|
+
req.options[:timeout] = timeout
|
150
|
+
end
|
151
|
+
|
152
|
+
Response.new(response).tap do |response|
|
153
|
+
raise ServerError.new(response) if response.server_error?
|
154
|
+
raise UnauthorizedError.new(response) if response.unauthorized?
|
155
|
+
end
|
156
|
+
|
157
|
+
rescue Faraday::Error::ConnectionFailed
|
158
|
+
raise ConnectionError, $!.message
|
159
|
+
rescue Faraday::Error::TimeoutError, Errno::ETIMEDOUT
|
160
|
+
raise TimeoutError, $!.message
|
161
|
+
end
|
162
|
+
|
163
|
+
def http
|
164
|
+
raise ConfigurationError, "Unfuddle is not configured" unless configured?
|
165
|
+
@http ||= Faraday.new("https://#{subdomain}.unfuddle.com", ssl: {verify: false}) do |faraday|
|
166
|
+
faraday.adapter Faraday.default_adapter
|
167
|
+
faraday.basic_auth username, password
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'unfuddle'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
|
5
|
+
|
6
|
+
class Unfuddle
|
7
|
+
class Base
|
8
|
+
|
9
|
+
def initialize(attributes)
|
10
|
+
__set_attributes(attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def unfetched?
|
14
|
+
@attributes.keys == %w{id}
|
15
|
+
end
|
16
|
+
|
17
|
+
def id
|
18
|
+
@attributes["id"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def __set_attributes(attributes)
|
22
|
+
raise ArgumentError, "attributes is expected to be a hash, but it was #{attributes.class} instead" unless attributes.is_a?(Hash)
|
23
|
+
if @attributes
|
24
|
+
@attributes.merge! attributes
|
25
|
+
else
|
26
|
+
@attributes = attributes
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
# Respond to attributes
|
33
|
+
|
34
|
+
def attributes
|
35
|
+
_attributes.dup
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(method_name, *args, &block)
|
39
|
+
if has_attribute?(method_name)
|
40
|
+
_attributes[method_name.to_s]
|
41
|
+
else
|
42
|
+
super(method_name, *args, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_to?(method_name)
|
47
|
+
super(method_name) || has_attribute?(method_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_attribute?(attribute_name)
|
51
|
+
_attributes.key?(attribute_name.to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_attribute(attribute, value)
|
55
|
+
_attributes[attribute.to_s] = value
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_attributes!(attributes)
|
59
|
+
attributes.each do |key, value|
|
60
|
+
write_attribute(key, value)
|
61
|
+
end
|
62
|
+
save!
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_attribute(attribute, value)
|
66
|
+
write_attribute(attribute, value)
|
67
|
+
save!
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
# Finders
|
73
|
+
|
74
|
+
class << self
|
75
|
+
|
76
|
+
def find_by(*attributes)
|
77
|
+
attributes.each do |attribute|
|
78
|
+
instance_eval <<-RUBY
|
79
|
+
def find_by_#{attribute}(value)
|
80
|
+
all.find { |instance| instance.#{attribute} == value }
|
81
|
+
end
|
82
|
+
RUBY
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
# Has many
|
91
|
+
|
92
|
+
class << self
|
93
|
+
|
94
|
+
def has_many(collection_name, options={})
|
95
|
+
collection_name = collection_name.to_s
|
96
|
+
path = collection_name
|
97
|
+
individual_name = collection_name.singularize
|
98
|
+
class_name = options.fetch(:class_name, individual_name.classify)
|
99
|
+
|
100
|
+
require "unfuddle/#{individual_name}"
|
101
|
+
|
102
|
+
class_eval <<-RUBY
|
103
|
+
def #{collection_name}
|
104
|
+
@#{collection_name} ||= get('#{path}').json.map { |attributes| #{class_name}.new(attributes) }
|
105
|
+
end
|
106
|
+
|
107
|
+
def #{individual_name}(id)
|
108
|
+
attributes = find_#{individual_name}(id)
|
109
|
+
#{class_name}.new(attributes) if attributes
|
110
|
+
end
|
111
|
+
|
112
|
+
def find_#{individual_name}(id)
|
113
|
+
response = get("#{path}/\#{id}")
|
114
|
+
|
115
|
+
return nil if response.status == 404
|
116
|
+
Unfuddle.assert_response!(200, response)
|
117
|
+
|
118
|
+
response.json
|
119
|
+
end
|
120
|
+
|
121
|
+
def new_#{individual_name}(attributes)
|
122
|
+
#{class_name}.new(attributes)
|
123
|
+
end
|
124
|
+
|
125
|
+
def create_#{individual_name}(params)
|
126
|
+
instance = #{class_name}.new(params)
|
127
|
+
response = post('#{path}', instance)
|
128
|
+
Unfuddle.assert_response!(201, response)
|
129
|
+
|
130
|
+
instance.__set_attributes(response.json)
|
131
|
+
|
132
|
+
unless instance.id
|
133
|
+
id = response.location.chomp("/")[/\\d+$/].to_i
|
134
|
+
instance.__set_attributes("id" => id) if id > 0
|
135
|
+
end
|
136
|
+
|
137
|
+
@#{collection_name}.push(instance) if @#{collection_name}
|
138
|
+
instance
|
139
|
+
end
|
140
|
+
RUBY
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
|
147
|
+
# Nest Unfuddle requests
|
148
|
+
|
149
|
+
def relative_path
|
150
|
+
raise NotImplementedError
|
151
|
+
end
|
152
|
+
|
153
|
+
[:get, :post, :put, :delete].each do |method|
|
154
|
+
module_eval <<-RUBY
|
155
|
+
def #{method}(path, *args)
|
156
|
+
Unfuddle.#{method}(relative_path + "/" + path, *args)
|
157
|
+
end
|
158
|
+
RUBY
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
|
163
|
+
# Serialization
|
164
|
+
|
165
|
+
def to_params
|
166
|
+
attributes
|
167
|
+
end
|
168
|
+
|
169
|
+
def singular_name
|
170
|
+
self.class.name[/[^:]*$/].tableize.singularize
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_xml
|
174
|
+
to_params.to_xml(root: singular_name)
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_json
|
178
|
+
JSON.dump({singular_name => to_params})
|
179
|
+
end
|
180
|
+
|
181
|
+
def fetch!
|
182
|
+
response = get(get_path)
|
183
|
+
Unfuddle.assert_response!(200, response)
|
184
|
+
__set_attributes(response.json)
|
185
|
+
|
186
|
+
rescue InvalidResponseError
|
187
|
+
binding.pry if binding.respond_to?(:pry)
|
188
|
+
raise $!
|
189
|
+
end
|
190
|
+
|
191
|
+
def save!
|
192
|
+
put put_path, self
|
193
|
+
end
|
194
|
+
|
195
|
+
def destroy!
|
196
|
+
delete delete_path
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
|
201
|
+
def get_path
|
202
|
+
""
|
203
|
+
end
|
204
|
+
|
205
|
+
def put_path
|
206
|
+
""
|
207
|
+
end
|
208
|
+
|
209
|
+
def delete_path
|
210
|
+
""
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
|
215
|
+
private
|
216
|
+
|
217
|
+
def _attributes
|
218
|
+
fetch! if unfetched?
|
219
|
+
@attributes
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Unfuddle
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
|
5
|
+
[:subdomain, :username, :password, :logger, :include_associations, :include_closed_on, :timeout].each do |attribute|
|
6
|
+
module_eval <<-RUBY
|
7
|
+
def #{attribute}(*arg)
|
8
|
+
set_value :#{attribute}, arg.first if arg.any?
|
9
|
+
get_value :#{attribute}
|
10
|
+
end
|
11
|
+
|
12
|
+
def #{attribute}=(value)
|
13
|
+
set_value :#{attribute}, value
|
14
|
+
end
|
15
|
+
RUBY
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
alias :include_associations? :include_associations
|
20
|
+
alias :include_closed_on? :include_closed_on
|
21
|
+
|
22
|
+
|
23
|
+
def merge(options)
|
24
|
+
to_h.merge(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
{ subdomain: subdomain,
|
29
|
+
username: username,
|
30
|
+
password: password,
|
31
|
+
include_associations: include_associations,
|
32
|
+
include_closed_on: include_closed_on,
|
33
|
+
logged: logger,
|
34
|
+
timeout: timeout }
|
35
|
+
end
|
36
|
+
|
37
|
+
def from_options(options)
|
38
|
+
options = options.with_indifferent_access
|
39
|
+
self.subdomain = options[:subdomain] if options.key?(:subdomain)
|
40
|
+
self.username = options[:username] if options.key?(:username)
|
41
|
+
self.password = options[:password] if options.key?(:password)
|
42
|
+
self.include_associations = options[:include_associations] if options.key?(:include_associations)
|
43
|
+
self.include_closed_on = options[:include_closed_on] if options.key?(:include_closed_on)
|
44
|
+
self.timeout = options.fetch :timeout, 120
|
45
|
+
self.logger = options.fetch :logger, Unfuddle::Configuration::Logger.new
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
class Logger
|
50
|
+
def info(s)
|
51
|
+
puts s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def set_value(attribute, value)
|
59
|
+
instance_variable_set(:"@#{attribute}", value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_value(attribute)
|
63
|
+
instance_variable_get(:"@#{attribute}")
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Unfuddle
|
2
|
+
|
3
|
+
module Error
|
4
|
+
end
|
5
|
+
|
6
|
+
class ConfigurationError < StandardError
|
7
|
+
include ::Unfuddle::Error
|
8
|
+
end
|
9
|
+
|
10
|
+
class ConnectionError < StandardError
|
11
|
+
include ::Unfuddle::Error
|
12
|
+
end
|
13
|
+
|
14
|
+
class TimeoutError < ConnectionError
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
class InvalidResponseError < StandardError
|
20
|
+
include ::Unfuddle::Error
|
21
|
+
|
22
|
+
def initialize(response)
|
23
|
+
@response = response
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :response
|
27
|
+
|
28
|
+
def message
|
29
|
+
response.body
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class ServerError < InvalidResponseError
|
35
|
+
end
|
36
|
+
|
37
|
+
class UnauthorizedError < InvalidResponseError
|
38
|
+
|
39
|
+
def message
|
40
|
+
"The user '#{Unfuddle.instance.username}' does not have permission to access this resource"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
class UndefinedCustomField < ArgumentError
|
48
|
+
include ::Unfuddle::Error
|
49
|
+
end
|
50
|
+
|
51
|
+
class UndefinedCustomFieldValue < ArgumentError
|
52
|
+
include ::Unfuddle::Error
|
53
|
+
end
|
54
|
+
|
55
|
+
class UndefinedSeverity < ArgumentError
|
56
|
+
include ::Unfuddle::Error
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
|
3
|
+
|
4
|
+
class Unfuddle
|
5
|
+
module HasTickets
|
6
|
+
|
7
|
+
def find_ticket_by_number(number)
|
8
|
+
response = get("tickets/by_number/#{number}")
|
9
|
+
|
10
|
+
return nil if response.status == 404
|
11
|
+
Unfuddle.assert_response!(200, response)
|
12
|
+
|
13
|
+
response.json
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_tickets!(*conditions)
|
17
|
+
raise ArgumentError.new("No conditions supplied: that's probably not good") if conditions.none?
|
18
|
+
path = "ticket_reports/dynamic.json"
|
19
|
+
path << "?conditions_string=#{construct_ticket_query(*conditions)}"
|
20
|
+
fields = %w{number summary description reporter_email resolution milestone_id created_at due_on severity_id component_id}
|
21
|
+
fields << "associations" if Unfuddle.include_associations?
|
22
|
+
fields << "closed_on" if Unfuddle.include_closed_on?
|
23
|
+
path << "&fields_string=#{fields.join(",")}" if fields.any?
|
24
|
+
response = get(path)
|
25
|
+
|
26
|
+
Unfuddle.assert_response!(200, response)
|
27
|
+
|
28
|
+
ticket_report = response.json
|
29
|
+
group0 = ticket_report.fetch("groups", [])[0] || {}
|
30
|
+
group0.fetch("tickets", [])
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
def construct_ticket_query(*conditions)
|
36
|
+
conditions_string = []
|
37
|
+
conditions.each do |condition|
|
38
|
+
case condition
|
39
|
+
when Hash
|
40
|
+
conditions_string.concat(condition.map { |key, value| Array.wrap(value).map { |value| create_condition_string(key, value) }.join("|") })
|
41
|
+
when Array
|
42
|
+
key, value = condition
|
43
|
+
conditions_string.push(Array.wrap(value).map { |value| create_condition_string(key, value) }.join("|"))
|
44
|
+
else
|
45
|
+
conditions_string.push(condition)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
conditions_string.join("%2C")
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_condition_string(key, value)
|
52
|
+
comparison = "eq"
|
53
|
+
comparison, value = "neq", value.value if value.is_a?(Neq)
|
54
|
+
key, value = prepare_key_and_value_for_conditions_string(key, value)
|
55
|
+
"#{key}-#{comparison}-#{value}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def prepare_key_and_value_for_conditions_string(key, value)
|
59
|
+
|
60
|
+
# If the value is an id, convert it to a number
|
61
|
+
value = value.to_i if value.is_a?(String) && value =~ /^\d+$/
|
62
|
+
|
63
|
+
[key, value]
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/lib/unfuddle/neq.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
class Unfuddle
|
2
|
+
module NeqHelper
|
3
|
+
|
4
|
+
def neq(arg)
|
5
|
+
Neq.new(arg)
|
6
|
+
end
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
class Neq
|
11
|
+
|
12
|
+
def initialize(value)
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"neq(#{value.inspect})"
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :value
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'unfuddle/base'
|
2
|
+
|
3
|
+
|
4
|
+
class Unfuddle
|
5
|
+
class Person < Base
|
6
|
+
|
7
|
+
def self.all(options={})
|
8
|
+
url = options[:including_removed] == true ? "people.json?removed=true" : "people.json"
|
9
|
+
response = Unfuddle.get(url)
|
10
|
+
|
11
|
+
if response.status == 404
|
12
|
+
nil
|
13
|
+
else
|
14
|
+
Unfuddle.assert_response!(200, response)
|
15
|
+
response.json.map { |attributes| self.new(attributes) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def relative_path
|
20
|
+
"people/#{id}"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'unfuddle/base'
|
2
|
+
require 'unfuddle/has_tickets'
|
3
|
+
|
4
|
+
|
5
|
+
class Unfuddle
|
6
|
+
class Project < Base
|
7
|
+
include HasTickets
|
8
|
+
|
9
|
+
def self.all
|
10
|
+
@projects ||= begin
|
11
|
+
response = Unfuddle.get('projects')
|
12
|
+
|
13
|
+
if response.status == 404
|
14
|
+
nil
|
15
|
+
else
|
16
|
+
Unfuddle.assert_response!(200, response)
|
17
|
+
response.json.map { |attributes| self.new(attributes) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.fetch(id)
|
23
|
+
response = Unfuddle.get("projects/#{id}")
|
24
|
+
|
25
|
+
if response.status == 404
|
26
|
+
nil
|
27
|
+
else
|
28
|
+
Unfuddle.assert_response!(200, response)
|
29
|
+
self.new response.json
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def relative_path
|
34
|
+
"projects/#{id}"
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
find_by :title
|
40
|
+
|
41
|
+
has_many :custom_field_values
|
42
|
+
has_many :severities
|
43
|
+
has_many :components
|
44
|
+
has_many :ticket_reports
|
45
|
+
has_many :tickets
|
46
|
+
has_many :milestones
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
def open_milestones
|
51
|
+
@open_milestones ||= get("projects/#{id}/milestones/upcoming").json.map { |attributes| Milestone.new(attributes) }
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
def custom_fields
|
57
|
+
{ 1 => ticket_field1_active ? ticket_field1_title : nil,
|
58
|
+
2 => ticket_field2_active ? ticket_field2_title : nil,
|
59
|
+
3 => ticket_field3_active ? ticket_field3_title : nil }
|
60
|
+
end
|
61
|
+
|
62
|
+
def custom_fields_defined
|
63
|
+
custom_fields.values.compact
|
64
|
+
end
|
65
|
+
|
66
|
+
def custom_field_defined?(field_title)
|
67
|
+
custom_fields_defined.member?(field_title)
|
68
|
+
end
|
69
|
+
|
70
|
+
def first_available_custom_field
|
71
|
+
(1..3).find { |n| custom_fields[n].nil? }
|
72
|
+
end
|
73
|
+
|
74
|
+
def number_of_custom_field_named!(name)
|
75
|
+
number_of_custom_field_named(name) || (raise UndefinedCustomField, "A custom field named \"#{name}\" is not defined!")
|
76
|
+
end
|
77
|
+
|
78
|
+
def number_of_custom_field_named(name)
|
79
|
+
custom_fields.key(name)
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
def find_custom_field_value_by_value!(field, value)
|
85
|
+
n = number_of_custom_field_named!(field)
|
86
|
+
result = custom_field_values.find { |cfv| cfv.field_number == n && cfv.value == value }
|
87
|
+
raise UndefinedCustomFieldValue, "\"#{value}\" is not a value for the custom field \"#{field}\"!" unless result
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_custom_field_value_by_id!(field, id)
|
92
|
+
n = number_of_custom_field_named!(field)
|
93
|
+
result = custom_field_values.find { |cfv| cfv.field_number == n && cfv.id == id }
|
94
|
+
raise UndefinedCustomFieldValue, "\"#{id}\" is not the id of a value for the custom field \"#{field}\"!" unless result
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
def find_severity_by_name!(name)
|
101
|
+
find_severity_by_name(name) || (raise UndefinedSeverity, "A severity named \"#{name}\" is not defined!")
|
102
|
+
end
|
103
|
+
|
104
|
+
def find_severity_by_name(name)
|
105
|
+
severities.find { |severity| severity.name == name }
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
def prepare_key_and_value_for_conditions_string(key, value)
|
111
|
+
key, value = super
|
112
|
+
|
113
|
+
# If the value is the name of a severity, try to look it up
|
114
|
+
value = find_severity_by_name!(value).id if key == :severity && value.is_a?(String)
|
115
|
+
|
116
|
+
# If the key is a custom field, look up the key and value
|
117
|
+
if key.is_a?(String)
|
118
|
+
value = find_custom_field_value_by_value!(key, value).id unless value.is_a?(Fixnum)
|
119
|
+
key = get_key_for_custom_field_named!(key)
|
120
|
+
end
|
121
|
+
|
122
|
+
[key, value]
|
123
|
+
end
|
124
|
+
|
125
|
+
def get_key_for_custom_field_named!(name)
|
126
|
+
"field_#{number_of_custom_field_named!(name)}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def get_ticket_attribute_for_custom_value_named!(name)
|
130
|
+
"field#{number_of_custom_field_named!(name)}_value_id"
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'unfuddle/error'
|
2
|
+
|
3
|
+
|
4
|
+
class Unfuddle
|
5
|
+
class Response
|
6
|
+
|
7
|
+
|
8
|
+
def initialize(faraday_response)
|
9
|
+
@faraday_response = faraday_response
|
10
|
+
end
|
11
|
+
|
12
|
+
def status
|
13
|
+
faraday_response.status
|
14
|
+
end
|
15
|
+
|
16
|
+
def server_error?
|
17
|
+
status == 500
|
18
|
+
end
|
19
|
+
|
20
|
+
def unauthorized?
|
21
|
+
status == 401
|
22
|
+
end
|
23
|
+
|
24
|
+
def body
|
25
|
+
faraday_response.body
|
26
|
+
end
|
27
|
+
|
28
|
+
def location
|
29
|
+
faraday_response["location"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def json
|
33
|
+
@json ||= begin
|
34
|
+
json = self.class.normalized_body(body)
|
35
|
+
json.empty? ? {} : JSON.load(json)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.normalized_body(body)
|
40
|
+
body.gsub(/\u0000/, "").strip
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :faraday_response
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/test/test_helper.rb
ADDED
data/unfuddle.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "unfuddle/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "boblail-unfuddle"
|
7
|
+
s.version = Unfuddle::VERSION
|
8
|
+
s.authors = ["Bob Lail"]
|
9
|
+
s.email = ["bob.lailfamily@gmail.com"]
|
10
|
+
|
11
|
+
s.homepage = "http://boblail.github.com/unfuddle/"
|
12
|
+
s.summary = %q{A library for communicating with Unfuddle}
|
13
|
+
s.description = %q{A library for communicating with Unfuddle}
|
14
|
+
|
15
|
+
s.add_dependency "activesupport"
|
16
|
+
s.add_dependency "builder"
|
17
|
+
s.add_dependency "faraday", "~> 0.8.8"
|
18
|
+
|
19
|
+
s.add_development_dependency "rails"
|
20
|
+
s.add_development_dependency "turn"
|
21
|
+
s.add_development_dependency "simplecov"
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: boblail-unfuddle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bob Lail
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: builder
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faraday
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.8.8
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.8.8
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: turn
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: A library for communicating with Unfuddle
|
98
|
+
email:
|
99
|
+
- bob.lailfamily@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- Gemfile
|
106
|
+
- Gemfile.lock
|
107
|
+
- Rakefile
|
108
|
+
- lib/unfuddle.rb
|
109
|
+
- lib/unfuddle/base.rb
|
110
|
+
- lib/unfuddle/comment.rb
|
111
|
+
- lib/unfuddle/component.rb
|
112
|
+
- lib/unfuddle/configuration.rb
|
113
|
+
- lib/unfuddle/custom_field_value.rb
|
114
|
+
- lib/unfuddle/error.rb
|
115
|
+
- lib/unfuddle/has_tickets.rb
|
116
|
+
- lib/unfuddle/milestone.rb
|
117
|
+
- lib/unfuddle/neq.rb
|
118
|
+
- lib/unfuddle/person.rb
|
119
|
+
- lib/unfuddle/project.rb
|
120
|
+
- lib/unfuddle/response.rb
|
121
|
+
- lib/unfuddle/severity.rb
|
122
|
+
- lib/unfuddle/ticket.rb
|
123
|
+
- lib/unfuddle/ticket_report.rb
|
124
|
+
- lib/unfuddle/version.rb
|
125
|
+
- test/test_helper.rb
|
126
|
+
- unfuddle.gemspec
|
127
|
+
homepage: http://boblail.github.com/unfuddle/
|
128
|
+
licenses: []
|
129
|
+
metadata: {}
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubyforge_project:
|
146
|
+
rubygems_version: 2.2.2
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: A library for communicating with Unfuddle
|
150
|
+
test_files:
|
151
|
+
- test/test_helper.rb
|