desk_api 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/.yardopts +3 -0
- data/Gemfile +15 -0
- data/Guardfile +5 -0
- data/LICENSE +7 -0
- data/README.md +185 -0
- data/Rakefile +15 -0
- data/config.rb +7 -0
- data/desk_api.gemspec +36 -0
- data/lib/desk/action/create.rb +15 -0
- data/lib/desk/action/delete.rb +9 -0
- data/lib/desk/action/embedded.rb +12 -0
- data/lib/desk/action/field.rb +33 -0
- data/lib/desk/action/link.rb +29 -0
- data/lib/desk/action/resource.rb +14 -0
- data/lib/desk/action/search.rb +15 -0
- data/lib/desk/action/update.rb +12 -0
- data/lib/desk/client.rb +67 -0
- data/lib/desk/configuration.rb +132 -0
- data/lib/desk/default.rb +67 -0
- data/lib/desk/error/bad_gateway.rb +10 -0
- data/lib/desk/error/bad_request.rb +10 -0
- data/lib/desk/error/client_error.rb +9 -0
- data/lib/desk/error/configuration_error.rb +8 -0
- data/lib/desk/error/conflict.rb +10 -0
- data/lib/desk/error/forbidden.rb +10 -0
- data/lib/desk/error/gateway_timeout.rb +10 -0
- data/lib/desk/error/internal_server_error.rb +10 -0
- data/lib/desk/error/method_not_allowed.rb +10 -0
- data/lib/desk/error/method_not_supported.rb +9 -0
- data/lib/desk/error/not_acceptable.rb +10 -0
- data/lib/desk/error/not_found.rb +10 -0
- data/lib/desk/error/parse_error.rb +9 -0
- data/lib/desk/error/parser_error.rb +9 -0
- data/lib/desk/error/server_error.rb +9 -0
- data/lib/desk/error/service_unavailable.rb +10 -0
- data/lib/desk/error/too_many_requests.rb +10 -0
- data/lib/desk/error/unauthorized.rb +10 -0
- data/lib/desk/error/unprocessable_entity.rb +10 -0
- data/lib/desk/error/unsupported_media_type.rb +10 -0
- data/lib/desk/error.rb +61 -0
- data/lib/desk/rate_limit.rb +26 -0
- data/lib/desk/request/retry.rb +51 -0
- data/lib/desk/resource/article.rb +10 -0
- data/lib/desk/resource/article_translation.rb +8 -0
- data/lib/desk/resource/attachment.rb +8 -0
- data/lib/desk/resource/case.rb +9 -0
- data/lib/desk/resource/company.rb +8 -0
- data/lib/desk/resource/customer.rb +9 -0
- data/lib/desk/resource/integration_url.rb +9 -0
- data/lib/desk/resource/job.rb +7 -0
- data/lib/desk/resource/label.rb +9 -0
- data/lib/desk/resource/macro.rb +9 -0
- data/lib/desk/resource/macro_action.rb +7 -0
- data/lib/desk/resource/note.rb +7 -0
- data/lib/desk/resource/page.rb +81 -0
- data/lib/desk/resource/reply.rb +8 -0
- data/lib/desk/resource/topic.rb +9 -0
- data/lib/desk/resource/topic_translation.rb +9 -0
- data/lib/desk/resource/user_preference.rb +7 -0
- data/lib/desk/resource.rb +53 -0
- data/lib/desk/resources.json +76 -0
- data/lib/desk/response/raise_error.rb +34 -0
- data/lib/desk/version.rb +3 -0
- data/lib/desk.rb +35 -0
- data/spec/cassettes/Desk_Client/_delete/deletes_a_resource.yml +48 -0
- data/spec/cassettes/Desk_Client/_get/fetches_resources.yml +55 -0
- data/spec/cassettes/Desk_Client/_patch/updates_a_resource.yml +62 -0
- data/spec/cassettes/Desk_Client/_post/creates_a_resource.yml +58 -0
- data/spec/cassettes/Desk_Error/_from_response/can_be_created_from_a_faraday_response.yml +54 -0
- data/spec/cassettes/Desk_Error/_from_response/uses_the_body_message_if_present.yml +54 -0
- data/spec/cassettes/Desk_Resource/_by_url/finds_resources_by_url.yml +313 -0
- data/spec/cassettes/Desk_Resource/_create/creates_a_new_topic.yml +58 -0
- data/spec/cassettes/Desk_Resource/_delete/deletes_a_resource.yml +164 -0
- data/spec/cassettes/Desk_Resource/_delete/throws_an_error_deleting_a_non_deletalbe_resource.yml +56 -0
- data/spec/cassettes/Desk_Resource/_exec_/can_be_forced_to_reload.yml +313 -0
- data/spec/cassettes/Desk_Resource/_exec_/loads_the_current_resource.yml +313 -0
- data/spec/cassettes/Desk_Resource/_method_missing/loads_the_resource_to_find_a_suitable_method.yml +313 -0
- data/spec/cassettes/Desk_Resource/_method_missing/raises_an_error_if_method_does_not_exist.yml +313 -0
- data/spec/cassettes/Desk_Resource/_search/allows_searching_on_search_enabled_resources.yml +54 -0
- data/spec/cassettes/Desk_Resource/_update/throws_an_error_updating_a_user.yml +56 -0
- data/spec/cassettes/Desk_Resource/_update/updates_a_topic.yml +118 -0
- data/spec/cassettes/Desk_Resource_Page/_by_id/loads_the_requested_resource.yml +54 -0
- data/spec/cassettes/Desk_Resource_Page/_page/keeps_the_resource_as_loaded.yml +113 -0
- data/spec/cassettes/Desk_Resource_Page/_page/returns_the_current_page_and_loads_if_page_not_defined.yml +313 -0
- data/spec/cassettes/Desk_Resource_Page/_page/sets_the_resource_to_not_loaded.yml +113 -0
- data/spec/desk/client_spec.rb +133 -0
- data/spec/desk/configuration_spec.rb +183 -0
- data/spec/desk/default_spec.rb +69 -0
- data/spec/desk/error_spec.rb +23 -0
- data/spec/desk/rate_limit_spec.rb +42 -0
- data/spec/desk/request/retry_spec.rb +46 -0
- data/spec/desk/resource_spec.rb +119 -0
- data/spec/desk/resources/page_spec.rb +64 -0
- data/spec/desk_spec.rb +43 -0
- data/spec/spec_helper.rb +39 -0
- metadata +347 -0
data/lib/desk/default.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'desk/version'
|
2
|
+
|
3
|
+
module Desk
|
4
|
+
module Default
|
5
|
+
CONNECTION_OPTIONS = {
|
6
|
+
headers: {
|
7
|
+
accept: 'application/json',
|
8
|
+
user_agent: "desk.com Ruby Gem v#{Desk::VERSION}"
|
9
|
+
},
|
10
|
+
request: {
|
11
|
+
open_timeout: 5,
|
12
|
+
timeout: 10
|
13
|
+
}
|
14
|
+
} unless defined? Desk::Default::CONNECTION_OPTIONS
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# @return [Hash]
|
18
|
+
def options
|
19
|
+
Hash[Desk::Configuration.keys.map{|key| [key, send(key)]}]
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String]
|
23
|
+
def username
|
24
|
+
ENV['DESK_USERNAME']
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
def password
|
29
|
+
ENV['DESK_PASSWORD']
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String]
|
33
|
+
def consumer_key
|
34
|
+
ENV['DESK_CONSUMER_KEY']
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String]
|
38
|
+
def consumer_secret
|
39
|
+
ENV['DESK_CONSUMER_SECRET']
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [String]
|
43
|
+
def token
|
44
|
+
ENV['DESK_TOKEN']
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [String]
|
48
|
+
def token_secret
|
49
|
+
ENV['DESK_TOKEN_SECRET']
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
def subdomain
|
54
|
+
ENV['DESK_SUBDOMAIN']
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [String]
|
58
|
+
def endpoint
|
59
|
+
ENV['DESK_ENDPOINT']
|
60
|
+
end
|
61
|
+
|
62
|
+
def connection_options
|
63
|
+
CONNECTION_OPTIONS
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/desk/error.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'desk/rate_limit'
|
2
|
+
|
3
|
+
module Desk
|
4
|
+
# Custom error class for rescuing from all desk.com errors
|
5
|
+
class Error < StandardError
|
6
|
+
attr_reader :rate_limit
|
7
|
+
|
8
|
+
# Initializes a new Error object
|
9
|
+
#
|
10
|
+
# @param exception [Exception, String]
|
11
|
+
# @param response_headers [Hash]
|
12
|
+
# @param code [Integer]
|
13
|
+
# @return [Desk::Error]
|
14
|
+
def initialize(exception=$!, response_headers={}, code = nil)
|
15
|
+
@rate_limit = Desk::RateLimit.new(response_headers)
|
16
|
+
@wrapped_exception = exception
|
17
|
+
@code = code
|
18
|
+
exception.respond_to?(:backtrace) ? super(exception.message) : super(exception.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
def backtrace
|
22
|
+
@wrapped_exception.respond_to?(:backtrace) ? @wrapped_exception.backtrace : super
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# Create a new error from an HTTP response
|
27
|
+
#
|
28
|
+
# @param response [Hash]
|
29
|
+
# @return [Desk::Error]
|
30
|
+
def from_response(response = {})
|
31
|
+
error, code = parse_error(response[:body]), response[:status]
|
32
|
+
new(error, response[:response_headers], code)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Hash]
|
36
|
+
def errors
|
37
|
+
@errors ||= descendants.each_with_object({}) do |klass, hash|
|
38
|
+
hash[klass::HTTP_STATUS_CODE] = klass
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Array]
|
43
|
+
def descendants
|
44
|
+
@descendants ||= []
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Array]
|
48
|
+
def inherited(descendant)
|
49
|
+
descendants << descendant
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def parse_error(body)
|
55
|
+
if body['message']
|
56
|
+
body['message']
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Desk
|
2
|
+
class RateLimit
|
3
|
+
def initialize(attrs = {})
|
4
|
+
@attrs = attrs
|
5
|
+
end
|
6
|
+
|
7
|
+
# @return [Integer]
|
8
|
+
def limit
|
9
|
+
limit = @attrs['x-rate-limit-limit']
|
10
|
+
limit.to_i if limit
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Integer]
|
14
|
+
def remaining
|
15
|
+
remaining = @attrs['x-rate-limit-remaining']
|
16
|
+
remaining.to_i if remaining
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Integer]
|
20
|
+
def reset_in
|
21
|
+
reset_in = @attrs['x-rate-limit-reset']
|
22
|
+
reset_in.to_i if reset_in
|
23
|
+
end
|
24
|
+
alias retry_after reset_in
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Desk
|
2
|
+
module Request
|
3
|
+
class Retry < Faraday::Request::Retry
|
4
|
+
def initialize(app, options = {})
|
5
|
+
@max = options[:max] || 3
|
6
|
+
@interval = options[:interval] || 10
|
7
|
+
super(app)
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
retries = @retries
|
12
|
+
interval = @interval
|
13
|
+
|
14
|
+
begin
|
15
|
+
@app.call(env)
|
16
|
+
rescue Desk::Error::TooManyRequests => e
|
17
|
+
if retries > 0 and e.rate_limit.reset_in
|
18
|
+
retries = 0
|
19
|
+
sleep e.rate_limit.reset_in
|
20
|
+
retry
|
21
|
+
end
|
22
|
+
raise
|
23
|
+
rescue exception_matcher
|
24
|
+
if retries > 0
|
25
|
+
retries -= 1
|
26
|
+
sleep @interval
|
27
|
+
retry
|
28
|
+
end
|
29
|
+
raise
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def exception_matcher
|
34
|
+
exceptions = [Errno::ETIMEDOUT, 'Timeout::Error', Faraday::Error::TimeoutError]
|
35
|
+
matcher = Module.new
|
36
|
+
(class << matcher; self; end).class_eval do
|
37
|
+
define_method(:===) do |error|
|
38
|
+
exceptions.any? do |ex|
|
39
|
+
if ex.is_a? Module then error.is_a? ex
|
40
|
+
else error.class.to_s == ex.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
matcher
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Faraday.register_middleware :request, retry: lambda { Retry }
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Desk
|
2
|
+
class Resource
|
3
|
+
class Page < Desk::Resource
|
4
|
+
include Enumerable
|
5
|
+
include Desk::Action::Embedded
|
6
|
+
|
7
|
+
[
|
8
|
+
:all?, :any?,
|
9
|
+
:collect, :each, :entries, :find, :find_all,
|
10
|
+
:find_index, :first, :first, :group_by, :last,
|
11
|
+
:map, :map!, :none?, :one?, :partition, :reduce,
|
12
|
+
:reject, :select, :sort, :sort_by, :taken, :zip
|
13
|
+
].each do |method|
|
14
|
+
define_method(method) do |*args, &block|
|
15
|
+
exec!.records.send(method, *args, &block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def search(params)
|
20
|
+
raise Desk::Error::MethodNotSupported unless base_class.respond_to?(:search)
|
21
|
+
url = Addressable::URI.parse(clean_base_url + '/search')
|
22
|
+
url.query_values = params
|
23
|
+
base_class.search(client, url.to_s).exec!
|
24
|
+
end
|
25
|
+
|
26
|
+
def create(params)
|
27
|
+
raise Desk::Error::MethodNotSupported unless base_class.respond_to?(:create)
|
28
|
+
base_class.create(client, clean_base_url, params)
|
29
|
+
end
|
30
|
+
|
31
|
+
[:page, :per_page].each do |method|
|
32
|
+
define_method(method) do |value = nil|
|
33
|
+
if not value
|
34
|
+
self.exec! if self.query_params(method.to_s) == nil
|
35
|
+
return self.query_params(method.to_s).to_i
|
36
|
+
end
|
37
|
+
self.query_params = Hash[method.to_s, value.to_s]
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def by_id(id)
|
43
|
+
by_url("#{clean_base_url}/#{id}")
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
attr_reader :records
|
49
|
+
|
50
|
+
def setup(definition)
|
51
|
+
setup_embedded(definition._embedded['entries']) if definition._embedded?
|
52
|
+
super(definition)
|
53
|
+
end
|
54
|
+
|
55
|
+
def query_params(param)
|
56
|
+
params = Addressable::URI.parse(@_links.self.href).query_values || {}
|
57
|
+
params.include?(param) ? params[param] : nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def query_params=(params = {})
|
61
|
+
return @_links.self.href if params.empty?
|
62
|
+
|
63
|
+
uri = Addressable::URI.parse(@_links.self.href)
|
64
|
+
params = (uri.query_values || {}).merge(params)
|
65
|
+
|
66
|
+
@loaded = false unless params == uri.query_values
|
67
|
+
|
68
|
+
uri.query_values = params
|
69
|
+
@_links.self.href = uri.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
def clean_base_url
|
73
|
+
Addressable::URI.parse(@_links.self.href).path.gsub(/\/search$/, '')
|
74
|
+
end
|
75
|
+
|
76
|
+
def base_class
|
77
|
+
resource(clean_base_url[/\w+$/].singularize)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|