diff --git a/README.md b/README.md index 3646b809..8505c8b7 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,13 @@ client = ZendeskAPI::Client.new do |config| config.access_token = "your OAuth access token" # Optional: + # When the library initializes, it will fetch the ticket fields metadata + # from the API and cache it for future requests. + # You then have access to ticket field definitions through ticket.custom_field_symbol["field_key"] + # Enabling this will add an extra API request during initialization. + config.load_ticket_fields_metadata = true # Default: false + # Retry uses middleware to notify the user # when hitting the rate limit, sleep automatically, # then retry the request. diff --git a/lib/zendesk_api/client.rb b/lib/zendesk_api/client.rb index 57e41808..a0f8b88c 100644 --- a/lib/zendesk_api/client.rb +++ b/lib/zendesk_api/client.rb @@ -32,6 +32,10 @@ class Client # @return [Array] Custom response callbacks attr_reader :callbacks + def ticket_fields_metadata + @ticket_fields_metadata ||= [] + end + # Handles resources such as 'tickets'. Any options are passed to the underlying collection, except reload which disregards # memoization and creates a new Collection instance. # @return [Collection] Collection instance for resource @@ -103,6 +107,17 @@ def initialize set_token_auth set_default_logger add_warning_callback + load_ticket_fields_metadata if @config.load_ticket_fields_metadata + end + + def load_ticket_fields_metadata + @ticket_fields_metadata = [] + ticket_fields.all do |f| + if f + @ticket_fields_metadata << f + end + end + @ticket_fields_metadata end # token impersonation for the scope of the block diff --git a/lib/zendesk_api/configuration.rb b/lib/zendesk_api/configuration.rb index bf8d27af..e03bd231 100644 --- a/lib/zendesk_api/configuration.rb +++ b/lib/zendesk_api/configuration.rb @@ -37,8 +37,12 @@ class Configuration # @return [String] OAuth2 access_token attr_accessor :access_token + # @return [String] url_based_access_token attr_accessor :url_based_access_token + # @return [Boolean] load_ticket_fields_metadata + attr_accessor :load_ticket_fields_metadata + # Use this cache instead of default ZendeskAPI::LRUCache.new # - must respond to read/write/fetch e.g. ActiveSupport::Cache::MemoryStore.new) # - pass false to disable caching diff --git a/lib/zendesk_api/resources.rb b/lib/zendesk_api/resources.rb index ec55822d..ea9e8c10 100644 --- a/lib/zendesk_api/resources.rb +++ b/lib/zendesk_api/resources.rb @@ -464,6 +464,55 @@ class Ticket < Resource extend UpdateMany extend DestroyMany + # Proxy to trap array operator usage on custom_field_symbol + class CustomFieldSymbolProxy + def initialize(ticket) + @ticket = ticket + @field_array = @ticket.custom_fields || [] + end + + def [](key) + raise "Cannot find custom field #{key}, configuration ticket_fields_metadata is OFF" unless + @ticket.instance_variable_get(:@client).ticket_fields_metadata + # Trap read access + fld = @ticket.instance_variable_get(:@client).ticket_fields_metadata.find { |val| val[:title] == key } + raise "Cannot find custom field #{key}" unless fld + cf = @ticket.custom_fields.find { |h| h[:id] == fld[:id] } + cf ? cf[:value] : nil + end + + def []=(key, value) + raise "Cannot find custom field #{key}, configuration ticket_fields_metadata is OFF" unless + @ticket.instance_variable_get(:@client).ticket_fields_metadata + # Trap write access + fld = @ticket.instance_variable_get(:@client).ticket_fields_metadata.find { |val| val[:title] == key } + raise "Cannot find custom field #{key}" unless fld + cf = @ticket.custom_fields.find { |h| h[:id] == fld[:id] } if @ticket.custom_fields + if cf + cf[:value] = value + else + @ticket.custom_fields << {id: fld[:id], value: value} + end + end + + def to_a + @field_array + end + + # Delegate other hash methods as needed + def method_missing(method, ...) + @field_array.send(method, ...) + end + + def respond_to_missing?(method, include_private = false) + @field_array.respond_to?(method, include_private) + end + end + + def custom_field_symbol + @custom_field_symbol_proxy ||= CustomFieldSymbolProxy.new(self) + end + def self.cbp_path_regexes [/^tickets$/, %r{organizations/\d+/tickets}, %r{users/\d+/tickets/requested}] end diff --git a/spec/core/resources/custom_field_symbol_proxy_spec.rb b/spec/core/resources/custom_field_symbol_proxy_spec.rb new file mode 100644 index 00000000..81bda224 --- /dev/null +++ b/spec/core/resources/custom_field_symbol_proxy_spec.rb @@ -0,0 +1,88 @@ +require "core/spec_helper" +require_relative "../../../lib/zendesk_api/resources" + +RSpec.describe ZendeskAPI::Ticket::CustomFieldSymbolProxy do + let(:field_metadata) do + [ + {id: 1, title: "foo"}, + {id: 2, title: "bar"} + ] + end + let(:client) do + double("Client").tap do |c| + allow(c).to receive(:ticket_fields_metadata).and_return(field_metadata) + end + end + let(:ticket) do + t = ZendeskAPI::Ticket.new({}) + t.instance_variable_set(:@client, client) + t.instance_variable_set(:@custom_fields, [{id: 1, value: "abc"}]) + def t.custom_fields + _foo = 1 + @custom_fields + end + t + end + let(:proxy) { described_class.new(ticket) } + + describe "[] and []=" do + it "reads a custom field by symbol (existing)" do + expect(proxy["foo"]).to eq("abc") + end + + it "returns nil for existing field with no value" do + ticket.instance_variable_set(:@custom_fields, [{id: 1}]) + expect(proxy["foo"]).to be_nil + end + + it "raises error for missing field title" do + expect { proxy["baz"] }.to raise_error(/Cannot find custom field/) + end + + it "writes a custom field by symbol (existing)" do + proxy["foo"] = "updated" + expect(ticket.custom_fields.find { |h| h[:id] == 1 }[:value]).to eq("updated") + end + + it "writes a custom field by symbol (new)" do + proxy["bar"] = "def" + expect(ticket.custom_fields.find { |h| h[:id] == 2 }[:value]).to eq("def") + end + end + + describe "delegation and integration" do + it "delegates to_a" do + expect(proxy.to_a).to eq(ticket.custom_fields) + end + + it "delegates method_missing and respond_to_missing?" do + expect(proxy.respond_to?(:each)).to be true + expect(proxy.map { |h| h[:id] }).to include(1) + end + + it "returns proxy from custom_field_symbol accessor" do + t = ZendeskAPI::Ticket.new({}) + t.instance_variable_set(:@client, client) + t.instance_variable_set(:@custom_fields, [{id: 1, value: "abc"}]) + def t.custom_fields + _foo = 1 + @custom_fields + end + expect(t.custom_field_symbol["foo"]).to eq("abc") + end + end + + describe "[] and []= with missing ticket_fields_metadata" do + before do + allow(client).to receive(:ticket_fields_metadata).and_return(nil) + end + + it "raises error for [] when ticket_fields_metadata is missing" do + expect { proxy["foo"] }.to raise_error(/configuration ticket_fields_metadata is OFF/) + end + + it "raises error for []= when ticket_fields_metadata is missing" do + expect { proxy["foo"] = "bar" }.to raise_error(/configuration ticket_fields_metadata is OFF/) + end + end +end