autotask_ruby 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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +54 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +7 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +7 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +8 -0
  10. data/Gemfile.lock +152 -0
  11. data/Guardfile +72 -0
  12. data/LICENSE +21 -0
  13. data/README.md +73 -0
  14. data/Rakefile +8 -0
  15. data/atws-1_6.wsdl +3263 -0
  16. data/atws.wsdl +3191 -0
  17. data/autotask_ruby.gemspec +51 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/lib/autotask_ruby.rb +30 -0
  21. data/lib/autotask_ruby/account.rb +14 -0
  22. data/lib/autotask_ruby/account_to_do.rb +20 -0
  23. data/lib/autotask_ruby/action_type.rb +13 -0
  24. data/lib/autotask_ruby/appointment.rb +20 -0
  25. data/lib/autotask_ruby/association.rb +38 -0
  26. data/lib/autotask_ruby/client.rb +91 -0
  27. data/lib/autotask_ruby/configuration.rb +25 -0
  28. data/lib/autotask_ruby/constants.rb +7 -0
  29. data/lib/autotask_ruby/contact.rb +26 -0
  30. data/lib/autotask_ruby/create_response.rb +5 -0
  31. data/lib/autotask_ruby/delete_response.rb +5 -0
  32. data/lib/autotask_ruby/entity.rb +113 -0
  33. data/lib/autotask_ruby/fields.rb +16 -0
  34. data/lib/autotask_ruby/project.rb +20 -0
  35. data/lib/autotask_ruby/query.rb +22 -0
  36. data/lib/autotask_ruby/query_response.rb +8 -0
  37. data/lib/autotask_ruby/resource.rb +22 -0
  38. data/lib/autotask_ruby/response.rb +28 -0
  39. data/lib/autotask_ruby/service_call.rb +24 -0
  40. data/lib/autotask_ruby/service_call_ticket.rb +11 -0
  41. data/lib/autotask_ruby/service_call_ticket_resource.rb +11 -0
  42. data/lib/autotask_ruby/task.rb +19 -0
  43. data/lib/autotask_ruby/ticket.rb +18 -0
  44. data/lib/autotask_ruby/version.rb +5 -0
  45. data/lib/autotask_ruby/zone_info.rb +31 -0
  46. metadata +273 -0
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'autotask_ruby/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'autotask_ruby'
9
+ spec.version = AutotaskRuby::VERSION
10
+ spec.authors = ['Jared L Jennings']
11
+ spec.email = ['jared@jaredjennings.org']
12
+
13
+ spec.summary = 'A ruby client for the Autotask API'
14
+ spec.description = 'A ruby client for the Autotask API. The client tries to use a full-featured approach.'
15
+ spec.homepage = 'https://github.com/trepidity/autotask_ruby'
16
+ spec.license = 'MIT'
17
+
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ spec.metadata['source_code_uri'] = 'https://github.com/trepidity/autotask_ruby'
23
+ else
24
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
25
+ end
26
+
27
+ # Specify which files should be added to the gem when it is released.
28
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
29
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
30
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
31
+ end
32
+ spec.bindir = 'exe'
33
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
35
+ spec.require_paths = ['lib']
36
+
37
+ spec.add_runtime_dependency 'activesupport', '~> 5.2'
38
+ spec.add_runtime_dependency 'savon', '~> 2.12'
39
+
40
+ spec.add_development_dependency 'awesome_print', '~> 1.8'
41
+ spec.add_development_dependency 'bundler', '~> 1.17'
42
+ spec.add_development_dependency 'byebug', '~> 10.0'
43
+ spec.add_development_dependency 'dotenv', '~> 2.5'
44
+ spec.add_development_dependency 'guard', '~> 2.15'
45
+ spec.add_development_dependency 'guard-rspec', '~> 4.7'
46
+ spec.add_development_dependency 'rubocop-rspec', '~> 1.31'
47
+ spec.add_development_dependency 'rake', '~> 12.3'
48
+ spec.add_development_dependency 'rspec', '~> 3.8'
49
+ spec.add_development_dependency 'rubocop', '~> 0.62'
50
+ spec.add_development_dependency 'webmock', '~> 3.5'
51
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'autotask_ruby'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'active_support/core_ext/class/attribute'
5
+ require 'active_support/core_ext/string/inflections'
6
+ require 'active_support/core_ext/hash'
7
+ require 'savon'
8
+ require 'nokogiri'
9
+ require 'autotask_ruby/configuration'
10
+ require 'autotask_ruby/constants'
11
+ require 'autotask_ruby/query'
12
+ require 'autotask_ruby/association'
13
+ require 'autotask_ruby/response'
14
+ require 'autotask_ruby/query_response'
15
+ require 'autotask_ruby/create_response'
16
+ require 'autotask_ruby/delete_response'
17
+ require 'autotask_ruby/zone_info'
18
+ require 'autotask_ruby/entity'
19
+ require 'autotask_ruby/client'
20
+ require 'autotask_ruby/resource'
21
+ require 'autotask_ruby/account'
22
+ require 'autotask_ruby/contact'
23
+ require 'autotask_ruby/account_to_do'
24
+ require 'autotask_ruby/appointment'
25
+ require 'autotask_ruby/task'
26
+ require 'autotask_ruby/ticket'
27
+ require 'autotask_ruby/project'
28
+ require 'autotask_ruby/service_call'
29
+ require 'autotask_ruby/service_call_ticket'
30
+ require 'autotask_ruby/service_call_ticket_resource'
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutotaskRuby
4
+ # Represents the Autotask Account Entity
5
+ class Account
6
+ include Entity
7
+
8
+ FIELDS = %i[id Address1 City Country CreateDate AccountName AccountNumber Phone PostalCode State Active].freeze
9
+ .each do |field|
10
+ self.attr_accessor :"#{field.to_s.underscore}"
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutotaskRuby
4
+ # Represents the Autotask AccountToDo entity
5
+ class AccountToDo
6
+ include AutotaskRuby::Entity
7
+ include AutotaskRuby::Query
8
+
9
+ FIELDS = %i[id AccountID ContactID ActivityDescription StartDateTime EndDateTime
10
+ AssignedToResourceID ActionType CreateDateTime LastModifiedDate].freeze
11
+ .each do |field|
12
+ self.attr_accessor :"#{field.to_s.underscore}"
13
+ end
14
+
15
+ def post_initialize
16
+ belongs_to :resource
17
+ belongs_to :account
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutotaskRuby
4
+ # Represents the Autotask Entity Action Type
5
+ class ActionType
6
+ include Entity
7
+
8
+ FIELDS = %i[id Name View Active SystemActionType].freeze
9
+ .each do |field|
10
+ self.attr_accessor :"#{field.to_s.underscore}"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutotaskRuby
4
+
5
+ # Represents an AutoTask Appointment Entity
6
+ class Appointment
7
+ include Entity
8
+
9
+ FIELDS = %i[id Title Description ResourceID StartDateTime EndDateTime CreateDateTime UpdateDateTime].freeze
10
+ .each do |field|
11
+ self.attr_accessor :"#{field.to_s.underscore}"
12
+ end
13
+
14
+ def post_initialize
15
+ belongs_to :resource
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,38 @@
1
+ module AutotaskRuby
2
+ # handles loading associated entities.
3
+ module Association
4
+ # loads parent entity.
5
+ # Thanks to scoop for this example.
6
+ def belongs_to(name, options = {})
7
+ name = name.to_s
8
+ klass = "#{(options[:class_name] || name).to_s.classify}"
9
+ foreign_key = name.foreign_key
10
+ define_singleton_method(name) do
11
+ find(klass, send(foreign_key))
12
+ end
13
+ end
14
+
15
+ # Example, an appointment can have one contact.
16
+ # Thanks to scoop for this example.
17
+ def has_one(name, options = {})
18
+ name = name.to_s
19
+ options.reverse_merge! foreign_key: self.to_s.foreign_key.camelize
20
+ klass = "#{(options[:class_name] || name).to_s.classify}"
21
+ define_singleton_method(name) do
22
+ find(klass, options[:foreign_key], id).first
23
+ end
24
+ end
25
+
26
+ # loads child entities.
27
+ # Example: A project can have many tasks.
28
+ # Thanks to scoop for this example.
29
+ def has_many(name, options = {})
30
+ name = name.to_s
31
+ options.reverse_merge! foreign_key: self.to_s.foreign_key.camelize
32
+ klass = "#{(options[:class_name] || name).to_s.classify}"
33
+ define_singleton_method(name) do
34
+ query(klass, options[:foreign_key], id)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'autotask_ruby/version'
4
+ require 'autotask_ruby/resource'
5
+
6
+ module AutotaskRuby
7
+
8
+ # the primary client that interfaces with the SOAP Client that will interface with AutoTask.
9
+ class Client
10
+ attr_accessor :soap_client, :headers, :logger
11
+
12
+ def initialize(options = {})
13
+ @version = options[:version] || AutotaskRuby.configuration.version
14
+ integration_code = options[:integration_code] || AutotaskRuby.configuration.integration_code
15
+ @headers = {
16
+ 'tns:AutotaskIntegrations' =>
17
+ {
18
+ 'tns:IntegrationCode' => integration_code
19
+ }
20
+ }
21
+
22
+ @ssl_version = options[:ssl_version] || :TLSv1_2
23
+
24
+ @host = options[:host] || 'webservices.autotask.net'
25
+ @endpoint = options[:endpoint] || "https://#{@host}/ATServices/#{@version}/atws.asmx"
26
+
27
+ # Override optional Savon attributes
28
+ savon_options = {}
29
+ %w[read_timeout open_timeout proxy raise_errors log_level basic_auth log raise_errors].each do |prop|
30
+ key = prop.to_sym
31
+ savon_options[key] = options[key] if options.key?(key)
32
+ end
33
+
34
+ @soap_client = Savon.client({
35
+ wsdl: './atws.wsdl',
36
+ soap_header: @headers,
37
+ namespaces: { xmlns: AutotaskRuby.configuration.namespace },
38
+ logger: Logger.new($stdout),
39
+ raise_errors: false,
40
+ log: true,
41
+ endpoint: @endpoint,
42
+ ssl_version: @ssl_version # Sets ssl_version for HTTPI adapter
43
+ }.update(savon_options))
44
+ end
45
+
46
+ # Public: Get the names of all wsdl operations.
47
+ # List all available operations from the atws.wsdl
48
+ def operations
49
+ @soap_client.operations
50
+ end
51
+
52
+ # @param entity, id
53
+ # pass in the entity_type, I.E. AccountToDo, Resource, etc. and the ID of the entity.
54
+ # @return Entity
55
+ # Returns a single Entity if a match was found.
56
+ # Returns nil if no match is found.
57
+ def find(entity, id)
58
+ response = query(entity.to_s, id)
59
+
60
+ return nil if response.entities.empty?
61
+
62
+ response.entities.first
63
+ end
64
+
65
+ # @param entity_type and value
66
+ # Other parameters, are optional.
67
+ # full set of parameters include entity_type, field, operation, value.
68
+ # Queries the Autotask QUERY API. Returns a QueryResponse result set.
69
+ # @return AutotaskRuby::Response.
70
+ def query(entity_type, field = 'id', operation = 'equals', value)
71
+ result = @soap_client.call(:query, message: "<sXML><![CDATA[<queryxml><entity>#{entity_type}</entity><query><field>#{field}<expression op=\"#{operation}\">#{value}</expression></field></query></queryxml>]]></sXML>")
72
+ AutotaskRuby::QueryResponse.new(self, result)
73
+ end
74
+
75
+ # @param entity_type
76
+ # include the entity type. ServiceCall, Appointment, etc.
77
+ # @param ids
78
+ # One or more entity ID's that should be deleted.
79
+ # @return
80
+ # AutotaskRuby::DeleteResponse
81
+ def delete(entity_type, *ids)
82
+ entities = ++''
83
+ ids.each do |id|
84
+ entities << "<Entity xsi:type=\"#{entity_type}\"><id xsi:type=\"xsd:int\">#{id}</id></Entity>"
85
+ end
86
+ resp = @soap_client.call(:delete, message: "<Entities>#{entities.to_s}</Entities>")
87
+ AutotaskRuby::DeleteResponse.new(@client, resp)
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,25 @@
1
+ module AutotaskRuby
2
+
3
+ class Configuration
4
+ attr_accessor :integration_code
5
+ attr_accessor :version
6
+ attr_accessor :namespace
7
+
8
+ def initialize
9
+ @integration_code = nil
10
+ @version = 1.5
11
+ @namespace = 'http://autotask.net/ATWS/v1_5/'
12
+ end
13
+ end
14
+
15
+ class << self
16
+ def configuration
17
+ @configuration ||= Configuration.new
18
+ end
19
+
20
+ def configure
21
+ yield(configuration)
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutotaskRuby
4
+ module Constants
5
+ AUTOTASK_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutotaskRuby
4
+
5
+ # Represents the Autotask Entity Contact
6
+ class Contact
7
+ include Entity
8
+
9
+ FIELDS = %i[id Active AddressLine City Country CreateDate EMailAddress Extension FirstName AccountID LastName MobilePhone Phone State Title ZipCode].freeze
10
+ .each do |field|
11
+ self.attr_accessor :"#{field.to_s.underscore}"
12
+ end
13
+
14
+ def post_initialize
15
+ belongs_to :account
16
+ end
17
+
18
+ def full_name
19
+ [@first_name, @last_name].join(' ')
20
+ end
21
+
22
+ def email
23
+ @e_mail_address
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ module AutotaskRuby
2
+ class CreateResponse
3
+ include Response
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module AutotaskRuby
2
+ class DeleteResponse
3
+ include Response
4
+ end
5
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AutotaskRuby
4
+ # The base type for all objects represented by AutotaskRuby.
5
+ # This module should extend any object type being used with AutoTask.
6
+ module Entity
7
+ include AutotaskRuby::Constants
8
+ include AutotaskRuby::Association
9
+
10
+ FIELDS = %i[id].freeze
11
+
12
+ def self.included(base)
13
+ base.const_set :FIELDS, Entity::FIELDS unless base.const_defined?(:FIELDS)
14
+ end
15
+
16
+ def initialize(options = {})
17
+ @client = options if options.instance_of?(Client)
18
+ return unless options.kind_of?(Hash)
19
+
20
+ options.each do |k, v|
21
+ instance_variable_set("@#{k}", v)
22
+ end
23
+
24
+ post_initialize
25
+ end
26
+
27
+ # default post_initialize methods.
28
+ # Other classes that implement the Entity Class may override this.
29
+ def post_initialize
30
+
31
+ end
32
+
33
+ # Iterates the fields of a Entity Class Type.
34
+ # The fields are turned into instance variables.
35
+ # Fields could include id, StartDateTime, Title, etc.
36
+ def build(entity)
37
+ self.class.const_get(:FIELDS).each do |field|
38
+ instance_variable_set("@#{field}".underscore, field_set(entity, field))
39
+ end
40
+ end
41
+
42
+ # updates the entity in the AutoTask API.
43
+ # All fields are iterated and this builds the XML message that is sent to AutoTask.
44
+ # Any field that is not filled out will be sent as empty. This means that it will wipe
45
+ # any value that AutoTask has for that field.
46
+ def update
47
+ @client.soap_client.call(:update, message: "<Entity xsi:type=\"#{self.class.to_s.demodulize}\">#{fields_to_xml}</Entity>")
48
+ end
49
+
50
+ # creates an entity in AutoTask.
51
+ def create
52
+ result = @client.soap_client.call(:create, message: "<Entity xsi:type=\"#{self.class.to_s.demodulize}\">#{fields_to_xml}</Entity>")
53
+ CreateResponse.new(@client, result)
54
+ end
55
+
56
+ # converts the AutoTask dateTime string value to a ActiveSupport::TimeWithZone object.
57
+ # All dateTimes in AutoTask are in the Eastern Timezone.
58
+ def to_date_time(arg)
59
+ Time.find_zone!('Eastern Time (US & Canada)').parse(arg)
60
+ end
61
+
62
+ # Converts the specified field in the AutoTask XML response to the entity object field/attribute.
63
+ # @param
64
+ # - entity
65
+ # - field
66
+ def field_set(entity, field)
67
+ node = entity.xpath("Autotask:#{field}", Autotask: AutotaskRuby.configuration.namespace)
68
+
69
+ # entity may not contain all fields that are possible.
70
+ # Example: The entity may not have a contact specified.
71
+ return if node.blank?
72
+
73
+ return node.text.to_i if node.attr('type').blank? || node.attr('type').text.eql?('xsd:int')
74
+ return to_date_time(node.text) if node.attr('type').text.eql?('xsd:dateTime')
75
+ return node.text.to_f if node.attr('type').text.eql?('xsd:double')
76
+ return node.text.to_f if node.attr('type').text.eql?('xsd:decimal')
77
+
78
+ node.text
79
+ end
80
+
81
+ def to_bool(arg)
82
+ return true if arg == true || arg =~ (/(true|t|yes|y|1)$/i)
83
+ return false if arg == false || arg.empty? || arg =~ (/(false|f|no|n|0)$/i)
84
+ raise ArgumentError.new("invalid value for Boolean: \"#{arg}\"")
85
+ end
86
+
87
+ # converts the entity attributes to XML representation.
88
+ # This is used when sending the object to the AutoTask API.
89
+ def fields_to_xml
90
+ str = ++''
91
+
92
+ self.class.const_get(:FIELDS).each do |field|
93
+ obj = instance_variable_get("@#{field}".underscore)
94
+ next unless obj
95
+
96
+ str << format_field_to_xml(field, obj)
97
+ end
98
+ str
99
+ end
100
+
101
+ # Returns the specified field as an XML element for the XML Body.
102
+ # I.E. <id>xxx</id>
103
+ # @param field_name
104
+ # the field name
105
+ # @param field_value
106
+ # the field value.
107
+ def format_field_to_xml(field_name, field_value)
108
+ return "<#{field_name}>#{field_value.strftime(AUTOTASK_TIME_FORMAT)}</#{field_name}>" if field_value.instance_of?(ActiveSupport::TimeWithZone)
109
+
110
+ "<#{field_name}>#{field_value}</#{field_name}>"
111
+ end
112
+ end
113
+ end