issue_centre 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Jason Holloway
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # IssueCentre
2
+
3
+ This library is designed to help Ruby/Rails based applications
4
+ communicate with the publicly available API for IssueCentre, as
5
+ provided by First Option Software (http://www.bespokesoftware.com/).
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'issue_centre'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install issue_centre
23
+
24
+ ## Usage
25
+
26
+ Basic usage:
27
+
28
+ ```ruby
29
+ require 'rubygems'
30
+ require 'issue_centre'
31
+
32
+ # Create an authentication client connection to authenticate session
33
+ auth_conn = IssueCentre::AuthConnection.new( "<IssueCentre Auth URL>")
34
+
35
+ # Collect all applicable contracts
36
+ contracts = auth_conn.get_contracts( "<username>", "<password>")
37
+
38
+
39
+ # Obtain a session key for a relevant contract
40
+ session_key = auth_conn.generate_key( "<username>", "<password>", contracts.last[:id])
41
+
42
+ # Create a customer connection (note this is usually a different URL)
43
+ cust_conn = IssueCentre::CustomerConnection.new( "<IssueCentre Customer URL>"), session_key)
44
+
45
+ # Grab a list of all companies (you can also search with a wildcard)
46
+ companies = cust_conn.get_companies( session_key)
47
+
48
+ # or using a wildcard to find companies beginning with "British"...
49
+ companies = cust_conn.get_companies( session_key, "British*")
50
+
51
+ # Grab a list of all contacts (you can also search with a wildcard)
52
+ contacts = cust_conn.get_contacts( session_key)
53
+
54
+ # Grab a list of all countries
55
+ countries = cust_conn.get_countries( session_key)
56
+
57
+ # Create a ticket connection (note this is usually a different URL)
58
+ ticket_conn = IssueCentre::TicketConnection.new( "<IssueCentre Ticket URL>", session_key)
59
+
60
+ # Grab a list of open tickets for the specified company
61
+ open_tickets = ticket_conn.get_open_tickets( session_key, companies.first[:id], 100, 1)
62
+
63
+ # Grab a list of closed tickets for the specified company
64
+ closed_tickets = ticket_conn.get_closed_tickets( session_key, companies.first[:id], 100, 1)
65
+
66
+ ```
67
+
68
+ ## Contributing
69
+
70
+ 1. Fork it ( https://github.com/kitebuggy/issue_centre/fork )
71
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
72
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
73
+ 4. Push to the branch (`git push origin my-new-feature`)
74
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList[ 'test/test_*.rb']
8
+ end
9
+
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'issue_centre/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "issue_centre"
8
+ spec.version = IssueCentre::VERSION
9
+ spec.authors = ["Jason Holloway"]
10
+ spec.email = ["jason_holloway@mac.com"]
11
+ spec.summary = %q{Gem to wrap the IssueCentre API.}
12
+ spec.description = %q{Gem to wrap the IssueCentre API.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_development_dependency "minitest"
25
+ spec.add_development_dependency "vcr"
26
+ spec.add_development_dependency "webmock"
27
+
28
+ spec.add_dependency "faraday"
29
+ spec.add_dependency "savon", "~> 2.0"
30
+ spec.add_dependency "activesupport"
31
+ end
@@ -0,0 +1,15 @@
1
+ require 'bundler/setup'
2
+ require 'savon'
3
+ require 'active_support/core_ext/string/inflections'
4
+
5
+ $: << File.expand_path( File.dirname(__FILE__))
6
+
7
+ require 'issue_centre/version'
8
+ require 'issue_centre/exception'
9
+
10
+ require 'issue_centre/generic_connection'
11
+ require 'issue_centre/auth_connection'
12
+ require 'issue_centre/customer_connection'
13
+ require 'issue_centre/ticket_connection'
14
+ require 'issue_centre/response'
15
+
@@ -0,0 +1,50 @@
1
+ module IssueCentre
2
+ class AuthConnection < GenericConnection
3
+
4
+ # Connection client for authenticating and retrieving contracts
5
+ # from IssueCentre
6
+ #
7
+ # @param [String] endpoint_url IssueCentre endpoint url
8
+ # (e.g. {https://support.callclosed.net/issuecentre/Connection})
9
+ # @param [Hash] options Other options to pass to the constructors
10
+ #
11
+ # @return [AuthConnection] Instance of AuthConnection client
12
+ #
13
+ def initialize( auth_url, options = {})
14
+ super( auth_url, options)
15
+ end
16
+
17
+ # Obtain possible contracts from IssueCentre for this user
18
+ #
19
+ # @param [String] username A valid user in IssueCentre
20
+ # @param [String] password A valid password in IssueCentre
21
+ #
22
+ # @return [Response] A Response object
23
+ #
24
+ def get_contracts( username, password)
25
+ response_xml = self.call( :get_contracts, message: {
26
+ arg0: username,
27
+ arg1: password
28
+ })
29
+ response = IssueCentre::Response.parse( response_xml)
30
+ end
31
+
32
+ # Generate a contract-specific session key for this user from IssueCentre
33
+ #
34
+ # @param [String] username A valid user in IssueCentre
35
+ # @param [String] password A valid password in IssueCentre
36
+ # @param [String] contract A valid contract for this user
37
+ #
38
+ # @return [Array] Nokogiri::XML::Element array of return values
39
+ #
40
+ def generate_key( username, password, contract)
41
+ response_xml = self.call( :generate_key, message: {
42
+ arg0: username,
43
+ arg1: password,
44
+ arg2: contract
45
+ })
46
+ response = IssueCentre::Response.parse( response_xml,
47
+ {contract_id: contract})
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,77 @@
1
+ module IssueCentre
2
+ class CustomerConnection < GenericConnection
3
+
4
+ # Connection client for authenticating and retrieving customer
5
+ # information from IssueCentre
6
+ #
7
+ # @param [String] endpoint_url IssueCentre endpoint url
8
+ # (e.g. {https://support.callclosed.net/issuecentre/Customer})
9
+ # @param [String] session_key SessionKey for this session
10
+ # @param [Hash] options Other options to pass to the constructors
11
+ #
12
+ # @return [CustomerConnection] Instance of CustomerConnection client
13
+ #
14
+ def initialize( customer_url, session_key, options = {})
15
+ # @session_key = session_key
16
+ super( customer_url, options)
17
+ end
18
+
19
+
20
+ # Create companies from IssueCentre for this contract
21
+ #
22
+ # @param [String] session_key SessionKey for this session
23
+ #
24
+ # @param [String] wildcard The wildcard used to match the company
25
+ # names. E.g. 'a*', 'Brit?sh'. The wildcard is case
26
+ # insensitive. An empty string will return all companies.
27
+ #
28
+ # @return [Response] A Response object
29
+ #
30
+ def get_companies( session_key, wildcard = '')
31
+ response_xml = self.call( :get_companies, message: {
32
+ arg0: session_key,
33
+ arg1: wildcard
34
+ })
35
+ response = IssueCentre::Response.parse( response_xml)
36
+ end
37
+
38
+
39
+ # Create contacts from IssueCentre for this contract
40
+ #
41
+ # @param [String] session_key SessionKey for this session
42
+ #
43
+ # @param [Integer] company_id The numeric ID of the company for
44
+ # which to return contacts for. Defaults to returning all
45
+ # contacts for all companies.
46
+ #
47
+ # @param [String] wildcard The wildcard used to match the contact
48
+ # names. E.g. 'Ala?', 'Jas*'. The wildcard is case
49
+ # insensitive. An empty string will return all contacts.
50
+ #
51
+ # @return [Response] A Response object
52
+ #
53
+ def get_contacts( session_key, company_id = 0, wildcard = '')
54
+ response_xml = self.call( :get_contacts, message: {
55
+ arg0: session_key,
56
+ arg1: company_id,
57
+ arg2: wildcard
58
+ })
59
+ response = IssueCentre::Response.parse( response_xml)
60
+ end
61
+
62
+
63
+ # Create countries from IssueCentre for this contract
64
+ #
65
+ # @param [String] session_key SessionKey for this session
66
+ #
67
+ # @return [Response] A Response object
68
+ #
69
+ def get_countries( session_key)
70
+ response_xml = self.call( :get_countries, message: {
71
+ arg0: session_key,
72
+ arg1: ''
73
+ })
74
+ response = IssueCentre::Response.parse( response_xml)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,10 @@
1
+ module IssueCentre
2
+ class Error < StandardError
3
+ end
4
+
5
+ class ParseError < Error
6
+ end
7
+
8
+ class AuthenticationError < Error
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ module IssueCentre
2
+ class GenericConnection < Savon::Client
3
+
4
+ # Never used directly. Use sub-classes instead.
5
+ # @see AuthConnection
6
+ # @see CustomerConnection
7
+ #
8
+ # @param [String] endpoint_url IssueCentre endpoint url
9
+ # (e.g. {https://support.callclosed.net/issuecentre/Connection})
10
+ # @param [String] username A valid IssueCentre username
11
+ # @param [String] password A valid IssueCentre password
12
+ # @param [Hash] options Other options to pass to the constructors
13
+ #
14
+ # @return [Connection] instance of Connection
15
+ #
16
+ def initialize( base_url, options = {})
17
+
18
+ @log = options[:log] || false
19
+ @log_level = options[:log_level] || :info
20
+ @wsdl_suffix = options[:wsdl_suffix] || "?wsdl"
21
+ @endpoint_url = options[:endpoint] || base_url
22
+ @issue_centre_url = base_url + @wsdl_suffix
23
+
24
+ super( wsdl: @issue_centre_url,
25
+ endpoint: @endpoint_url,
26
+ log_level: @log_level,
27
+ log: @log
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,125 @@
1
+ module IssueCentre
2
+ class Response
3
+
4
+ class << self
5
+
6
+ # Parse the response retrieved during any request.
7
+ #
8
+ # @param [XML] savon_response Savon's representation of the
9
+ # response
10
+ #
11
+ # @param [Hash] options Any default attributes that should be
12
+ # added to the model
13
+ #
14
+ # @return [Array] Array of hashes with combined response details
15
+ #
16
+ def parse( savon_response, options = {})
17
+
18
+ # retrieve the embedded xml
19
+ embedded_xml = savon_response.xpath( '//return')
20
+
21
+ # check for responses we don't understand
22
+ if embedded_xml.blank?
23
+ raise IssueCentre::ParseError.new( savon_response.body.keys.first)
24
+ end
25
+
26
+ # parse the embbeded xml
27
+ doc = Nokogiri::XML.parse( embedded_xml.text)
28
+ model_name = which_model( doc)
29
+
30
+ case doc.internal_subset.name
31
+ when 'generateKey'
32
+ # special case for session keys
33
+ return doc.text
34
+ else
35
+ if doc.root.name == 'return'
36
+ # Embedded XML within an embedded XML. Yuk.
37
+ raise IssueCentre::AuthenticationError.new( doc.text)
38
+ end
39
+ end
40
+
41
+ arr, obj = [], {}
42
+ arr, obj = recurse_and_build_model( doc, model_name, arr, obj)
43
+ arr << obj unless obj.empty?
44
+ return arr
45
+ end
46
+
47
+
48
+ # Traverse through the namespace tree, building the object(s)
49
+ # along the way
50
+ #
51
+ # @param [Nokogiri::XML] fragment Fragment of the XML tree
52
+ # @param [String] model_name IssueCentre model being built
53
+ # @param [Array] arr Array of object hashes built so far
54
+ # @param [Hash] obj Object to be built (or being built)
55
+ #
56
+ # @return [Array] Array of hashes representing the XML objects
57
+ # @return [Hash] Hash representing the last object being built
58
+ # (which may be empty)
59
+ #
60
+ def recurse_and_build_model( fragment, model_name, arr, obj)
61
+ fragment.elements.each do |element|
62
+ arr, obj = recurse_and_build_model( element, model_name, arr, obj)
63
+ end
64
+ fragment.attribute_nodes.each do |attr|
65
+ case attr.name
66
+ when 'defaultContract'
67
+ obj[ :id] = attr.value.to_i
68
+ obj[ :default_contract] = true
69
+ obj[ :name] = 'defaultContract'
70
+ when 'company'
71
+ obj[ :company_name] = attr.value
72
+ when 'country'
73
+ obj[ :country_id] = attr.value
74
+ when 'isDefault'
75
+ obj[ :is_default] = attr.value == "1"
76
+ when 'ticketCount', 'changeType', 'supportType'
77
+ # skip (we don't need summaries)
78
+ else
79
+ obj[ attr.name.underscore.to_sym] = attr.value
80
+ end
81
+ end
82
+ if fragment.attribute_nodes.empty? && fragment.elements.empty?
83
+ # We're now close to the leaves themselves
84
+ fragment.children.each do |child|
85
+ if child.text?
86
+ case fragment.name
87
+ when 'date', 'dateclosed'
88
+ obj[ fragment.name.underscore.to_sym] =
89
+ Time.at( child.text.to_i / 1000)
90
+ else
91
+ obj[ fragment.name.underscore.to_sym] = child.text
92
+ end
93
+ end
94
+ end
95
+ end
96
+ if fragment.name == model_name
97
+ arr << obj unless obj.empty?
98
+ obj = {}
99
+ end
100
+ return arr, obj
101
+ end
102
+
103
+ private
104
+
105
+ # Returns the model name corresponding to the supplied
106
+ # Nokogiri::XML document.
107
+ #
108
+ # @param [Nokogiri::XML] doc Nokogiri document of the XML tree
109
+ #
110
+ # @return [String] The IssueCentre model name for this XML object.
111
+ #
112
+ def which_model( doc)
113
+ # Convert pluralised string into model name,
114
+ # e.g. "contracts" => "Contract"
115
+ model_name = doc.root.internal_subset.name.classify
116
+
117
+ if model_name == 'GenerateKey'
118
+ model_name = 'SessionKey'
119
+ end
120
+
121
+ model_name.downcase
122
+ end
123
+ end
124
+ end
125
+ end