autotask_ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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