desk_api 0.1.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/.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
|