issue_centre 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/LICENSE.txt +22 -0
- data/README.md +74 -0
- data/Rakefile +9 -0
- data/issue_centre.gemspec +31 -0
- data/lib/issue_centre.rb +15 -0
- data/lib/issue_centre/auth_connection.rb +50 -0
- data/lib/issue_centre/customer_connection.rb +77 -0
- data/lib/issue_centre/exception.rb +10 -0
- data/lib/issue_centre/generic_connection.rb +31 -0
- data/lib/issue_centre/response.rb +125 -0
- data/lib/issue_centre/ticket_connection.rb +76 -0
- data/lib/issue_centre/version.rb +3 -0
- data/test/test_connection.rb +7 -0
- data/test/test_generate_key.rb +7 -0
- data/test/test_get_contracts.rb +7 -0
- data/test/test_helper.rb +9 -0
- metadata +179 -0
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,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
|
data/lib/issue_centre.rb
ADDED
@@ -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,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
|