boblail-unfuddle 0.7.0
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.
- 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
|