client_success 0.1.2

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +466 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +111 -0
  8. data/README.md +52 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/client_success.gemspec +44 -0
  13. data/lib/client_success/access_token.rb +25 -0
  14. data/lib/client_success/client.rb +115 -0
  15. data/lib/client_success/client_type.rb +18 -0
  16. data/lib/client_success/connection.rb +99 -0
  17. data/lib/client_success/contact.rb +104 -0
  18. data/lib/client_success/domain_model/access_token.rb +11 -0
  19. data/lib/client_success/domain_model/client.rb +14 -0
  20. data/lib/client_success/domain_model/client_type.rb +14 -0
  21. data/lib/client_success/domain_model/contact.rb +14 -0
  22. data/lib/client_success/domain_model/custom_field_value.rb +9 -0
  23. data/lib/client_success/domain_model/employee.rb +14 -0
  24. data/lib/client_success/domain_model/product.rb +10 -0
  25. data/lib/client_success/domain_model/security_role.rb +9 -0
  26. data/lib/client_success/domain_model/status.rb +9 -0
  27. data/lib/client_success/domain_model/subscription.rb +12 -0
  28. data/lib/client_success/domain_model/success_cycle.rb +9 -0
  29. data/lib/client_success/domain_model/to_do.rb +9 -0
  30. data/lib/client_success/employee.rb +18 -0
  31. data/lib/client_success/product.rb +16 -0
  32. data/lib/client_success/schema/client/create.rb +34 -0
  33. data/lib/client_success/schema/client/update.rb +37 -0
  34. data/lib/client_success/schema/contact/create.rb +28 -0
  35. data/lib/client_success/schema/contact/update.rb +27 -0
  36. data/lib/client_success/schema/subscription/create.rb +20 -0
  37. data/lib/client_success/schema/subscription/update.rb +21 -0
  38. data/lib/client_success/status.rb +23 -0
  39. data/lib/client_success/subscription.rb +51 -0
  40. data/lib/client_success/to_do.rb +23 -0
  41. data/lib/client_success/types.rb +7 -0
  42. data/lib/client_success/version.rb +3 -0
  43. data/lib/client_success.rb +18 -0
  44. metadata +266 -0
@@ -0,0 +1,44 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "client_success/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "client_success"
7
+ spec.version = ClientSuccess::VERSION
8
+ spec.authors = ["Practice Ignition"]
9
+ spec.email = ["dev@practiceignition.com"]
10
+
11
+ spec.summary = "An unofficial Ruby wrapper for Client Success's REST API"
12
+ spec.homepage = "https://github.com/ignitionapp/"
13
+
14
+ # Specify which files should be added to the gem when it is released.
15
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
16
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
17
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency("bundler", "~> 2.0")
24
+ spec.add_development_dependency("rake", ">= 12.3.3")
25
+ spec.add_development_dependency("rspec", "~> 3.0")
26
+
27
+ spec.add_development_dependency("vcr")
28
+ spec.add_development_dependency("faker")
29
+
30
+ spec.add_development_dependency("rubocop", "0.77.0")
31
+ spec.add_development_dependency("rubocop-rspec")
32
+
33
+ spec.add_runtime_dependency("faraday")
34
+ spec.add_runtime_dependency("faraday_middleware")
35
+
36
+ # TODO: don't force the use of typhoeus
37
+ spec.add_runtime_dependency("typhoeus")
38
+
39
+ spec.add_runtime_dependency("dry-types", "0.11.0")
40
+ spec.add_runtime_dependency("hashie")
41
+
42
+ # TODO: remove activesupport as a dependency
43
+ spec.add_runtime_dependency("activesupport", "~> 5.2")
44
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "connection"
2
+ require_relative "domain_model/access_token"
3
+
4
+ module ClientSuccess
5
+ module AccessToken
6
+ extend self
7
+
8
+ class Error < StandardError; end
9
+ class InvalidCredentials < Error; end
10
+
11
+ def create(username:, password:,
12
+ connection: ClientSuccess::Connection.new)
13
+ response = connection.post("/v1/auth",
14
+ username: username,
15
+ password: password)
16
+
17
+ payload = response.body
18
+
19
+ DomainModel::AccessToken.new(
20
+ payload.deep_transform_keys(&:underscore))
21
+ rescue Connection::Unauthorised
22
+ raise InvalidCredentials, "invalid username or password"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,115 @@
1
+ require_relative "connection"
2
+ require_relative "domain_model/client"
3
+ require_relative "schema/client/create"
4
+ require_relative "schema/client/update"
5
+
6
+ module ClientSuccess
7
+ module Client
8
+ extend self
9
+
10
+ class Error < StandardError; end
11
+ class NotFound < Error; end
12
+
13
+ def list_all(assigned_csm_id: nil, active_only: true,
14
+ connection:)
15
+ params = {
16
+ "assignedCsmId" => assigned_csm_id,
17
+ "activeOnly" => active_only
18
+ }
19
+
20
+ response = connection.get(
21
+ "/v1/clients?#{params.compact.to_query}")
22
+
23
+ response.body.map do |payload|
24
+ DomainModel::Client.new(
25
+ payload.deep_transform_keys(&:underscore))
26
+ end
27
+ end
28
+
29
+ def get_details(client_id:, connection:)
30
+ response = connection.get(
31
+ "/v1/clients/#{client_id}")
32
+
33
+ payload = response.body
34
+
35
+ DomainModel::Client.new(
36
+ payload.deep_transform_keys(&:underscore))
37
+ rescue Connection::NotFound
38
+ raise NotFound, "client with id '#{client_id}' not found"
39
+ end
40
+
41
+ def get_details_by_external_id(external_id:, connection:)
42
+ params = {
43
+ "externalId" => external_id
44
+ }
45
+
46
+ response = connection.get(
47
+ "/v1/clients?#{params.compact.to_query}")
48
+
49
+ payload = response.body
50
+
51
+ DomainModel::Client.new(
52
+ payload.deep_transform_keys(&:underscore))
53
+ rescue Connection::NotFound
54
+ raise NotFound, "client with external id '#{external_id}' not found"
55
+ end
56
+
57
+ def create(attributes:, connection:)
58
+ body = Schema::Client::Create[attributes]
59
+ .transform_keys { |k| k.to_s.camelize(:lower) }
60
+ .to_json
61
+
62
+ response = connection.post(
63
+ "/v1/clients", body)
64
+
65
+ payload = response.body
66
+
67
+ # TODO: find a better way to deal with api weirdness here
68
+ payload["customFieldValues"].compact!
69
+
70
+ DomainModel::Client.new(
71
+ payload.deep_transform_keys(&:underscore))
72
+ end
73
+
74
+ # NOTE: according to the api documentation, this is _not_ a PATCH operation,
75
+ # i.e. any fields not supplied will be set to null - so make sure you
76
+ # supply everything :)
77
+ def update(client_id:, attributes:, connection:)
78
+ body = attributes
79
+ .deep_transform_keys { |k| k.to_s.camelize(:lower) }
80
+ .to_json
81
+
82
+ response = connection.put(
83
+ "/v1/clients/#{client_id}", body)
84
+
85
+ payload = response.body
86
+
87
+ # TODO: find a better way to deal with api weirdness here
88
+ payload["customFieldValues"].compact!
89
+
90
+ DomainModel::Client.new(
91
+ payload.deep_transform_keys(&:underscore))
92
+ end
93
+
94
+ def update_custom_field(client_id:, custom_field_name:, value:, connection:)
95
+ body = {
96
+ custom_field_name => value.to_s
97
+ }
98
+
99
+ begin
100
+ connection.patch(
101
+ "/v1/customfield/value/client/#{client_id}", body)
102
+ rescue Connection::ParsingError => error
103
+ # NOTE: request submitted to resolve invalid JSON response from client
104
+ # success for this endpoint
105
+ raise error unless error.message =~ /Update successful/
106
+ end
107
+ end
108
+
109
+ def delete(client_id:, connection:)
110
+ # TODO: handle response
111
+ connection.delete(
112
+ "/v1/clients/#{client_id}")
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "connection"
2
+ require_relative "domain_model/client_type"
3
+
4
+ module ClientSuccess
5
+ module ClientType
6
+ extend self
7
+
8
+ def list_all(connection:)
9
+ response = connection.get(
10
+ "/v1/client-segments")
11
+
12
+ response.body.map do |payload|
13
+ DomainModel::ClientType.new(
14
+ payload.deep_transform_keys(&:underscore))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,99 @@
1
+ require "faraday"
2
+ require "faraday_middleware"
3
+
4
+ module ClientSuccess
5
+ class Connection
6
+ class Error < StandardError; end
7
+
8
+ class BadRequest < Error; end
9
+ class Unauthorised < Error; end
10
+ class NotFound < Error; end
11
+
12
+ class ParsingError < Error; end
13
+
14
+ class << self
15
+ def authorised(access_token)
16
+ new.tap { |conn| conn.access_token = access_token }
17
+ end
18
+ end
19
+
20
+ def logger
21
+ # TODO: something better than this
22
+ Logger.new(STDOUT).tap do |l|
23
+ l.level = Logger::WARN
24
+ end
25
+ end
26
+
27
+ def access_token
28
+ headers["Authorization"]
29
+ end
30
+
31
+ def access_token=(access_token)
32
+ headers["Authorization"] = access_token
33
+ end
34
+
35
+ def get(path, headers = {}, &block)
36
+ request { adapter.get(path, headers, &block) }
37
+ end
38
+
39
+ def head(path, headers = {}, &block)
40
+ raise NotImplementedError
41
+ end
42
+
43
+ def post(path, body = nil, headers = {}, &block)
44
+ request { adapter.post(path, body, headers, &block) }
45
+ end
46
+
47
+ def put(path, body = nil, headers = {}, &block)
48
+ request { adapter.put(path, body, headers, &block) }
49
+ end
50
+
51
+ def patch(path, body = nil, headers = {}, &block)
52
+ request { adapter.patch(path, body, headers, &block) }
53
+ end
54
+
55
+ def delete(path, headers = {}, &block)
56
+ request { adapter.delete(path, headers, &block) }
57
+ end
58
+
59
+ def options(path, headers = {}, &block)
60
+ raise NotImplementedError
61
+ end
62
+
63
+ private
64
+
65
+ def headers
66
+ adapter.headers
67
+ end
68
+
69
+ def adapter
70
+ @adapter ||= Faraday.new(url: "https://api.clientsuccess.com") do |faraday|
71
+ faraday.request(:json)
72
+ faraday.response(:json, content_type: /\bjson$/)
73
+ faraday.response(:logger, logger)
74
+ faraday.use(Faraday::Response::RaiseError)
75
+ faraday.adapter(Faraday.default_adapter)
76
+ end
77
+ end
78
+
79
+ def request
80
+ yield
81
+ rescue Faraday::ParsingError => error
82
+ raise ParsingError, error
83
+ rescue Faraday::ClientError => error
84
+ case error.response[:status]
85
+ when 400
86
+ # TODO: parse out userMessage from the response here
87
+ raise BadRequest, error
88
+ when 401
89
+ raise Unauthorised, error
90
+ when 404
91
+ raise NotFound, error
92
+ else
93
+ raise Error, error
94
+ end
95
+ rescue SignalException => error
96
+ raise Error, error
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,104 @@
1
+ require_relative "domain_model/contact"
2
+ require_relative "schema/contact/create"
3
+ require_relative "schema/contact/update"
4
+
5
+ module ClientSuccess
6
+ module Contact
7
+ extend self
8
+
9
+ class Error < StandardError; end
10
+ class NotFound < Error; end
11
+
12
+ def list_all(client_id:, connection:)
13
+ response = connection.get(
14
+ "/v1/clients/#{client_id}/contacts")
15
+
16
+ response.body.map do |payload|
17
+ DomainModel::Contact.new(
18
+ payload.deep_transform_keys(&:underscore))
19
+ end
20
+ end
21
+
22
+ def get_summary(client_id:, contact_id:, connection:)
23
+ response = connection.get(
24
+ "/v1/clients/#{client_id}/contacts/#{contact_id}")
25
+
26
+ payload = response.body
27
+
28
+ DomainModel::Contact.new(
29
+ payload.deep_transform_keys(&:underscore))
30
+ end
31
+
32
+ def get_details(client_id:, contact_id:, connection:)
33
+ response = connection.get(
34
+ "/v1/clients/#{client_id}/contacts/#{contact_id}/details")
35
+
36
+ payload = response.body
37
+
38
+ DomainModel::Contact.new(
39
+ payload.deep_transform_keys(&:underscore))
40
+ end
41
+
42
+ def create(client_id:, attributes:, connection:)
43
+ body = Schema::Contact::Create[attributes]
44
+ .transform_keys { |k| k.to_s.camelize(:lower) }
45
+ .to_json
46
+
47
+ response = connection.post(
48
+ "/v1/clients/#{client_id}/contacts", body)
49
+
50
+ payload = response.body
51
+
52
+ DomainModel::Contact.new(
53
+ payload.deep_transform_keys(&:underscore))
54
+ end
55
+
56
+ def update(id:, client_id:, attributes:, connection:)
57
+ body = Schema::Contact::Update[attributes]
58
+ .transform_keys { |k| k.to_s.camelize(:lower) }
59
+ .to_json
60
+
61
+ response = connection.put("/v1/clients/#{client_id}/contacts/#{id}/details", body)
62
+
63
+ payload = response.body
64
+
65
+ DomainModel::Contact.new(
66
+ payload.deep_transform_keys(&:underscore))
67
+ end
68
+
69
+ def delete(id:, client_id:, connection:)
70
+ connection.delete("/v1/clients/#{client_id}/contacts/#{id}")
71
+ end
72
+
73
+ def search_by_email(email:, connection:)
74
+ search(term: email, connection: connection)
75
+ .reject { |contact| contact["email"].nil? }
76
+ .select { |contact| contact["email"].downcase == email.downcase }
77
+ end
78
+
79
+ def get_details_by_client_external_id_and_email(client_external_id:, email:, connection:)
80
+ params = {
81
+ "clientExternalId" => client_external_id,
82
+ "email" => email
83
+ }
84
+
85
+ response = connection.get(
86
+ "/v1/contacts?#{params.compact.to_query}")
87
+
88
+ # for some reason the ClientSuccess API does not return a 404
89
+ # but instead a body containing the string "null" if the contact is not found
90
+ # this is probably a security restriction on their end, but we will instead raise an error
91
+
92
+ if response.body.blank?
93
+ raise NotFound, "contact with email '#{email}' not found on client '#{client_external_id}'"
94
+ else
95
+ payload = response.body
96
+ DomainModel::Contact.new(payload.deep_transform_keys(&:underscore))
97
+ end
98
+ end
99
+
100
+ def search(term:, connection:)
101
+ connection.get("/v1/contacts/search?term='#{term}'").body
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,11 @@
1
+ module ClientSuccess
2
+ module DomainModel
3
+ class AccessToken < Hashie::Dash
4
+ include Hashie::Extensions::IndifferentAccess
5
+
6
+ property :access_token
7
+ property :token_type
8
+ property :expires_in
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require_relative "custom_field_value"
2
+
3
+ module ClientSuccess
4
+ module DomainModel
5
+ class Client < Hash
6
+ include Hashie::Extensions::MergeInitializer
7
+ include Hashie::Extensions::IndifferentAccess
8
+ include Hashie::Extensions::MethodAccess
9
+ include Hashie::Extensions::Coercion
10
+
11
+ coerce_key :custom_field_values, Array[CustomFieldValue]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require_relative "success_cycle"
2
+
3
+ module ClientSuccess
4
+ module DomainModel
5
+ class ClientType < Hash
6
+ include Hashie::Extensions::MergeInitializer
7
+ include Hashie::Extensions::IndifferentAccess
8
+ include Hashie::Extensions::MethodAccess
9
+ include Hashie::Extensions::Coercion
10
+
11
+ coerce_key :success_cycles, Array[SuccessCycle]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # require_relative "custom_field_value"
2
+
3
+ module ClientSuccess
4
+ module DomainModel
5
+ class Contact < Hash
6
+ include Hashie::Extensions::MergeInitializer
7
+ include Hashie::Extensions::IndifferentAccess
8
+ include Hashie::Extensions::MethodAccess
9
+ # include Hashie::Extensions::Coercion
10
+
11
+ # coerce_key :custom_field_values, Array[CustomFieldValue]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ module ClientSuccess
2
+ module DomainModel
3
+ class CustomFieldValue < Hash
4
+ include Hashie::Extensions::MergeInitializer
5
+ include Hashie::Extensions::IndifferentAccess
6
+ include Hashie::Extensions::MethodAccess
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ require_relative "security_role"
2
+
3
+ module ClientSuccess
4
+ module DomainModel
5
+ class Employee < Hash
6
+ include Hashie::Extensions::MergeInitializer
7
+ include Hashie::Extensions::IndifferentAccess
8
+ include Hashie::Extensions::MethodAccess
9
+ include Hashie::Extensions::Coercion
10
+
11
+ coerce_key :security_roles, Array[SecurityRole]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module ClientSuccess
2
+ module DomainModel
3
+ class Product < Hash
4
+ include Hashie::Extensions::MergeInitializer
5
+ include Hashie::Extensions::IndifferentAccess
6
+ include Hashie::Extensions::MethodAccess
7
+ include Hashie::Extensions::Coercion
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module ClientSuccess
2
+ module DomainModel
3
+ class SecurityRole < Hash
4
+ include Hashie::Extensions::MergeInitializer
5
+ include Hashie::Extensions::IndifferentAccess
6
+ include Hashie::Extensions::MethodAccess
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ClientSuccess
2
+ module DomainModel
3
+ class Status < Hash
4
+ include Hashie::Extensions::MergeInitializer
5
+ include Hashie::Extensions::IndifferentAccess
6
+ include Hashie::Extensions::MethodAccess
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ require_relative "custom_field_value"
2
+
3
+ module ClientSuccess
4
+ module DomainModel
5
+ class Subscription < Hash
6
+ include Hashie::Extensions::MergeInitializer
7
+ include Hashie::Extensions::IndifferentAccess
8
+ include Hashie::Extensions::MethodAccess
9
+ include Hashie::Extensions::Coercion
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module ClientSuccess
2
+ module DomainModel
3
+ class SuccessCycle < Hash
4
+ include Hashie::Extensions::MergeInitializer
5
+ include Hashie::Extensions::IndifferentAccess
6
+ include Hashie::Extensions::MethodAccess
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ClientSuccess
2
+ module DomainModel
3
+ class ToDo < Hash
4
+ include Hashie::Extensions::MergeInitializer
5
+ include Hashie::Extensions::IndifferentAccess
6
+ include Hashie::Extensions::MethodAccess
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "connection"
2
+ require_relative "domain_model/employee"
3
+
4
+ module ClientSuccess
5
+ module Employee
6
+ extend self
7
+
8
+ def list_all(connection:)
9
+ response = connection.get(
10
+ "/v1/employees")
11
+
12
+ response.body.map do |payload|
13
+ DomainModel::Employee.new(
14
+ payload.deep_transform_keys(&:underscore))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "domain_model/product"
2
+
3
+ module ClientSuccess
4
+ module Product
5
+ extend self
6
+
7
+ def list_all(connection:)
8
+ response = connection.get("/v1/products")
9
+
10
+ response.body.map do |payload|
11
+ DomainModel::Product.new(
12
+ payload.deep_transform_keys(&:underscore))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ require_relative "../../types"
2
+
3
+ module ClientSuccess
4
+ module Schema
5
+ module Client
6
+ # TODO: upgrade dry types to use latest schema functions
7
+ Create = Types::Hash.schema(
8
+ name: Types::Strict::String,
9
+ site_url: Types::Strict::String,
10
+ zip: Types::Strict::String,
11
+ status_id: Types::Coercible::Int,
12
+ inception_date: Types::Strict::String, # TODO
13
+ created_by_employee_id: Types::Coercible::Int,
14
+ linkedin_url: Types::Strict::String,
15
+ managed_by_employee_id: Types::Coercible::Int,
16
+ active: Types::Strict::Bool,
17
+ active_client_success_cycle_id: Types::Coercible::Int,
18
+ zendesk_id: Types::Coercible::String,
19
+ desk_id: Types::Coercible::String,
20
+ freshdesk_id: Types::Coercible::String,
21
+ jira_id: Types::Coercible::String,
22
+ terminated_date: Types::Strict::String, # TODO
23
+ assigned_sales_rep: Types::Coercible::Int,
24
+ salesforce_account_id: Types::Coercible::String,
25
+ usage_id: Types::Coercible::String,
26
+ street: Types::Strict::String,
27
+ state: Types::Strict::String,
28
+ country: Types::Strict::String,
29
+ city: Types::Strict::String,
30
+ external_id: Types::Coercible::String
31
+ )
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "../../types"
2
+
3
+ module ClientSuccess
4
+ module Schema
5
+ module Client
6
+ # TODO: upgrade dry types to use latest schema functions
7
+ Update = Types::Hash.schema(
8
+ external_id: Types::Coercible::String,
9
+ name: Types::Strict::String,
10
+ site_url: Types::Strict::String,
11
+ client_segment_id: Types::Coercible::Int,
12
+ zip: Types::Strict::String,
13
+ modified_by_employee_id: Types::Coercible::Int,
14
+ status_id: Types::Coercible::Int,
15
+ inception_date: Types::Strict::String, # TODO
16
+ created_by_employee: Types::Coercible::Int,
17
+ linkedin_url: Types::Strict::String,
18
+ managed_by_employee_id: Types::Coercible::Int,
19
+ active: Types::Strict::Bool,
20
+ success_score: Types::Strict::Int,
21
+ active_client_success_cycle_id: Types::Coercible::Int,
22
+ crm_customer_id: Types::Coercible::String,
23
+ crm_customer_url: Types::Strict::String,
24
+ zendesk_id: Types::Coercible::String,
25
+ desk_id: Types::Coercible::String,
26
+ freshdesk_id: Types::Coercible::String,
27
+ uservoice_id: Types::Coercible::String,
28
+ assigned_sales_rep: Types::Strict::String,
29
+ custom_field_values: Types::Strict::Array.default([]), # TODO
30
+ success_cycle_id: Types::Coercible::Int,
31
+ salesforce_account_id: Types::Coercible::String,
32
+ jira_id: Types::Coercible::String,
33
+ nps_score: Types::Strict::Int
34
+ )
35
+ end
36
+ end
37
+ end