zendesk_api 0.0.9

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.
Files changed (97) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +5 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +59 -0
  7. data/LICENSE +19 -0
  8. data/Rakefile +49 -0
  9. data/Readme.md +178 -0
  10. data/lib/zendesk_api.rb +10 -0
  11. data/lib/zendesk_api/actions.rb +176 -0
  12. data/lib/zendesk_api/association.rb +267 -0
  13. data/lib/zendesk_api/client.rb +150 -0
  14. data/lib/zendesk_api/collection.rb +233 -0
  15. data/lib/zendesk_api/configuration.rb +52 -0
  16. data/lib/zendesk_api/core_ext/inflection.rb +13 -0
  17. data/lib/zendesk_api/core_ext/modulize.rb +10 -0
  18. data/lib/zendesk_api/core_ext/snakecase.rb +12 -0
  19. data/lib/zendesk_api/lru_cache.rb +38 -0
  20. data/lib/zendesk_api/middleware/request/etag_cache.rb +38 -0
  21. data/lib/zendesk_api/middleware/request/retry.rb +39 -0
  22. data/lib/zendesk_api/middleware/request/upload.rb +32 -0
  23. data/lib/zendesk_api/middleware/response/callback.rb +19 -0
  24. data/lib/zendesk_api/middleware/response/deflate.rb +18 -0
  25. data/lib/zendesk_api/middleware/response/gzip.rb +18 -0
  26. data/lib/zendesk_api/middleware/response/parse_iso_dates.rb +29 -0
  27. data/lib/zendesk_api/rescue.rb +44 -0
  28. data/lib/zendesk_api/resource.rb +133 -0
  29. data/lib/zendesk_api/resources/forum.rb +51 -0
  30. data/lib/zendesk_api/resources/misc.rb +66 -0
  31. data/lib/zendesk_api/resources/playlist.rb +64 -0
  32. data/lib/zendesk_api/resources/ticket.rb +76 -0
  33. data/lib/zendesk_api/resources/user.rb +44 -0
  34. data/lib/zendesk_api/track_changes.rb +72 -0
  35. data/lib/zendesk_api/trackie.rb +8 -0
  36. data/lib/zendesk_api/verbs.rb +43 -0
  37. data/lib/zendesk_api/version.rb +3 -0
  38. data/live/Readme.md +4 -0
  39. data/live/activity_spec.rb +5 -0
  40. data/live/audit_spec.rb +5 -0
  41. data/live/bookmark_spec.rb +11 -0
  42. data/live/category_spec.rb +12 -0
  43. data/live/collection_spec.rb +68 -0
  44. data/live/crm_spec.rb +11 -0
  45. data/live/custom_role_spec.rb +5 -0
  46. data/live/forum_spec.rb +14 -0
  47. data/live/forum_subscription_spec.rb +12 -0
  48. data/live/group_membership_spec.rb +18 -0
  49. data/live/group_spec.rb +14 -0
  50. data/live/identity_spec.rb +14 -0
  51. data/live/locale_spec.rb +11 -0
  52. data/live/macro_spec.rb +5 -0
  53. data/live/mobile_device_spec.rb +11 -0
  54. data/live/organization_spec.rb +12 -0
  55. data/live/satisfaction_rating_spec.rb +6 -0
  56. data/live/setting_spec.rb +5 -0
  57. data/live/suspended_ticket_spec.rb +8 -0
  58. data/live/ticket_field_spec.rb +12 -0
  59. data/live/ticket_metrics_spec.rb +6 -0
  60. data/live/ticket_spec.rb +88 -0
  61. data/live/topic_comment_spec.rb +13 -0
  62. data/live/topic_spec.rb +18 -0
  63. data/live/topic_subscription_spec.rb +12 -0
  64. data/live/topic_vote_spec.rb +13 -0
  65. data/live/upload_spec.rb +9 -0
  66. data/live/user_spec.rb +13 -0
  67. data/live/view_spec.rb +6 -0
  68. data/spec/association_spec.rb +210 -0
  69. data/spec/client_spec.rb +149 -0
  70. data/spec/collection_spec.rb +302 -0
  71. data/spec/configuration_spec.rb +24 -0
  72. data/spec/create_resource_spec.rb +39 -0
  73. data/spec/data_resource_spec.rb +229 -0
  74. data/spec/fixtures/Argentina.gif +0 -0
  75. data/spec/fixtures/Argentina2.gif +0 -0
  76. data/spec/fixtures/credentials.yml.example +3 -0
  77. data/spec/fixtures/test_resources.rb +8 -0
  78. data/spec/fixtures/zendesk.rb +88 -0
  79. data/spec/lru_cache_spec.rb +26 -0
  80. data/spec/macros/resource_macros.rb +157 -0
  81. data/spec/middleware/request/etag_cache_spec.rb +17 -0
  82. data/spec/middleware/request/retry_spec.rb +47 -0
  83. data/spec/middleware/request/test.jpg +0 -0
  84. data/spec/middleware/request/upload_spec.rb +74 -0
  85. data/spec/middleware/response/callback_spec.rb +17 -0
  86. data/spec/middleware/response/deflate_spec.rb +15 -0
  87. data/spec/middleware/response/gzip_spec.rb +19 -0
  88. data/spec/middleware/response/parse_iso_dates_spec.rb +44 -0
  89. data/spec/playlist_spec.rb +95 -0
  90. data/spec/read_resource_spec.rb +37 -0
  91. data/spec/rescue_spec.rb +94 -0
  92. data/spec/resource_spec.rb +332 -0
  93. data/spec/spec_helper.rb +120 -0
  94. data/spec/string_spec.rb +7 -0
  95. data/spec/trackie_spec.rb +39 -0
  96. data/zendesk_api.gemspec +38 -0
  97. metadata +364 -0
@@ -0,0 +1,52 @@
1
+ module ZendeskAPI
2
+ class Configuration
3
+ # @return [String] The basic auth username.
4
+ attr_accessor :username
5
+
6
+ # @return [String] The basic auth password.
7
+ attr_accessor :password
8
+
9
+ # @return [String] The API url. Must be https unless {#allow_http} is set.
10
+ attr_accessor :url
11
+
12
+ # @return [Boolean] Whether to attempt to retry when rate-limited (http status: 429).
13
+ attr_accessor :retry
14
+
15
+ # @return [Logger] Logger to use when logging requests.
16
+ attr_accessor :logger
17
+
18
+ # @return [Hash] Client configurations (eg ssh config) to pass to Faraday
19
+ attr_accessor :client_options
20
+
21
+ # @return [Symbol] Faraday adapter
22
+ attr_accessor :adapter
23
+
24
+ # @return [Boolean] Whether to allow non-HTTPS connections for development purposes.
25
+ attr_accessor :allow_http
26
+
27
+ # Use this cache instead of default ZendeskAPI::LRUCache.new
28
+ # - must respond to read/write/fetch e.g. ActiveSupport::Cache::MemoryStore.new)
29
+ # - pass false to disable caching
30
+ # @return [ZendeskAPI::LRUCache]
31
+ attr_accessor :cache
32
+
33
+ def initialize
34
+ @client_options = {}
35
+ self.cache = ZendeskAPI::LRUCache.new(1000)
36
+ end
37
+
38
+ # Sets accept and user_agent headers, and url.
39
+ #
40
+ # @return [Hash] Faraday-formatted hash of options.
41
+ def options
42
+ {
43
+ :headers => {
44
+ :accept => 'application/json',
45
+ :accept_encoding => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
46
+ :user_agent => "ZendeskAPI API #{ZendeskAPI::VERSION}"
47
+ },
48
+ :url => @url
49
+ }.merge(client_options)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,13 @@
1
+ require 'inflection'
2
+
3
+ Inflection.plural_rule 'forum', 'forums'
4
+
5
+ class String
6
+ def singular
7
+ Inflection.singular(self)
8
+ end
9
+
10
+ def plural
11
+ Inflection.plural(self)
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # From https://github.com/rubyworks/facets/blob/master/lib/core/facets/string/modulize.rb
2
+ class String
3
+ def modulize
4
+ #gsub('__','/'). # why was this ever here?
5
+ gsub(/__(.?)/){ "::#{$1.upcase}" }.
6
+ gsub(/\/(.?)/){ "::#{$1.upcase}" }.
7
+ gsub(/(?:_+|-+)([a-z])/){ $1.upcase }.
8
+ gsub(/(\A|\s)([a-z])/){ $1 + $2.upcase }
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # From https://github.com/rubyworks/facets/blob/master/lib/core/facets/string/snakecase.rb
2
+ class String
3
+ def snakecase
4
+ #gsub(/::/, '/').
5
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
6
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
7
+ tr('-', '_').
8
+ gsub(/\s/, '_').
9
+ gsub(/__+/, '_').
10
+ downcase
11
+ end
12
+ end
@@ -0,0 +1,38 @@
1
+ module ZendeskAPI
2
+ # http://codesnippets.joyent.com/posts/show/12329
3
+ class ZendeskAPI::LRUCache
4
+ attr_accessor :size
5
+
6
+ def initialize(size = 10)
7
+ @size = size
8
+ @store = {}
9
+ @lru = []
10
+ end
11
+
12
+ def write(key, value)
13
+ @store[key] = value
14
+ set_lru(key)
15
+ @store.delete(@lru.pop) if @lru.size > @size
16
+ value
17
+ end
18
+
19
+ def read(key)
20
+ set_lru(key)
21
+ @store[key]
22
+ end
23
+
24
+ def fetch(key)
25
+ if @store.has_key? key
26
+ read key
27
+ else
28
+ write key, yield
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def set_lru(key)
35
+ @lru.unshift(@lru.delete(key) || key)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ require "faraday/middleware"
2
+
3
+ module ZendeskAPI
4
+ module Middleware
5
+ module Request
6
+ # Request middleware that caches responses based on etags
7
+ # can be removed once this is merged: https://github.com/pengwynn/faraday_middleware/pull/42
8
+ class EtagCache < Faraday::Middleware
9
+ def initialize(app, options = {})
10
+ @app = app
11
+ @cache = options[:cache] ||
12
+ raise("need :cache option e.g. ActiveSupport::Cache::MemoryStore.new")
13
+ @cache_key_prefix = options.fetch(:cache_key_prefix, :faraday_etags)
14
+ end
15
+
16
+ def call(env)
17
+ return @app.call(env) unless [:get, :head].include?(env[:method])
18
+ cache_key = [@cache_key_prefix, env[:url].to_s]
19
+
20
+ # send known etag
21
+ if cached = @cache.read(cache_key)
22
+ env[:request_headers]["If-None-Match"] ||= cached[:response_headers]["Etag"]
23
+ end
24
+
25
+ @app.call(env).on_complete do
26
+ if cached && env[:status] == 304 # not modified
27
+ env[:body] = cached[:body]
28
+ end
29
+
30
+ if env[:status] == 200 && env[:response_headers]["Etag"] # modified and cacheable
31
+ @cache.write(cache_key, env)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ require "faraday/middleware"
2
+
3
+ module ZendeskAPI
4
+ module Middleware
5
+ module Request
6
+ # Faraday middleware to handle HTTP Status 429 (rate limiting) / 503 (maintenance)
7
+ class Retry < Faraday::Middleware
8
+ DEFAULT_RETRY_AFTER = 10
9
+ ERROR_CODES = [429, 503]
10
+
11
+ def initialize(app, options={})
12
+ super(app)
13
+ @logger = options[:logger]
14
+ end
15
+
16
+ def call(env)
17
+ response = @app.call(env)
18
+
19
+ if ERROR_CODES.include?(response.env[:status])
20
+ seconds_left = (response.env[:response_headers][:retry_after] || DEFAULT_RETRY_AFTER).to_i
21
+ @logger.warn "You have been rate limited. Retrying in #{seconds_left} seconds..." if @logger
22
+
23
+ seconds_left.times do |i|
24
+ sleep 1
25
+ time_left = seconds_left - i
26
+ @logger.warn "#{time_left}..." if time_left > 0 && time_left % 5 == 0 && @logger
27
+ end
28
+
29
+ @logger.warn "" if @logger
30
+
31
+ @app.call(env)
32
+ else
33
+ response
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ require "faraday/middleware"
2
+ require "mime/types"
3
+
4
+ module ZendeskAPI
5
+ module Middleware
6
+ module Request
7
+ class Upload < Faraday::Middleware
8
+ def call(env)
9
+ if env[:body] && env[:body][:file]
10
+ file = env[:body].delete(:file)
11
+ case file
12
+ when File
13
+ path = file.path
14
+ when String
15
+ path = file
16
+ else
17
+ warn "WARNING: Passed invalid filename #{file} of type #{file.class} to upload"
18
+ end
19
+
20
+ if path
21
+ env[:body][:filename] ||= File.basename(path)
22
+ mime_type = MIME::Types.type_for(path).first || "application/octet-stream"
23
+ env[:body][:uploaded_data] = Faraday::UploadIO.new(path, mime_type)
24
+ end
25
+ end
26
+
27
+ @app.call(env)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ require "faraday/response"
2
+
3
+ module ZendeskAPI
4
+ module Middleware
5
+ module Response
6
+ class Callback < Faraday::Response::Middleware
7
+ def initialize(app, client)
8
+ super(app)
9
+ @client = client
10
+ end
11
+
12
+ def on_complete(env)
13
+ super(env)
14
+ @client.callbacks.each {|c| c.call(env)}
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+ module ZendeskAPI
4
+ module Middleware
5
+ module Response
6
+ # Faraday middleware to handle content-encoding = inflate
7
+ class Deflate < FaradayMiddleware::ResponseMiddleware
8
+ define_parser do |body|
9
+ Zlib::Inflate.inflate(body)
10
+ end
11
+
12
+ def parse_response?(env)
13
+ super && env[:response_headers]['content-encoding'] == "deflate"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+ module ZendeskAPI
4
+ module Middleware
5
+ module Response
6
+ # Faraday middleware to handle content-encoding = gzip
7
+ class Gzip < FaradayMiddleware::ResponseMiddleware
8
+ define_parser do |body|
9
+ Zlib::GzipReader.new(StringIO.new(body)).read
10
+ end
11
+
12
+ def parse_response?(env)
13
+ super && env[:response_headers]['content-encoding'] == "gzip"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ require 'time'
2
+ require "faraday/response"
3
+
4
+ module ZendeskAPI
5
+ module Middleware
6
+ module Response
7
+ # Parse ISO dates from response body
8
+ class ParseIsoDates < Faraday::Response::Middleware
9
+ def call(env)
10
+ response = @app.call(env)
11
+ parse_dates! response.env[:body]
12
+ response
13
+ end
14
+
15
+ private
16
+
17
+ def parse_dates!(value)
18
+ case value
19
+ when Hash then value.each { |key,element| value[key] = parse_dates!(element) }
20
+ when Array then value.each_with_index { |element, index| value[index] = parse_dates!(element) }
21
+ when /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\Z/m then Time.parse(value)
22
+ else
23
+ value
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ module ZendeskAPI
2
+ module Rescue
3
+ def self.included(klass)
4
+ klass.extend(Methods)
5
+ klass.send(:include, Methods)
6
+ end
7
+
8
+ module Methods
9
+ def log_error(e, method = false)
10
+ if logger = (@client ? @client.config.logger : Kernel)
11
+ logger.warn "#{self} - #{method}" if method
12
+ logger.warn e.message
13
+ logger.warn e.backtrace.join("\n")
14
+ logger.warn "\t#{e.response[:body].inspect}" if e.response
15
+ end
16
+ end
17
+
18
+ def rescue_client_error(*args)
19
+ opts = args.last.is_a?(Hash) ? args.pop : {}
20
+
21
+ if args.any?
22
+ args.each do |method|
23
+ class_eval("alias :orig_#{method} :#{method}")
24
+ define_method method do |*args|
25
+ begin
26
+ send("orig_#{method}", *args)
27
+ rescue Faraday::Error::ClientError => e
28
+ log_error(e, method)
29
+ opts[:with].respond_to?(:call) ? opts[:with].call : opts[:with]
30
+ end
31
+ end
32
+ end
33
+ elsif block_given?
34
+ begin
35
+ yield
36
+ rescue Faraday::Error::ClientError => e
37
+ log_error(e)
38
+ opts[:with].respond_to?(:call) ? opts[:with].call : opts[:with]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,133 @@
1
+ require 'zendesk_api/trackie'
2
+ require 'zendesk_api/actions'
3
+ require 'zendesk_api/association'
4
+ require 'zendesk_api/verbs'
5
+
6
+ module ZendeskAPI
7
+ # Represents a resource that only holds data.
8
+ class Data
9
+ include Associations
10
+ include Rescue
11
+
12
+ class << self
13
+ # The singular resource name taken from the class name (e.g. ZendeskAPI::Ticket -> ticket)
14
+ def singular_resource_name
15
+ @singular_resource_name ||= to_s.split("::").last.snakecase
16
+ end
17
+
18
+ # The resource name taken from the class name (e.g. ZendeskAPI::Ticket -> tickets)
19
+ def resource_name
20
+ @resource_name ||= singular_resource_name.plural
21
+ end
22
+
23
+ alias :model_key :resource_name
24
+
25
+ # Rails tries to load dependencies, which messes up automatic resource our own loading
26
+ if method_defined?(:const_missing_without_dependencies)
27
+ alias :const_missing :const_missing_without_dependencies
28
+ end
29
+
30
+ def only_send_unnested_params
31
+ @unnested_params = true
32
+ end
33
+
34
+ def unnested_params
35
+ @unnested_params ||= false
36
+ end
37
+ end
38
+
39
+ # @return [Hash] The resource's attributes
40
+ attr_reader :attributes
41
+ # @return [ZendeskAPI::Association] The association
42
+ attr_accessor :association
43
+
44
+ # Create a new resource instance.
45
+ # @param [Client] client The client to use
46
+ # @param [Hash] attributes The optional attributes that describe the resource
47
+ def initialize(client, attributes = {})
48
+ raise "Expected a Hash for attributes, got #{attributes.inspect}" unless attributes.is_a?(Hash)
49
+ @association = attributes.delete(:association) || Association.new(:class => self.class)
50
+ @client = client
51
+ @attributes = ZendeskAPI::Trackie.new(attributes)
52
+ ZendeskAPI::Client.check_deprecated_namespace_usage @attributes, self.class.singular_resource_name
53
+
54
+ @attributes.clear_changes unless new_record?
55
+ end
56
+
57
+ # Passes the method onto the attributes hash.
58
+ # If the attributes are nested (e.g. { :tickets => { :id => 1 } }), passes the method onto the nested hash.
59
+ def method_missing(*args, &block)
60
+ raise NoMethodError, ":save is not defined" if args.first.to_sym == :save
61
+ @attributes.send(*args, &block)
62
+ end
63
+
64
+ # Returns the resource id of the object or nil
65
+ def id
66
+ key?(:id) ? method_missing(:id) : nil
67
+ end
68
+
69
+ # Has this been object been created server-side? Does this by checking for an id.
70
+ def new_record?
71
+ id.nil?
72
+ end
73
+
74
+ # Returns the path to the resource
75
+ def path(*args)
76
+ @association.generate_path(self, *args)
77
+ end
78
+
79
+ def to_s
80
+ "#{self.class.singular_resource_name}: #{attributes.inspect}"
81
+ end
82
+ alias :inspect :to_s
83
+
84
+ def ==(other)
85
+ warn "Trying to compare #{other.class} to a Resource" if other && !other.is_a?(Data)
86
+ other.is_a?(self.class) && ((other.id && other.id == id) || (other.object_id == self.object_id))
87
+ end
88
+ alias :eql :==
89
+ alias :hash :id
90
+
91
+ def inspect
92
+ "#<#{self.class.name} #{@attributes.to_hash.inspect}>"
93
+ end
94
+
95
+ alias :to_param :attributes
96
+ end
97
+
98
+ # Indexable resource
99
+ class DataResource < Data
100
+ extend Verbs
101
+ end
102
+
103
+ # Represents a resource that can only GET
104
+ class ReadResource < DataResource
105
+ extend Read
106
+ end
107
+
108
+ # Represents a resource that can only POST
109
+ class CreateResource < DataResource
110
+ include Create
111
+ end
112
+
113
+ # Represents a resource that can only PUT
114
+ class UpdateResource < DataResource
115
+ include Update
116
+ end
117
+
118
+ # Represents a resource that can only DELETE
119
+ class DeleteResource < DataResource
120
+ include Destroy
121
+ end
122
+
123
+ # Represents a resource that can CRUD (create, read, update, delete).
124
+ class Resource < DataResource
125
+ extend Read
126
+ include Create
127
+
128
+ include Update
129
+ include Destroy
130
+ end
131
+
132
+ class SingularResource < Resource; end
133
+ end