zendesk/zendesk_api_client_rb

View on GitHub
lib/zendesk_api/resources.rb

Summary

Maintainability
D
1 day
Test Coverage
module ZendeskAPI
  # @internal The following are redefined later, but needed by some circular resources (e.g. Ticket -> User, User -> Ticket)

  class Ticket < Resource; end
  class User < Resource; end
  class UserRelated < DataResource; end
  class Category < Resource; end
  class OrganizationSubscription < ReadResource; end
  class CustomStatus < Resource; end

  # @internal Begin actual Resource definitions

  class RecipientAddress < Resource; end

  class Locale < ReadResource; end

  class CustomRole < DataResource; end

  class Role < DataResource
    def to_param
      name
    end
  end

  class Topic < Resource
    class << self
      def cbp_path_regexes
        [%r{^community/topics$}]
      end

      def resource_path
        "community/topics"
      end
    end
  end

  class Bookmark < Resource; end
  class Ability < DataResource; end
  class Group < Resource; end
  class SharingAgreement < ReadResource; end
  class JobStatus < ReadResource; end

  class Session < ReadResource
    include Destroy
  end

  class Tag < DataResource
    include Update
    include Destroy

    alias :name :id
    alias :to_param :id

    def path(opts = {})
      raise "tags must have parent resource" unless association.options.parent
      super(opts.merge(:with_parent => true, :with_id => false))
    end

    def changed?
      true
    end

    def destroy!
      super do |req|
        req.body = attributes_for_save
      end
    end

    module Update
      def _save(method = :save)
        return self unless @resources

        @client.connection.post(path) do |req|
          req.body = { :tags => @resources.reject(&:destroyed?).map(&:id) }
        end

        true
      rescue Faraday::ClientError => e
        if method == :save
          false
        else
          raise e
        end
      end
    end

    def attributes_for_save
      { self.class.resource_name => [id] }
    end

    def self.cbp_path_regexes
      [/^tags$/]
    end
  end

  class Attachment < ReadResource
    def initialize(client, attributes = {})
      attributes[:file] ||= attributes.delete(:id)

      super
    end

    def save
      upload = Upload.create!(@client, attributes)
      self.token = upload.token
    end

    def to_param
      token
    end
  end

  class Upload < Data
    include Create
    include Destroy

    def id
      token
    end

    has_many Attachment

    private

    def attributes_for_save
      attributes.changes
    end
  end

  class MobileDevice < Resource
    # Clears this devices' badge
    put :clear_badge
  end

  class OrganizationRelated < DataResource; end

  class OrganizationMembership < ReadResource
    extend CreateOrUpdate
  end

  class Organization < Resource
    extend CreateMany
    extend CreateOrUpdate
    extend DestroyMany

    has Ability, :inline => true
    has Group
    has :related, :class => OrganizationRelated

    has_many Ticket
    has_many User
    has_many Tag, :extend => Tag::Update, :inline => :create
    has_many OrganizationMembership
    has_many :subscriptions, class: OrganizationSubscription

    # Gets a incremental export of organizations from the start_time until now.
    # @param [Client] client The {Client} object to be used
    # @param [Integer] start_time The start_time parameter
    # @return [Collection] Collection of {Organization}
    def self.incremental_export(client, start_time)
      ZendeskAPI::Collection.new(client, self, :path => "incremental/organizations?start_time=#{start_time.to_i}")
    end

    def self.cbp_path_regexes
      [/^organizations$/]
    end
  end

  class Brand < Resource
    def self.cbp_path_regexes
      [/^brands$/]
    end

    def destroy!
      self.active = false
      save!

      super
    end
  end

  class OrganizationMembership < ReadResource
    include Create
    include Destroy

    extend CreateMany
    extend DestroyMany

    has User
    has Organization
  end

  class OrganizationSubscription < ReadResource
    include Create
    include Destroy

    has User
    has Organization

    def self.cbp_path_regexes
      [%r{^organizations/\d+/subscriptions$}]
    end
  end

  class Category < Resource
    class << self
      def resource_path
        "help_center/categories"
      end
    end

    class Section < Resource
    end
    class Translation < Resource; end

    has_many Section
    has_many Translation
  end

  class Section < ReadResource
    class << self
      def resource_path
        "help_center/sections"
      end
    end

    has Category

    class Vote < Resource; end
    class Translation < Resource; end
    class Article < Resource
      has_many Vote
      has_many Translation
    end

    has_many Translation
    has_many Article
  end

  class Article < ReadResource
    class << self
      def resource_path
        "help_center/articles"
      end
    end

    class Vote < Resource; end
    has_many Vote
    class Translation < Resource; end
    has_many Translation
    class Label < DataResource
      include Read
      include Create
      include Destroy

      def destroy!
        super do |req|
          req.path = path
        end
      end
    end
    has_many Label
  end

  class TopicSubscription < Resource
    class << self
      def model_key
        "subscriptions"
      end
    end

    has Topic
    has User

    def path(options = {})
      super(options.merge(:with_parent => true))
    end
  end

  class Topic < Resource
    has_many :subscriptions, class: TopicSubscription, inline: true
    has_many Tag, extend: Tag::Update, inline: :create
    has_many Attachment
    has_many :uploads, class: Attachment, inline: true
  end

  class Activity < Resource
    has User
    has :actor, :class => User

    def self.cbp_path_regexes
      [/^activities$/]
    end
  end

  class Setting < UpdateResource
    attr_reader :on

    def initialize(client, attributes = {})
      # Try and find the root key
      @on = (attributes.keys.map(&:to_s) - %w{association options}).first

      # Make what's inside that key the root attributes
      attributes.merge!(attributes.delete(@on))

      super
    end

    def new_record?
      false
    end

    def path(options = {})
      super(options.merge(:with_parent => true))
    end

    def attributes_for_save
      { self.class.resource_name => { @on => attributes.changes } }
    end
  end

  class SatisfactionRating < ReadResource
    has :assignee, :class => User
    has :requester, :class => User
    has Ticket
    has Group
  end

  class Interval < Resource; end

  class Schedule < Resource
    has_many Interval

    class << self
      def resource_path
        "business_hours/schedules"
      end
    end
  end

  class Request < Resource
    class Comment < DataResource
      include Save

      has_many :uploads, class: Attachment, inline: true
      has :author, :class => User

      def save
        if new_record?
          save_associations
          true
        else
          false
        end
      end

      alias :save! :save
    end

    has Comment, :inline => true
    has_many Comment

    has Organization
    has Group
    has :requester, :class => User
  end

  class AnonymousRequest < CreateResource
    def self.singular_resource_name
      'request'
    end

    namespace 'portal'
  end

  class TicketField < Resource
    def self.cbp_path_regexes
      [/^ticket_fields$/]
    end
  end

  class TicketMetric < DataResource
    include Read

    def self.cbp_path_regexes
      [/^ticket_metrics$/]
    end
  end

  class TicketRelated < DataResource; end

  class TicketEvent < DataResource
    class Event < Data; end

    has_many :child_events, class: Event
    has Ticket
    has :updater, :class => User

    # Gets a incremental export of ticket events from the start_time until now.
    # @param [Client] client The {Client} object to be used
    # @param [Integer] start_time The start_time parameter
    # @return [Collection] Collection of {TicketEvent}
    def self.incremental_export(client, start_time)
      ZendeskAPI::Collection.new(client, self, :path => "incremental/ticket_events?start_time=#{start_time.to_i}")
    end
  end

  class Ticket < Resource
    extend CreateMany
    extend UpdateMany
    extend DestroyMany

    def self.cbp_path_regexes
      [/^tickets$/, %r{organizations/\d+/tickets}]
    end

    # Unlike other attributes, "comment" is not a property of the ticket,
    # but is used as a "comment on save", so it should be kept unchanged,
    # See https://github.com/zendesk/zendesk_api_client_rb/issues/321
    def attribute_changes
      attributes.changes.merge("comment" => attributes["comment"])
    end

    class Audit < DataResource
      class Event < Data
        has :author, :class => User
      end

      put :trust

      # need this to support SideLoading
      has :author, :class => User

      has_many Event

      def self.cbp_path_regexes
        [%r{^tickets/\d+/audits$}]
      end
    end

    class Comment < DataResource
      include Save

      has_many :uploads, class: Attachment, inline: true
      has :author, class: User

      def save
        if new_record?
          save_associations
          true
        else
          false
        end
      end

      alias :save! :save
    end

    class SatisfactionRating < CreateResource
      class << self
        alias :resource_name :singular_resource_name
      end
    end

    put :mark_as_spam
    post :merge

    has :requester, :class => User, :inline => :create
    has :submitter, :class => User
    has :assignee, :class => User

    has_many :collaborators, class: User, inline: true, extend: (Module.new do
      def to_param
        map(&:id)
      end
    end)

    has_many Audit
    has :metrics, class: TicketMetric
    has Group
    has Organization
    has Brand
    has :related, class: TicketRelated

    has Comment, inline: true
    has_many Comment

    has :last_comment, class: Comment, inline: true
    has_many :last_comments, class: Comment, inline: true

    has_many Tag, extend: Tag::Update, inline: :create

    has_many :incidents, class: Ticket

    # Gets a incremental export of tickets from the start_time until now.
    # @param [Client] client The {Client} object to be used
    # @param [Integer] start_time The start_time parameter
    # @return [Collection] Collection of {Ticket}
    def self.incremental_export(client, start_time)
      ZendeskAPI::Collection.new(client, self, path: "incremental/tickets?start_time=#{start_time.to_i}")
    end

    # Imports a ticket through the imports/tickets endpoint using save!
    # @param [Client] client The {Client} object to be used
    # @param [Hash] attributes The attributes to create.
    # @return [Ticket] Created object or nil
    def self.import!(client, attributes)
      new(client, attributes).tap do |ticket|
        ticket.save!(path: "imports/tickets")
      end
    end

    # Imports a ticket through the imports/tickets endpoint
    # @param [Client] client The {Client} object to be used
    # @param [Hash] attributes The attributes to create.
    # @return [Ticket] Created object or nil
    def self.import(client, attributes)
      ticket = new(client, attributes)
      return unless ticket.save(path: "imports/tickets")
      ticket
    end
  end

  class SuspendedTicket < ReadResource
    include Destroy

    # Recovers this suspended ticket to an actual ticket
    put :recover

    def self.cbp_path_regexes
      [/^suspended_tickets$/]
    end
  end

  class DeletedTicket < ReadResource
    include Destroy
    extend DestroyMany

    # Restores this previously deleted ticket to an actual ticket
    put :restore
    put :restore_many

    def self.cbp_path_regexes
      [/^deleted_tickets$/]
    end
  end

  class UserViewRow < DataResource
    has User
    def self.model_key
      "rows"
    end
  end

  class ViewRow < DataResource
    has Ticket

    # @internal Optional columns

    has Group
    has :assignee, class: User
    has :requester, class: User
    has :submitter, class: User
    has Organization

    def self.model_key
      "rows"
    end
  end

  class RuleExecution < Data
    has_many :custom_fields, :class => TicketField
  end

  class ViewCount < DataResource; end

  class Rule < Resource
    private

    def attributes_for_save
      to_save = [:conditions, :actions, :output].inject({}) { |h, k| h.merge(k => send(k)) }
      { self.class.singular_resource_name.to_sym => attributes.changes.merge(to_save) }
    end
  end

  class TriggerCategory < Resource; end

  module Conditions
    def all_conditions=(all_conditions)
      self.conditions ||= {}
      self.conditions[:all] = all_conditions
    end

    def any_conditions=(any_conditions)
      self.conditions ||= {}
      self.conditions[:any] = any_conditions
    end

    def add_all_condition(field, operator, value)
      self.conditions ||= {}
      self.conditions[:all] ||= []
      self.conditions[:all] << { :field => field, :operator => operator, :value => value }
    end

    def add_any_condition(field, operator, value)
      self.conditions ||= {}
      self.conditions[:any] ||= []
      self.conditions[:any] << { :field => field, :operator => operator, :value => value }
    end
  end

  module Actions
    def add_action(field, value)
      self.actions ||= []
      self.actions << { :field => field, :value => value }
    end
  end

  class View < Rule
    include Conditions

    has_many :tickets, :class => Ticket
    has_many :feed, :class => Ticket, :path => "feed"

    has_many :rows, :class => ViewRow, :path => "execute"
    has :execution, :class => RuleExecution
    has ViewCount, :path => "count"

    def add_column(column)
      columns = execution.columns.map(&:id)
      columns << column
      self.columns = columns
    end

    def columns=(columns)
      self.output ||= {}
      self.output[:columns] = columns
    end

    def self.preview(client, options = {})
      Collection.new(client, ViewRow, options.merge(:path => "views/preview", :verb => :post))
    end

    def self.cbp_path_regexes
      [/^views$/]
    end
  end

  class Trigger < Rule
    include Conditions
    include Actions

    has :execution, :class => RuleExecution

    def self.cbp_path_regexes
      [/^triggers$/, %r{^triggers/active$}]
    end
  end

  class Automation < Rule
    include Conditions
    include Actions

    has :execution, :class => RuleExecution

    def self.cbp_path_regexes
      [/^automations$/]
    end
  end

  class Macro < Rule
    include Actions

    has :execution, :class => RuleExecution

    def self.cbp_path_regexes
      [/^macros$/]
    end

    # Returns the update to a ticket that happens when a macro will be applied.
    # @param [Ticket] ticket Optional {Ticket} to apply this macro to.
    # @raise [Faraday::ClientError] Raised for any non-200 response.
    def apply!(ticket = nil)
      path = "#{self.path}/apply"

      if ticket
        path = "#{ticket.path}/#{path}"
      end

      response = @client.connection.get(path)
      SilentMash.new(response.body.fetch("result", {}))
    end

    # Returns the update to a ticket that happens when a macro will be applied.
    # @param [Ticket] ticket Optional {Ticket} to apply this macro to
    def apply(ticket = nil)
      apply!(ticket)
    rescue Faraday::ClientError
      SilentMash.new
    end
  end

  class UserView < Rule
    def self.preview(client, options = {})
      Collection.new(client, UserViewRow, options.merge!(:path => "user_views/preview", :verb => :post))
    end
  end

  class GroupMembership < Resource
    extend CreateMany
    extend DestroyMany

    has User
    has Group

    def self.cbp_path_regexes
      [%r{^groups/\d+/memberships$}]
    end
  end

  class Group < Resource
    has_many :memberships, class: GroupMembership, path: "memberships"

    def self.cbp_path_regexes
      [/^groups$/, %r{^groups/assignable$}]
    end
  end

  class User < Resource
    extend CreateMany
    extend UpdateMany
    extend CreateOrUpdate
    extend CreateOrUpdateMany
    extend DestroyMany

    class GroupMembership < Resource
      put :make_default
    end

    class Identity < Resource
      # Makes this identity the primary one bumping all other identities down one
      put :make_primary

      # Verifies this identity
      put :verify

      # Requests verification for this identity
      put :request_verification
    end

    def self.cbp_path_regexes
      [/^users$/, %r{^organizations/\d+/users$}]
    end

    any :password

    # Set a user's password
    def set_password(opts = {})
      password(opts.merge(:verb => :post))
    end

    # Change a user's password
    def change_password(opts = {})
      password(opts.merge(:verb => :put))
    end

    # Set a user's password
    def set_password!(opts = {})
      password!(opts.merge(:verb => :post))
    end

    # Change a user's password
    def change_password!(opts = {})
      password!(opts.merge(:verb => :put))
    end

    # Gets a incremental export of users from the start_time until now.
    # @param [Client] client The {Client} object to be used
    # @param [Integer] start_time The start_time parameter
    # @return [Collection] Collection of {User}
    def self.incremental_export(client, start_time)
      ZendeskAPI::Collection.new(client, self, :path => "incremental/users?start_time=#{start_time.to_i}")
    end

    has Organization

    class Session < Resource
    end

    class CurrentSession < SingularResource
      class << self
        def singular_resource_name
          'session'
        end

        alias :resource_name :singular_resource_name
      end
    end

    has_many Session

    def current_session
      ZendeskAPI::User::CurrentSession.find(@client, :user_id => 'me')
    end

    delete :logout

    def clear_sessions!
      @client.connection.delete(path + '/sessions')
    end

    def clear_sessions
      clear_sessions!
    rescue ZendeskAPI::Error::ClientError
      false
    end

    put :merge

    has CustomRole, :inline => true, :include => :roles
    has Role, :inline => true, :include_key => :name
    has Ability, :inline => true
    has :related, :class => UserRelated

    has_many Identity

    has_many Request
    has_many :requested_tickets, :class => Ticket, :path => 'tickets/requested'
    has_many :assigned_tickets, :class => Ticket, :path => 'tickets/assigned'
    has_many :ccd_tickets, :class => Ticket, :path => 'tickets/ccd'

    has_many Group
    has_many GroupMembership
    has_many OrganizationMembership
    has_many OrganizationSubscription

    has_many Setting
    has_many Tag, :extend => Tag::Update, :inline => :create

    def attributes_for_save
      # Don't send role_id, it's necessary
      # for side-loading, but causes problems on save
      # see #initialize
      attrs = attributes.changes.delete_if do |k, _|
        k == "role_id"
      end

      { self.class.singular_resource_name => attrs }
    end

    def handle_response(*)
      super

      # Needed for proper Role sideloading
      self.role_id = role.name if key?(:role)
    end
  end

  class DeletedUser < ReadResource
    include Destroy
  end

  class UserField < Resource; end
  class OrganizationField < Resource; end

  class OauthClient < Resource
    namespace "oauth"

    def self.singular_resource_name
      "client"
    end

    def self.cbp_path_regexes
      [%r{^oauth/clients$}]
    end
  end

  class OauthToken < ReadResource
    include Destroy
    namespace "oauth"

    def self.singular_resource_name
      "token"
    end
  end

  class Target < Resource; end

  class Invocation < Resource; end
  class Webhook < Resource
    has_many Invocation
  end

  module Voice
    include DataNamespace

    class PhoneNumber < Resource
      namespace "channels/voice"
    end

    class Address < Resource
      namespace "channels/voice"
    end

    class Greeting < Resource
      namespace "channels/voice"
    end

    class GreetingCategory < Resource
      namespace "channels/voice"
    end

    class Ticket < CreateResource
      namespace "channels/voice"
    end

    class Agent < ReadResource
      namespace "channels/voice"

      class Ticket < CreateResource
        def new_record?
          true
        end

        def self.display!(client, options)
          new(client, options).tap do |resource|
            resource.save!(path: resource.path + '/display')
          end
        end
      end

      has_many Ticket
    end
  end

  class TicketForm < Resource
    # TODO
    # post :clone
  end

  class AppInstallation < Resource
    namespace "apps"

    def self.singular_resource_name
      "installation"
    end

    # Don't nest attributes
    def attributes_for_save
      attributes.changes
    end

    def handle_response(response)
      @attributes.replace(response.body) if response.body
    end
  end

  class AppNotification < CreateResource
    class << self
      def resource_path
        "apps/notify"
      end
    end

    # Don't nest attributes
    def attributes_for_save
      attributes.changes
    end

    def handle_response(response)
      @attributes.replace(response.body) if response.body.is_a?(Hash)
    end
  end

  class App < Resource
    def initialize(client, attributes = {})
      attributes[:upload_id] ||= nil

      super
    end

    def self.create!(client, attributes = {}, &block)
      if file_path = attributes.delete(:upload)
        attributes[:upload_id] = client.apps.uploads.create!(:file => file_path).id
      end

      super
    end

    class Plan < Resource
    end

    class Upload < Data
      class << self
        def resource_path
          "uploads"
        end
      end

      include Create

      def initialize(client, attributes)
        attributes[:file] ||= attributes.delete(:id)

        super
      end

      # Not nested under :upload, just returns :id
      def save!(*)
        super.tap do
          attributes.id = @response.body["id"]
        end
      end

      # Always save
      def changed?
        true
      end

      # Don't nest attributes
      def attributes_for_save
        attributes
      end
    end

    def self.uploads(client, *args, &block)
      ZendeskAPI::Collection.new(client, Upload, *args, &block)
    end

    def self.installations(client, *args, &block)
      ZendeskAPI::Collection.new(client, AppInstallation, *args, &block)
    end

    has Upload, :path => "uploads"
    has_many Plan

    # Don't nest attributes
    def attributes_for_save
      attributes.changes
    end

    def handle_response(response)
      @attributes.replace(response.body) if response.body
    end
  end

  module DynamicContent
    include DataNamespace

    class Item < ZendeskAPI::Resource
      namespace 'dynamic_content'

      class Variant < ZendeskAPI::Resource
      end

      has_many Variant
    end
  end

  class PushNotificationDevice < DataResource
    def self.destroy_many(client, tokens)
      ZendeskAPI::Collection.new(
        client, self, "push_notification_devices" => tokens,
        :path => "push_notification_devices/destroy_many",
        :verb => :post
      )
    end
  end
end