diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 14892656f..dd51077c6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -156,6 +156,17 @@ job_integration_slack:
- ruby -I test test/integration/slack_test.rb
- rake db:drop
+job_integration_clearbit:
+ stage: test
+ tags:
+ - core
+ script:
+ - export RAILS_ENV=test
+ - rake db:create
+ - rake db:migrate
+ - ruby -I test test/integration/clearbit_test.rb
+ - rake db:drop
+
job_integration_es_mysql:
stage: test
tags:
diff --git a/Gemfile b/Gemfile
index a188559e6..2f180d344 100644
--- a/Gemfile
+++ b/Gemfile
@@ -32,8 +32,6 @@ gem 'omniauth-facebook'
gem 'omniauth-linkedin'
gem 'omniauth-google-oauth2'
-gem 'zendesk_api'
-
gem 'twitter'
gem 'koala'
gem 'mail'
@@ -90,6 +88,8 @@ gem 'browser'
# integrations
gem 'slack-notifier'
+gem 'clearbit'
+gem 'zendesk_api'
# event machine
gem 'eventmachine'
diff --git a/Gemfile.lock b/Gemfile.lock
index 7141ed216..d4644fc4c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -54,6 +54,8 @@ GEM
childprocess (0.5.9)
ffi (~> 1.0, >= 1.0.11)
clavius (1.0.2)
+ clearbit (0.2.3)
+ nestful (~> 1.1.0)
coderay (1.1.1)
coffee-rails (4.1.1)
coffee-script (>= 2.2.0)
@@ -134,7 +136,7 @@ GEM
faraday
multi_json
libv8 (3.16.14.13)
- listen (3.0.6)
+ listen (3.1.1)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9.7)
loofah (2.0.3)
@@ -154,6 +156,7 @@ GEM
mysql2 (0.3.20)
naught (1.1.0)
nenv (0.3.0)
+ nestful (1.1.1)
net-ldap (0.14.0)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
@@ -323,6 +326,7 @@ DEPENDENCIES
autoprefixer-rails (>= 5.2)
biz
browser
+ clearbit
coffee-rails
coffee-script-source
coffeelint
diff --git a/app/assets/javascripts/app/controllers/_integration/clearbit.coffee b/app/assets/javascripts/app/controllers/_integration/clearbit.coffee
new file mode 100644
index 000000000..54a9dc9b2
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/_integration/clearbit.coffee
@@ -0,0 +1,165 @@
+class Index extends App.ControllerIntegrationBase
+ featureIntegration: 'clearbit_integration'
+ featureName: 'Clearbit'
+ featureConfig: 'clearbit_config'
+ description: [
+ ['Automatically enrich your customers and organizations with fresh, up-to-date intel. Map data directly to object fields.
+']
+ ]
+
+ render: =>
+ super
+ new Form(
+ el: @$('.js-form')
+ )
+
+ new App.HttpLog(
+ el: @$('.js-log')
+ facility: 'clearbit'
+ )
+
+class Form extends App.Controller
+ events:
+ 'submit form': 'update'
+ 'click .js-userSync .js-add': 'addUserSync'
+ 'click .js-organizationSync .js-add': 'addOrganizationSync'
+ 'click .js-userSync .js-remove': 'removeRow'
+ 'click .js-organizationSync .js-remove': 'removeRow'
+
+ constructor: ->
+ super
+
+ # check authentication
+ return if !@authenticate()
+
+ @subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
+
+ currentConfig: ->
+ config = App.Setting.get('clearbit_config')
+ if !config
+ config = {}
+ if config.organization_autocreate is undefined
+ config.organization_autocreate = true
+ if config.organization_shared is undefined
+ config.organization_shared = false
+ if !config.user_sync
+ config.user_sync =
+ 'person.name.givenName': 'user.firstname'
+ 'person.name.familyName': 'user.lastname'
+ 'person.email': 'user.email'
+ 'person.bio': 'user.note'
+ 'company.url': 'user.web'
+ 'person.site': 'user.web'
+ 'company.location': 'user.address'
+ 'person.location': 'user.address'
+ if !config.organization_sync
+ config.organization_sync =
+ 'company.legalName': 'organization.name'
+ 'company.name': 'organization.name'
+ 'company.description': 'organization.note'
+ config
+
+ setConfig: (value) ->
+ App.Setting.set('clearbit_config', value)
+
+ render: =>
+ @config = @currentConfig()
+
+ settings = [
+ { name: 'api_key', display: 'API Key', tag: 'input', type: 'text', limit: 100, null: false, placeholder: '...', note: 'Your api key.' },
+ { name: 'organization_autocreate', display: 'Auto create', tag: 'boolean', type: 'boolean', null: false, note: 'Create organizations automatically if record has one.' },
+ { name: 'organization_shared', display: 'Shared', tag: 'boolean', type: 'boolean', null: false, note: 'New organizations are shared.' },
+ ]
+
+ @html App.view('integration/clearbit')(
+ config: @config
+ settings: settings
+ )
+
+ for setting in settings
+ setting.display = ''
+ new App.ControllerForm(
+ el: @$("[data-name=#{setting.name}]")
+ model: { configure_attributes: [setting] }
+ params: @config
+ )
+
+ updateCurrentConfig: =>
+ config = @config
+ cleanupInput = @cleanupInput
+
+ params = @formParam(@$('form'))
+ config.api_key = params['api_key']
+ config.organization_autocreate = params['organization_autocreate']
+ config.organization_shared = params['organization_shared']
+
+ # user sync
+ config.user_sync = {}
+ @$('.js-userSync .js-row').each(->
+ element = $(@)
+ source = cleanupInput(element.find('input[name="source"]').val())
+ destination = cleanupInput(element.find('input[name="destination"]').val())
+ config.user_sync[source] = destination
+ )
+
+ # organization sync
+ config.organization_sync = {}
+ @$('.js-organizationSync .js-row').each(->
+ element = $(@)
+ source = cleanupInput(element.find('input[name="source"]').val())
+ destination = cleanupInput(element.find('input[name="destination"]').val())
+ config.organization_sync[source] = destination
+ )
+
+ @config = config
+
+ update: (e) =>
+ e.preventDefault()
+ @updateCurrentConfig()
+ @setConfig(@config)
+
+ cleanupInput: (value) ->
+ return value if !value
+ value.replace(/\s/g, '').trim()
+
+ addUserSync: (e) =>
+ e.preventDefault()
+ @updateCurrentConfig()
+ element = $(e.currentTarget).closest('tr')
+ source = @cleanupInput(element.find('input[name="source"]').val())
+ destination = @cleanupInput(element.find('input[name="destination"]').val())
+ @config.user_sync[source] = destination
+ @setConfig(@config)
+ @render()
+
+ addOrganizationSync: (e) =>
+ e.preventDefault()
+ @updateCurrentConfig()
+ element = $(e.currentTarget).closest('tr')
+ source = @cleanupInput(element.find('input[name="source"]').val())
+ destination = @cleanupInput(element.find('input[name="destination"]').val())
+ @config.organization_sync[source] = destination
+ @setConfig(@config)
+ @render()
+
+ removeRow: (e) =>
+ e.preventDefault()
+ @updateCurrentConfig()
+ element = $(e.currentTarget).closest('tr')
+ element.remove()
+
+class State
+ @current: ->
+ App.Setting.get('clearbit_integration')
+
+App.Config.set(
+ 'IntegrationClearbit'
+ {
+ name: 'Clearbit'
+ target: '#system/integration/clearbit'
+ description: 'A powerfull service to get more information about your customers.'
+ controller: Index
+ state: State
+ }
+ 'NavBarIntegrations'
+)
diff --git a/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee b/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee
index 7baecf237..d7d0e6a1a 100644
--- a/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee
+++ b/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee
@@ -148,7 +148,7 @@ App.Config.set(
{
name: 'sipgate.io'
target: '#system/integration/sipgate'
- description: 'VoIP services provide.'
+ description: 'VoIP service provider with realtime push.'
controller: Index
state: State
}
diff --git a/app/assets/javascripts/app/controllers/_settings/area.coffee b/app/assets/javascripts/app/controllers/_settings/area.coffee
index f6bdc1674..cffbb750c 100644
--- a/app/assets/javascripts/app/controllers/_settings/area.coffee
+++ b/app/assets/javascripts/app/controllers/_settings/area.coffee
@@ -1,93 +1,3 @@
-class App.SettingsForm extends App.Controller
- events:
- 'submit form': 'update'
-
- constructor: ->
- super
-
- # check authentication
- return if !@authenticate()
-
- @subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
-
- render: =>
-
- # serach area settings
- settings = App.Setting.search(
- filter:
- area: @area
- )
-
- # filter online service settings
- if App.Config.get('system_online_service')
- settings = _.filter(settings, (setting) ->
- return if setting.online_service
- return if setting.preferences && setting.preferences.online_service_disable
- setting
- )
- return if _.isEmpty(settings)
-
- # sort by prio
- settings = _.sortBy( settings, (setting) ->
- return if !setting.preferences
- setting.preferences.prio
- )
-
- localEl = $( App.view('settings/form')(
- settings: settings
- ))
-
- for setting in settings
- configure_attributes = setting.options['form']
- value = App.Setting.get(setting.name)
- params = {}
- params[setting.name] = value
- new App.ControllerForm(
- el: localEl.find("[data-name=#{setting.name}]")
- model: { configure_attributes: configure_attributes, className: '' }
- params: params
- )
- @html localEl
-
- update: (e) =>
- e.preventDefault()
- @formDisable(e)
- params = @formParam(e.target)
-
- ui = @
- count = 0
- for name, value of params
- if App.Setting.findByAttribute('name', name)
- count += 1
- App.Setting.set(
- name,
- value,
- done: ->
- ui.formEnable(e)
- count -= 1
- if count == 0
- App.Event.trigger 'notify', {
- type: 'success'
- msg: App.i18n.translateContent('Update successful!')
- timeout: 2000
- }
-
- # rerender ui || get new collections and session data
- if @preferences
- if @preferences.render
- App.Event.trigger( 'ui:rerender' )
-
- if @preferences.session_check
- App.Auth.loginCheck()
-
- fail: (settings, details) ->
- App.Event.trigger 'notify', {
- type: 'error'
- msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!')
- timeout: 2000
- }
- )
-
class App.SettingsArea extends App.Controller
constructor: ->
super
@@ -123,9 +33,9 @@ class App.SettingsArea extends App.Controller
elements = []
for setting in settings
if setting.name is 'product_logo'
- item = new App.SettingsAreaLogo( setting: setting )
+ item = new App.SettingsAreaLogo(setting: setting)
else
- item = new App.SettingsAreaItem( setting: setting )
+ item = new App.SettingsAreaItem(setting: setting)
elements.push item.el
@html elements
diff --git a/app/assets/javascripts/app/controllers/_settings/form.coffee b/app/assets/javascripts/app/controllers/_settings/form.coffee
new file mode 100644
index 000000000..f3ed764ed
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/_settings/form.coffee
@@ -0,0 +1,89 @@
+class App.SettingsForm extends App.Controller
+ events:
+ 'submit form': 'update'
+
+ constructor: ->
+ super
+
+ # check authentication
+ return if !@authenticate()
+
+ @subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
+
+ render: =>
+
+ # serach area settings
+ settings = App.Setting.search(
+ filter:
+ area: @area
+ )
+
+ # filter online service settings
+ if App.Config.get('system_online_service')
+ settings = _.filter(settings, (setting) ->
+ return if setting.online_service
+ return if setting.preferences && setting.preferences.online_service_disable
+ setting
+ )
+ return if _.isEmpty(settings)
+
+ # sort by prio
+ settings = _.sortBy( settings, (setting) ->
+ return if !setting.preferences
+ setting.preferences.prio
+ )
+
+ localEl = $( App.view('settings/form')(
+ settings: settings
+ ))
+
+ for setting in settings
+ configure_attributes = setting.options['form']
+ value = App.Setting.get(setting.name)
+ params = {}
+ params[setting.name] = value
+ new App.ControllerForm(
+ el: localEl.find("[data-name=#{setting.name}]")
+ model: { configure_attributes: configure_attributes }
+ params: params
+ )
+ @html localEl
+
+ update: (e) =>
+ e.preventDefault()
+ @formDisable(e)
+ params = @formParam(e.target)
+
+ ui = @
+ count = 0
+ for name, value of params
+ if App.Setting.findByAttribute('name', name)
+ count += 1
+ App.Setting.set(
+ name,
+ value,
+ done: ->
+ ui.formEnable(e)
+ count -= 1
+ if count == 0
+ App.Event.trigger 'notify', {
+ type: 'success'
+ msg: App.i18n.translateContent('Update successful!')
+ timeout: 2000
+ }
+
+ # rerender ui || get new collections and session data
+ if @preferences
+ if @preferences.render
+ App.Event.trigger( 'ui:rerender' )
+
+ if @preferences.session_check
+ App.Auth.loginCheck()
+
+ fail: (settings, details) ->
+ App.Event.trigger 'notify', {
+ type: 'error'
+ msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!')
+ timeout: 2000
+ }
+ )
diff --git a/app/assets/javascripts/app/index.coffee b/app/assets/javascripts/app/index.coffee
index 6db4444ab..e2c123e21 100644
--- a/app/assets/javascripts/app/index.coffee
+++ b/app/assets/javascripts/app/index.coffee
@@ -220,6 +220,7 @@ class App extends Spine.Controller
# define richtext helper
params.RichText = (string) ->
+ return string if !string
if string.match(/@T\('/)
string = string.replace(/@T\('(.+?)'\)/g, (match, capture) ->
App.i18n.translateContent(capture)
diff --git a/app/assets/javascripts/app/views/integration/clearbit.jst.eco b/app/assets/javascripts/app/views/integration/clearbit.jst.eco
new file mode 100644
index 000000000..2ffb40f3d
--- /dev/null
+++ b/app/assets/javascripts/app/views/integration/clearbit.jst.eco
@@ -0,0 +1,75 @@
+
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/integration/index.jst.eco b/app/assets/javascripts/app/views/integration/index.jst.eco
index 8b5c9dbff..9932de977 100644
--- a/app/assets/javascripts/app/views/integration/index.jst.eco
+++ b/app/assets/javascripts/app/views/integration/index.jst.eco
@@ -6,7 +6,7 @@
|
- <%- @T('Service') %> |
+ <%- @T('Service') %> |
<%- @T('Description') %> |
diff --git a/app/models/external_sync.rb b/app/models/external_sync.rb
new file mode 100644
index 000000000..aa7fe5481
--- /dev/null
+++ b/app/models/external_sync.rb
@@ -0,0 +1,5 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class ExternalSync < ApplicationModel
+ store :last_payload
+end
diff --git a/app/models/transaction/background_job.rb b/app/models/transaction/background_job.rb
index 263a269a3..1668a9012 100644
--- a/app/models/transaction/background_job.rb
+++ b/app/models/transaction/background_job.rb
@@ -23,8 +23,13 @@ class Transaction::BackgroundJob
def perform
Setting.where(area: 'Transaction::Backend').order(:name).each {|setting|
backend = Setting.get(setting.name)
- integration = Kernel.const_get(backend).new(@item, @params)
- integration.perform
+ begin
+ integration = Kernel.const_get(backend).new(@item, @params)
+ integration.perform
+ rescue => e
+ logger.error 'ERROR: ' + setting.inspect
+ logger.error 'ERROR: ' + e.inspect
+ end
}
end
diff --git a/app/models/transaction/clearbit_enrichment.rb b/app/models/transaction/clearbit_enrichment.rb
new file mode 100644
index 000000000..ef1371c31
--- /dev/null
+++ b/app/models/transaction/clearbit_enrichment.rb
@@ -0,0 +1,321 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class Transaction::ClearbitEnrichment
+
+=begin
+ {
+ object: 'User',
+ type: 'create',
+ object_id: 123,
+ changes: {
+ 'attribute1' => [before, now],
+ 'attribute2' => [before, now],
+ }
+ },
+=end
+
+ def initialize(item, params = {})
+ @item = item
+ @params = params
+ end
+
+ def perform
+
+ # return if we run import mode
+ return if Setting.get('import_mode')
+
+ return if @item[:object] != 'User'
+ return if @item[:type] != 'create'
+
+ return if !Setting.get('clearbit_integration')
+
+ config = Setting.get('clearbit_config')
+ return if !config
+ return if config['api_key'].empty?
+
+ user = User.lookup(id: @item[:object_id])
+ return if !user
+
+ Transaction::ClearbitEnrichment.sync_user(user)
+ end
+
+ def self.sync
+ users = User.of_role('Customer')
+ users.each {|user|
+ sync_user(user)
+ }
+ end
+
+ def self.sync_user(user)
+
+ return if !user.email
+ data = fetch(user.email)
+ #p 'OO: ' + data.inspect
+
+ config = Setting.get('clearbit_config')
+ return if !config
+
+ # get new user sync attributes
+ user_sync = config['user_sync']
+ user_sync_values = {}
+ if user_sync
+ user_sync.each {|callback, destination|
+ next if !user_sync_values[destination].empty?
+ value = _replace(callback, data)
+ next if !value
+ user_sync_values[destination] = value
+ }
+ end
+
+ # get new organization sync attributes
+ organization_sync = config['organization_sync']
+ organization_sync_values = {}
+ if organization_sync
+ organization_sync.each {|callback, destination|
+ next if !organization_sync_values[destination].empty?
+ value = _replace(callback, data)
+ next if !value
+ organization_sync_values[destination] = value
+ }
+ end
+
+ # get latest user synced attributes
+ external_syn_user = nil
+ user_sync_values_last_time = {}
+ if data && data['person'] && data['person']['id']
+ external_syn_user = ExternalSync.find_by(
+ source: 'clearbit',
+ source_id: data['person']['id'],
+ object: 'User',
+ o_id: user.id,
+ )
+ if external_syn_user && external_syn_user.last_payload
+ user_sync.each {|callback, destination|
+ next if !user_sync_values_last_time[destination].empty?
+ value = _replace(callback, external_syn_user.last_payload)
+ next if !value
+ user_sync_values_last_time[destination] = value
+ }
+ end
+ end
+
+ # if person record exists
+ user_has_changed = false
+ user_sync_values.each {|destination, value|
+ attribute = destination.sub(/^user\./, '')
+ next if user[attribute] == value
+ next if !user[attribute].empty? && user_sync_values_last_time[destination] != user[attribute]
+ begin
+ user[attribute] = value
+ rescue => e
+ Rails.logger.error "ERROR: Unable to assign user.#{attribute}: #{e.inspect}"
+ end
+ user_has_changed = true
+ }
+ if user_has_changed
+ user.updated_by_id = 1
+ if data['person'] && data['person']['id']
+ if external_syn_user
+ external_syn_user.last_payload = data
+ external_syn_user.save
+ else
+ external_syn_user = ExternalSync.create(
+ source: 'clearbit',
+ source_id: data['person']['id'],
+ object: 'User',
+ o_id: user.id,
+ last_payload: data,
+ )
+ end
+ end
+ end
+
+ # if no company record exists
+ if !data['company']
+ if user_has_changed
+ user.save
+ end
+ Observer::Transaction.commit
+ return
+ end
+
+ # if company record exists
+ external_syn_organization = ExternalSync.find_by(
+ source: 'clearbit',
+ source_id: data['company']['id'],
+ )
+
+ # create new organization
+ if !external_syn_organization
+
+ # can't create organization without name
+ if organization_sync_values['organization.name'].empty?
+ Observer::Transaction.commit
+ return
+ end
+
+ # find by name
+ organization = Organization.find_by(name: organization_sync_values['organization.name'])
+
+ # create new organization
+ if !organization
+ organization = Organization.new(
+ shared: config['organization_shared'],
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ organization_sync_values.each {|destination, value|
+ attribute = destination.sub(/^organization\./, '')
+ next if !organization[attribute].empty?
+ begin
+ organization[attribute] = value
+ rescue => e
+ Rails.logger.error "ERROR: Unable to assign organization.#{attribute}: #{e.inspect}"
+ end
+ }
+ organization.save
+ end
+ ExternalSync.create(
+ source: 'clearbit',
+ source_id: data['company']['id'],
+ object: 'Organization',
+ o_id: organization.id,
+ last_payload: data,
+ )
+
+ # assign new organization to user
+ if !user.organization_id
+ user.organization_id = organization.id
+ user.save
+ end
+ Observer::Transaction.commit
+ return
+ end
+
+ # get latest organization synced attributes
+ organization_sync_values_last_time = {}
+ if external_syn_organization && external_syn_organization.last_payload
+ organization_sync.each {|callback, destination|
+ next if !organization_sync_values_last_time[destination].empty?
+ value = _replace(callback, external_syn_organization.last_payload)
+ next if !value
+ organization_sync_values_last_time[destination] = value
+ }
+ end
+
+ # update existing organization
+ organization = Organization.find(external_syn_organization[:o_id])
+ organization_has_changed = false
+ organization_sync_values.each {|destination, value|
+ attribute = destination.sub(/^organization\./, '')
+ next if organization[attribute] == value
+ next if !organization[attribute].empty? && organization_sync_values_last_time[destination] != organization[attribute]
+ begin
+ organization[attribute] = value
+ rescue => e
+ Rails.logger.error "ERROR: Unable to assign organization.#{attribute}: #{e.inspect}"
+ end
+ organization_has_changed = true
+ }
+ if organization_has_changed
+ organization.updated_by_id = 1
+ organization.save
+ external_syn_organization.last_payload = data
+ external_syn_organization.save
+ end
+
+ # assign new organization to user
+ if !user.organization_id
+ user_has_changed = true
+ user.organization_id = organization.id
+ end
+
+ if user_has_changed
+ user.save
+ end
+
+ Observer::Transaction.commit
+ true
+ end
+
+ def self._replace(callback, data)
+ object_name = nil
+ object_method = nil
+ placeholder = nil
+
+ if callback =~ /\A ( [\w]+ )\.( [\w\.]+ ) \z/x
+ object_name = $1
+ object_method = $2
+ end
+
+ return if !data
+ return if !data[object_name]
+
+ # do validaton, ignore some methodes
+ if callback =~ /(`|\.(|\s*)(save|destroy|delete|remove|drop|update\(|update_att|create\(|new|all|where|find))/i
+ placeholder = "#{callback} (not allowed)"
+
+ # get value based on object_name and object_method
+ elsif object_name && object_method
+ object_refs = data[object_name]
+ object_methods = object_method.split('.')
+ object_methods_s = ''
+ object_methods.each {|method|
+ if object_methods_s != ''
+ object_methods_s += '.'
+ end
+ object_methods_s += method
+
+ # if method exists
+ break if !object_refs.respond_to?(method.to_sym) && !object_refs[method]
+
+ object_refs = if object_refs.respond_to?(method.to_sym)
+ object_refs.send(method.to_sym)
+ else
+ object_refs[method]
+ end
+ }
+ if object_refs.class == String
+ placeholder = object_refs
+ end
+ end
+ placeholder
+ end
+
+ def self.fetch(email)
+ if !Rails.env.production?
+ filename = "#{Rails.root}/test/fixtures/clearbit/#{email}.json"
+ if File.exist?(filename)
+ data = IO.binread(filename)
+ return JSON.parse(data) if data
+ end
+ end
+
+ config = Setting.get('clearbit_config')
+ return if !config
+ return if config['api_key'].empty?
+
+ record = {
+ direction: 'out',
+ facility: 'clearbit',
+ url: "clearbit -> #{email}",
+ status: 200,
+ ip: nil,
+ request: { content: email },
+ response: {},
+ method: 'GET',
+ }
+
+ begin
+ Clearbit.key = config['api_key']
+ result = Clearbit::Enrichment.find(email: email, stream: true)
+ record[:response] = { code: 200, content: result.to_s }
+ rescue => e
+ record[:status] = 500
+ record[:response] = { code: 500, content: e.inspect }
+ end
+ HttpLog.create(record)
+ result
+ end
+
+end
diff --git a/db/migrate/20160424000002_create_external_sync.rb b/db/migrate/20160424000002_create_external_sync.rb
new file mode 100644
index 000000000..d0e6962c1
--- /dev/null
+++ b/db/migrate/20160424000002_create_external_sync.rb
@@ -0,0 +1,15 @@
+class CreateExternalSync < ActiveRecord::Migration
+ def up
+ create_table :external_syncs do |t|
+ t.string :source, limit: 100, null: false
+ t.string :source_id, limit: 200, null: false
+ t.string :object, limit: 100, null: false
+ t.integer :o_id, null: false
+ t.text :last_payload, limit: 500.kilobytes + 1, null: true
+ t.timestamps null: false
+ end
+ add_index :external_syncs, [:source, :source_id], unique: true
+ add_index :external_syncs, [:source, :source_id, :object, :o_id]
+ add_index :external_syncs, [:object, :o_id]
+ end
+end
diff --git a/db/migrate/20160425000001_add_clearbit_integration.rb b/db/migrate/20160425000001_add_clearbit_integration.rb
new file mode 100644
index 000000000..8f0973ad9
--- /dev/null
+++ b/db/migrate/20160425000001_add_clearbit_integration.rb
@@ -0,0 +1,46 @@
+class AddClearbitIntegration < ActiveRecord::Migration
+ def up
+ Setting.create_if_not_exists(
+ title: 'Clearbit integration',
+ name: 'clearbit_integration',
+ area: 'Integration::Switch',
+ description: 'Define if Clearbit (http://www.clearbit.com) is enabled or not.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'clearbit_integration',
+ tag: 'boolean',
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: false,
+ preferences: { prio: 1 },
+ frontend: false
+ )
+ Setting.create_if_not_exists(
+ title: 'Clearbit config',
+ name: 'clearbit_config',
+ area: 'Integration::Clearbit',
+ description: 'Define the Clearbit config.',
+ options: {},
+ state: {},
+ frontend: false,
+ preferences: { prio: 2 },
+ )
+ Setting.create_if_not_exists(
+ title: 'Define transaction backend.',
+ name: '9000_clearbit_enrichment',
+ area: 'Transaction::Backend',
+ description: 'Define the transaction backend which will enrich customer and organization informations from (http://www.clearbit.com).',
+ options: {},
+ state: 'Transaction::ClearbitEnrichment',
+ frontend: false
+ )
+ end
+end
diff --git a/db/seeds.rb b/db/seeds.rb
index 00a85194e..6ba2b3e16 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1864,6 +1864,48 @@ Setting.create_if_not_exists(
frontend: false,
preferences: { prio: 2 },
)
+Setting.create_if_not_exists(
+ title: 'Clearbit integration',
+ name: 'clearbit_integration',
+ area: 'Integration::Switch',
+ description: 'Define if Clearbit (http://www.clearbit.com) is enabled or not.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'clearbit_integration',
+ tag: 'boolean',
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: false,
+ preferences: { prio: 1 },
+ frontend: false
+)
+Setting.create_if_not_exists(
+ title: 'Clearbit config',
+ name: 'clearbit_config',
+ area: 'Integration::Clearbit',
+ description: 'Define the Clearbit config.',
+ options: {},
+ state: {},
+ frontend: false,
+ preferences: { prio: 2 },
+)
+Setting.create_if_not_exists(
+ title: 'Define transaction backend.',
+ name: '9000_clearbit_enrichment',
+ area: 'Transaction::Backend',
+ description: 'Define the transaction backend which will enrich customer and organization informations from (http://www.clearbit.com).',
+ options: {},
+ state: 'Transaction::ClearbitEnrichment',
+ frontend: false
+)
signature = Signature.create_if_not_exists(
id: 1,
diff --git a/test/fixtures/clearbit/alex@alexmaccaw.com.json b/test/fixtures/clearbit/alex@alexmaccaw.com.json
new file mode 100644
index 000000000..34b513907
--- /dev/null
+++ b/test/fixtures/clearbit/alex@alexmaccaw.com.json
@@ -0,0 +1,188 @@
+{
+ "person": {
+ "id": "d54c54ad-40be-4305-8a34-0ab44710b90d",
+ "name": {
+ "fullName": "Alex MacCaw",
+ "givenName": "Alex",
+ "familyName": "MacCaw"
+ },
+ "email": "alex@alexmaccaw.com",
+ "gender": "male",
+ "location": "San Francisco, CA, US",
+ "timeZone": "America/Los_Angeles",
+ "utcOffset": -8,
+ "geo": {
+ "city": "San Francisco",
+ "state": "California",
+ "stateCode": "CA",
+ "country": "United States",
+ "countryCode": "US",
+ "lat": 37.7749295,
+ "lng": -122.4194155
+ },
+ "bio": "O'Reilly author, software engineer & traveller. Founder of https://clearbit.com",
+ "site": "http://alexmaccaw.com",
+ "avatar": "https://d1ts43dypk8bqh.cloudfront.net/v1/avatars/d54c54ad-40be-4305-8a34-0ab44710b90d",
+ "employment": {
+ "domain": "clearbit.com",
+ "name": "Clearbit",
+ "title": "Founder and CEO",
+ "role": "ceo",
+ "seniority": "executive"
+ },
+ "facebook": {
+ "handle": "amaccaw"
+ },
+ "github": {
+ "handle": "maccman",
+ "avatar": "https://avatars.githubusercontent.com/u/2142?v=2",
+ "company": "Clearbit",
+ "blog": "http://alexmaccaw.com",
+ "followers": 2932,
+ "following": 94
+ },
+ "twitter": {
+ "handle": "maccaw",
+ "id": "2006261",
+ "bio": "O'Reilly author, software engineer & traveller. Founder of https://clearbit.com",
+ "followers": 15248,
+ "following": 1711,
+ "location": "San Francisco",
+ "site": "http://alexmaccaw.com",
+ "avatar": "https://pbs.twimg.com/profile_images/1826201101/297606_10150904890650705_570400704_21211347_1883468370_n.jpeg"
+ },
+ "linkedin": {
+ "handle": "pub/alex-maccaw/78/929/ab5"
+ },
+ "googleplus": {
+ "handle": null
+ },
+ "angellist": {
+ "handle": "maccaw",
+ "bio": "O'Reilly author, engineer & traveller. Mostly harmless.",
+ "blog": "http://blog.alexmaccaw.com",
+ "site": "http://alexmaccaw.com",
+ "followers": 532,
+ "avatar": "https://d1qb2nb5cznatu.cloudfront.net/users/403357-medium_jpg?1405661263"
+ },
+ "aboutme": {
+ "handle": "maccaw",
+ "bio": "Software engineer & traveller. Walker, skier, reader, tennis player, breather, ginger beer drinker, scooterer & generally enjoying things :)",
+ "avatar": "http://o.aolcdn.com/dims-global/dims/ABOUTME/5/803/408/80/http://d3mod6n032mdiz.cloudfront.net/thumb2/m/a/c/maccaw/maccaw-840x560.jpg"
+ },
+ "gravatar": {
+ "handle": "maccman",
+ "urls": [
+ {
+ "value": "http://alexmaccaw.com",
+ "title": "Personal Website"
+ }
+ ],
+ "avatar": "http://2.gravatar.com/avatar/994909da96d3afaf4daaf54973914b64",
+ "avatars": [
+ {
+ "url": "http://2.gravatar.com/avatar/994909da96d3afaf4daaf54973914b64",
+ "type": "thumbnail"
+ }
+ ]
+ },
+ "fuzzy": false
+ },
+ "company": {
+ "id": "027b0d40-016c-40ea-8925-a076fa640992",
+ "name": "Uber",
+ "legalName": "Uber, Inc.",
+ "domain": "uber.com",
+ "domainAliases": [],
+ "url": "http://uber.com",
+ "site": {
+ "url": "http://uber.com",
+ "title": null,
+ "h1": null,
+ "metaDescription": null,
+ "metaAuthor": null,
+ "phoneNumbers": [
+ "+1 877-223-8023"
+ ],
+ "emailAddresses": [
+ "team@uber.com"
+ ]
+ },
+ "tags": [
+ "Transportation",
+ "Design",
+ "SEO",
+ "Automotive",
+ "Real Time",
+ "Limousines",
+ "Public Transportation",
+ "Transport"
+ ],
+ "description": "Uber is a mobile app connecting passengers with drivers for hire.",
+ "foundedDate": "2009-03-01",
+ "location": "1455 Market Street, San Francisco, CA 94103, USA",
+ "timeZone": "America/Los_Angeles",
+ "utcOffset": -8,
+ "geo": {
+ "streetNumber": "1455",
+ "streetName": "Market Street",
+ "subPremise": null,
+ "city": "San Francisco",
+ "state": "California",
+ "stateCode": "CA",
+ "postalCode": "94103",
+ "country": "United States",
+ "countryCode": "US",
+ "lat": 37.7752315,
+ "lng": -122.4175567
+ },
+ "metrics": {
+ "raised": 5900000000,
+ "employees": 3250,
+ "googleRank": 7,
+ "alexaUsRank": 649,
+ "alexaGlobalRank": 1071,
+ "marketCap": null,
+ "annualRevenue": null
+ },
+ "logo": "https://dqus23xyrtg1i.cloudfront.net/v1/logos/027b0d40-016c-40ea-8925-a076fa640992",
+ "facebook": {
+ "handle": "uber.IND"
+ },
+ "linkedin": {
+ "handle": "company/uber.com"
+ },
+ "twitter": {
+ "handle": "uber",
+ "id": 19103481,
+ "bio": "Everyone's Private Driver. Question, concern or praise? Tweet at your local community manager here: https://t.co/EUiTjLk0xj",
+ "followers": 176582,
+ "following": 330,
+ "location": "Global",
+ "site": "http://t.co/PtMbwFTeQA",
+ "avatar": "https://pbs.twimg.com/profile_images/378800000762572812/91ea09a6535666e18ca3c56f731f67ef_normal.jpeg"
+ },
+ "angellist": {
+ "id": 19163,
+ "handle": "uber",
+ "description": "Request a car from any mobile phone via text message, iPhone and Android apps. Within minutes, a professional driver in a sleek black car will arrive curbside. Automatically charged to your credit card on file, tip included.",
+ "followers": 2650,
+ "blogUrl": "http://blog.uber.com/"
+ },
+ "crunchbase": {
+ "handle": "uber"
+ },
+ "phone": "+1 877-223-8023",
+ "emailProvider": false,
+ "type": "private",
+ "tech": [
+ "google_analytics",
+ "double_click",
+ "mixpanel",
+ "optimizely",
+ "typekit_by_adobe",
+ "nginx",
+ "google_apps"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/clearbit/me2@example.com.json b/test/fixtures/clearbit/me2@example.com.json
new file mode 100644
index 000000000..36f4e260d
--- /dev/null
+++ b/test/fixtures/clearbit/me2@example.com.json
@@ -0,0 +1,196 @@
+{
+ "person": {
+ "id": "4db7cfeb-8017-4aac-b4b9-debb6d3142c0",
+ "name": {
+ "fullName": "Martini Edenhofer",
+ "givenName": "Martini",
+ "familyName": "Edenhofer"
+ },
+ "email": "me2@otrs.org",
+ "gender": null,
+ "location": "Berlin, Berlin, DE",
+ "timeZone": "Europe/Berlin",
+ "utcOffset": 2,
+ "geo": {
+ "city": "Berlin",
+ "state": "Berlin",
+ "stateCode": "Berlin",
+ "country": "Germany",
+ "countryCode": "DE",
+ "lat": 52.52000659999999,
+ "lng": 13.404954
+ },
+ "bio": "Open Source professional and geek. Also known as OTRS inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.",
+ "site": "http://edenhofer.de/",
+ "avatar": "https://d1ts43dypk8bqh.cloudfront.net/v1/avatars/4db7cfeb-8017-4aac-b4b9-debb6d3142c0",
+ "employment": {
+ "domain": "otrs.org",
+ "name": "OTRS",
+ "title": "CEO",
+ "role": null,
+ "seniority": "executive"
+ },
+ "facebook": {
+ "handle": null
+ },
+ "github": {
+ "handle": null,
+ "id": null,
+ "avatar": null,
+ "company": null,
+ "blog": null,
+ "followers": null,
+ "following": null
+ },
+ "twitter": {
+ "handle": "medenhofer",
+ "id": 219826253,
+ "bio": "Open Source professional and geek. Also known as OTRS inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.",
+ "followers": 215,
+ "following": 260,
+ "statuses": 114,
+ "favorites": 8,
+ "location": "",
+ "site": "http://edenhofer.de/",
+ "avatar": "https://pbs.twimg.com/profile_images/1216362658/DSC_0084-p.jpg"
+ },
+ "linkedin": {
+ "handle": "in/enjoyme"
+ },
+ "googleplus": {
+ "handle": null
+ },
+ "aboutme": {
+ "handle": null,
+ "bio": null,
+ "avatar": null
+ },
+ "gravatar": {
+ "handle": "bazzi",
+ "urls": [
+ {
+ "value": "http://edenhofer.de",
+ "title": "Blog"
+ },
+ {
+ "value": "http://znuny.com",
+ "title": "Znuny - OTRS related services"
+ }
+ ],
+ "avatar": "http://2.gravatar.com/avatar/0e321a416c45d214d1040a7667cb0b54",
+ "avatars": [
+ {
+ "url": "http://2.gravatar.com/avatar/0e321a416c45d214d1040a7667cb0b54",
+ "type": "thumbnail"
+ }
+ ]
+ },
+ "fuzzy": false
+ },
+ "company": {
+ "id": "2b30663f-f1a4-47a9-b2a9-0817b8d3ae7b",
+ "name": "OTRS",
+ "legalName": "OTRS AG",
+ "domain": "otrs.com",
+ "domainAliases": [
+ "otrs.org"
+ ],
+ "url": "http://otrs.com",
+ "site": {
+ "url": "http://otrs.com",
+ "title": "OTRS Simple Service Management",
+ "h1": "What is OTRS?",
+ "metaDescription": "OTRS is an Open Source helpdesk software and an IT Service Management software free of licence costs. Improve your Customer Service Management with OTRS.",
+ "metaAuthor": null,
+ "phoneNumbers": [
+ "+49 6172 681988",
+ "+1 408-549-1717",
+ "+49 5255 11689664",
+ "+49 6032 0355578",
+ "+49 9421 568181",
+ "+1 408-512-1748",
+ "+49 8523 6901503",
+ "+49 6032 7258038",
+ "+49 6032 0355568",
+ "+49 551 141308205"
+ ],
+ "emailAddresses": [
+ "sales@otrs.com",
+ "otrs-demo@otrs.org",
+ "itsm-demo@otrs.org",
+ "security@otrs.org",
+ "investor-relations@otrs.com"
+ ]
+ },
+ "category": {
+ "sector": "Information Technology",
+ "industryGroup": "Software & Services",
+ "industry": "Internet Software & Services",
+ "subIndustry": "Internet Software & Services"
+ },
+ "tags": [
+ "Information Technology & Services",
+ "Technology",
+ "Software"
+ ],
+ "description": "OTRS is an Open Source helpdesk software and an IT Service Management software free of licence costs. Improve your Customer Service Management with OTRS.",
+ "foundedDate": null,
+ "location": "Norsk-Data-Straße 1, 61352 Bad Homburg vor der Höhe, Germany",
+ "timeZone": "Europe/Berlin",
+ "utcOffset": 2,
+ "geo": {
+ "streetNumber": "1",
+ "streetName": "Norsk-Data-Straße",
+ "subPremise": null,
+ "city": "Bad Homburg vor der Höhe",
+ "postalCode": "61352",
+ "state": "Hessen",
+ "stateCode": "HE",
+ "country": "Germany",
+ "countryCode": "DE",
+ "lat": 50.21093,
+ "lng": 8.652809999999999
+ },
+ "logo": "https://logo.clearbit.com/otrs.com",
+ "facebook": {
+ "handle": "otrsgroup"
+ },
+ "linkedin": {
+ "handle": "company/otrs-ag"
+ },
+ "twitter": {
+ "handle": "OTRSGroup",
+ "id": "126673991",
+ "bio": "OTRS is the leading open source service management software including Help Desk and ITIL V3-certified IT service management (ITSM).",
+ "followers": 2988,
+ "following": 2484,
+ "location": "Cupertino, CA, Bad Homburg",
+ "site": "http://t.co/F09dcTEScJ",
+ "avatar": "https://pbs.twimg.com/profile_images/575314188848889856/F4KWmvVX_normal.png"
+ },
+ "crunchbase": {
+ "handle": "organization/otrs"
+ },
+ "emailProvider": false,
+ "type": "private",
+ "ticker": null,
+ "phone": null,
+ "metrics": {
+ "alexaUsRank": null,
+ "alexaGlobalRank": 116381,
+ "googleRank": 0,
+ "employees": 15,
+ "marketCap": null,
+ "raised": null,
+ "annualRevenue": null
+ },
+ "tech": [
+ "google_analytics",
+ "double_click",
+ "google_adwords",
+ "optimizely",
+ "wordpress",
+ "pardot"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/clearbit/me@example.com.json b/test/fixtures/clearbit/me@example.com.json
new file mode 100644
index 000000000..181255c4d
--- /dev/null
+++ b/test/fixtures/clearbit/me@example.com.json
@@ -0,0 +1,196 @@
+{
+ "person": {
+ "id": "4db7cfeb-8017-4aac-b4b9-debb6d3142c0",
+ "name": {
+ "fullName": "Martin Edenhofer",
+ "givenName": "Martin",
+ "familyName": "Edenhofer"
+ },
+ "email": "me@otrs.org",
+ "gender": null,
+ "location": "Berlin, Berlin, DE",
+ "timeZone": "Europe/Berlin",
+ "utcOffset": 2,
+ "geo": {
+ "city": "Berlin",
+ "state": "Berlin",
+ "stateCode": "Berlin",
+ "country": "Germany",
+ "countryCode": "DE",
+ "lat": 52.52000659999999,
+ "lng": 13.404954
+ },
+ "bio": "Open Source professional and geek. Also known as OTRS inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.",
+ "site": "http://edenhofer.de/",
+ "avatar": "https://d1ts43dypk8bqh.cloudfront.net/v1/avatars/4db7cfeb-8017-4aac-b4b9-debb6d3142c0",
+ "employment": {
+ "domain": "otrs.org",
+ "name": "OTRS",
+ "title": "CEO",
+ "role": null,
+ "seniority": "executive"
+ },
+ "facebook": {
+ "handle": null
+ },
+ "github": {
+ "handle": null,
+ "id": null,
+ "avatar": null,
+ "company": null,
+ "blog": null,
+ "followers": null,
+ "following": null
+ },
+ "twitter": {
+ "handle": "medenhofer",
+ "id": 219826253,
+ "bio": "Open Source professional and geek. Also known as OTRS inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.",
+ "followers": 215,
+ "following": 260,
+ "statuses": 114,
+ "favorites": 8,
+ "location": "",
+ "site": "http://edenhofer.de/",
+ "avatar": "https://pbs.twimg.com/profile_images/1216362658/DSC_0084-p.jpg"
+ },
+ "linkedin": {
+ "handle": "in/enjoyme"
+ },
+ "googleplus": {
+ "handle": null
+ },
+ "aboutme": {
+ "handle": null,
+ "bio": null,
+ "avatar": null
+ },
+ "gravatar": {
+ "handle": "bazzi",
+ "urls": [
+ {
+ "value": "http://edenhofer.de",
+ "title": "Blog"
+ },
+ {
+ "value": "http://znuny.com",
+ "title": "Znuny - OTRS related services"
+ }
+ ],
+ "avatar": "http://2.gravatar.com/avatar/0e321a416c45d214d1040a7667cb0b54",
+ "avatars": [
+ {
+ "url": "http://2.gravatar.com/avatar/0e321a416c45d214d1040a7667cb0b54",
+ "type": "thumbnail"
+ }
+ ]
+ },
+ "fuzzy": false
+ },
+ "company": {
+ "id": "2b30663f-f1a4-47a9-b2a9-0817b8d3ae7b",
+ "name": "OTRS",
+ "legalName": null,
+ "domain": "otrs.com",
+ "domainAliases": [
+ "otrs.org"
+ ],
+ "url": "http://otrs.com",
+ "site": {
+ "url": "http://otrs.com",
+ "title": "OTRS Simple Service Management",
+ "h1": "What is OTRS?",
+ "metaDescription": "OTRS is an Open Source helpdesk software and an IT Service Management software free of licence costs. Improve your Customer Service Management with OTRS.",
+ "metaAuthor": null,
+ "phoneNumbers": [
+ "+49 6172 681988",
+ "+1 408-549-1717",
+ "+49 5255 11689664",
+ "+49 6032 0355578",
+ "+49 9421 568181",
+ "+1 408-512-1748",
+ "+49 8523 6901503",
+ "+49 6032 7258038",
+ "+49 6032 0355568",
+ "+49 551 141308205"
+ ],
+ "emailAddresses": [
+ "sales@otrs.com",
+ "otrs-demo@otrs.org",
+ "itsm-demo@otrs.org",
+ "security@otrs.org",
+ "investor-relations@otrs.com"
+ ]
+ },
+ "category": {
+ "sector": "Information Technology",
+ "industryGroup": "Software & Services",
+ "industry": "Internet Software & Services",
+ "subIndustry": "Internet Software & Services"
+ },
+ "tags": [
+ "Information Technology & Services",
+ "Technology",
+ "Software"
+ ],
+ "description": "OTRS is an Open Source helpdesk software and an IT Service Management software free of licence costs. Improve your Customer Service Management with OTRS.",
+ "foundedDate": null,
+ "location": "Norsk-Data-Straße 1, 61352 Bad Homburg vor der Höhe, Germany",
+ "timeZone": "Europe/Berlin",
+ "utcOffset": 2,
+ "geo": {
+ "streetNumber": "1",
+ "streetName": "Norsk-Data-Straße",
+ "subPremise": null,
+ "city": "Bad Homburg vor der Höhe",
+ "postalCode": "61352",
+ "state": "Hessen",
+ "stateCode": "HE",
+ "country": "Germany",
+ "countryCode": "DE",
+ "lat": 50.21093,
+ "lng": 8.652809999999999
+ },
+ "logo": "https://logo.clearbit.com/otrs.com",
+ "facebook": {
+ "handle": "otrsgroup"
+ },
+ "linkedin": {
+ "handle": "company/otrs-ag"
+ },
+ "twitter": {
+ "handle": "OTRSGroup",
+ "id": "126673991",
+ "bio": "OTRS is the leading open source service management software including Help Desk and ITIL V3-certified IT service management (ITSM).",
+ "followers": 2988,
+ "following": 2484,
+ "location": "Cupertino, CA, Bad Homburg",
+ "site": "http://t.co/F09dcTEScJ",
+ "avatar": "https://pbs.twimg.com/profile_images/575314188848889856/F4KWmvVX_normal.png"
+ },
+ "crunchbase": {
+ "handle": "organization/otrs"
+ },
+ "emailProvider": false,
+ "type": "private",
+ "ticker": null,
+ "phone": null,
+ "metrics": {
+ "alexaUsRank": null,
+ "alexaGlobalRank": 116381,
+ "googleRank": 0,
+ "employees": 15,
+ "marketCap": null,
+ "raised": null,
+ "annualRevenue": null
+ },
+ "tech": [
+ "google_analytics",
+ "double_click",
+ "google_adwords",
+ "optimizely",
+ "wordpress",
+ "pardot"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/clearbit/testing4@znuny.com.json b/test/fixtures/clearbit/testing4@znuny.com.json
new file mode 100644
index 000000000..9bef810d3
--- /dev/null
+++ b/test/fixtures/clearbit/testing4@znuny.com.json
@@ -0,0 +1,96 @@
+{
+ "person": null,
+ "company": {
+ "id": "273d8666-9d70-43dc-836b-a47a88293905",
+ "name": "Znuny / ES for OTRS",
+ "legalName": null,
+ "domain": "znuny.com",
+ "domainAliases": [
+ "znuny.de"
+ ],
+ "url": "http://znuny.com",
+ "site": {
+ "url": "http://znuny.com",
+ "title": "Support, Consulting, Development and Training for OTRS - Znuny GmbH",
+ "h1": "We make OTRS support easy!",
+ "metaDescription": "OTRS Support, Consulting, Development, Training and Customizing - Znuny GmbH",
+ "metaAuthor": null,
+ "phoneNumbers": [
+ "+49 30 6098541",
+ "+49 4107 15880339",
+ "+49 8601 81017925",
+ "+49 4107 15880186",
+ "+49 8602 16139061"
+ ],
+ "emailAddresses": [
+ "info@znuny.com"
+ ]
+ },
+ "category": {
+ "sector": "Industrials",
+ "industryGroup": "Commercial & Professional Services",
+ "industry": "Professional Services",
+ "subIndustry": "Consulting"
+ },
+ "tags": [
+ "Consulting & Professional Services",
+ "Corporate & Business",
+ "Information Technology & Services"
+ ],
+ "description": "OTRS Support, Consulting, Development, Training and Customizing - Znuny GmbH",
+ "foundedDate": null,
+ "location": "Marienstraße 11, 10117 Berlin, Germany",
+ "timeZone": "Europe/Berlin",
+ "utcOffset": 2,
+ "geo": {
+ "streetNumber": "11",
+ "streetName": "Marienstraße",
+ "subPremise": null,
+ "city": "Berlin",
+ "postalCode": "10117",
+ "state": "Berlin",
+ "stateCode": "Berlin",
+ "country": "Germany",
+ "countryCode": "DE",
+ "lat": 52.52182,
+ "lng": 13.38268
+ },
+ "logo": "https://logo.clearbit.com/znuny.com",
+ "facebook": {
+ "handle": null
+ },
+ "linkedin": {
+ "handle": "company/znuny-gmbh"
+ },
+ "twitter": {
+ "handle": "znuny",
+ "id": "293437546",
+ "bio": "Enterprise Services for OTRS! We love to do what we can do!",
+ "followers": 85,
+ "following": 63,
+ "location": "Berlin",
+ "site": "http://t.co/miuzrhoHeG",
+ "avatar": "https://pbs.twimg.com/profile_images/2460325455/hix7so4b0h0miq9gwkw8_normal.png"
+ },
+ "crunchbase": {
+ "handle": null
+ },
+ "emailProvider": false,
+ "type": "private",
+ "ticker": null,
+ "phone": null,
+ "metrics": {
+ "alexaUsRank": null,
+ "alexaGlobalRank": 10086408,
+ "googleRank": 3,
+ "employees": null,
+ "marketCap": null,
+ "raised": null,
+ "annualRevenue": null
+ },
+ "tech": [
+ "google_analytics",
+ "ruby_on_rails"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/clearbit/testing5@znuny.com.json b/test/fixtures/clearbit/testing5@znuny.com.json
new file mode 100644
index 000000000..e74f92d8f
--- /dev/null
+++ b/test/fixtures/clearbit/testing5@znuny.com.json
@@ -0,0 +1,120 @@
+{
+ "person": {
+ "id": "4db7cfeb-8017-4aac-b4b9-debb6d3142c0-1",
+ "name": {
+ "fullName": "Bob Smith",
+ "givenName": "Bob",
+ "familyName": "Smith"
+ },
+ "email": "testing5@znuny.com",
+ "gender": null,
+ "location": "Berlin, Berlin, DE",
+ "timeZone": "Europe/Berlin",
+ "utcOffset": 2,
+ "geo": {
+ "city": "Berlin",
+ "state": "Berlin",
+ "stateCode": "Berlin",
+ "country": "Germany",
+ "countryCode": "DE",
+ "lat": 52.52000659999999,
+ "lng": 13.404954
+ },
+ "bio": "some_bio",
+ "site": "http://example.com/",
+ "avatar": "https://d1ts43dypk8bqh.cloudfront.net/v1/avatars/4db7cfeb-8017-4aac-b4b9-debb6d3142c0"
+ },
+ "company": {
+ "id": "273d8666-9d70-43dc-836b-a47a88293905",
+ "name": "Znuny2",
+ "legalName": null,
+ "domain": "znuny.com",
+ "domainAliases": [
+ "znuny.de"
+ ],
+ "url": "http://znuny.com",
+ "site": {
+ "url": "http://znuny.com",
+ "title": "Support, Consulting, Development and Training for OTRS - Znuny GmbH",
+ "h1": "We make OTRS support easy!",
+ "metaDescription": "OTRS Support, Consulting, Development, Training and Customizing - Znuny GmbH",
+ "metaAuthor": null,
+ "phoneNumbers": [
+ "+49 30 6098541",
+ "+49 4107 15880339",
+ "+49 8601 81017925",
+ "+49 4107 15880186",
+ "+49 8602 16139061"
+ ],
+ "emailAddresses": [
+ "info@znuny.com"
+ ]
+ },
+ "category": {
+ "sector": "Industrials",
+ "industryGroup": "Commercial & Professional Services",
+ "industry": "Professional Services",
+ "subIndustry": "Consulting"
+ },
+ "tags": [
+ "Consulting & Professional Services",
+ "Corporate & Business",
+ "Information Technology & Services"
+ ],
+ "description": "Znuny2 GmbH",
+ "foundedDate": null,
+ "location": "Marienstraße 11, 10117 Berlin, Germany",
+ "timeZone": "Europe/Berlin",
+ "utcOffset": 2,
+ "geo": {
+ "streetNumber": "11",
+ "streetName": "Marienstraße",
+ "subPremise": null,
+ "city": "Berlin",
+ "postalCode": "10117",
+ "state": "Berlin",
+ "stateCode": "Berlin",
+ "country": "Germany",
+ "countryCode": "DE",
+ "lat": 52.52182,
+ "lng": 13.38268
+ },
+ "logo": "https://logo.clearbit.com/znuny.com",
+ "facebook": {
+ "handle": null
+ },
+ "linkedin": {
+ "handle": "company/znuny-gmbh"
+ },
+ "twitter": {
+ "handle": "znuny",
+ "id": "293437546",
+ "bio": "Enterprise Services for OTRS! We love to do what we can do!",
+ "followers": 85,
+ "following": 63,
+ "location": "Berlin",
+ "site": "http://t.co/miuzrhoHeG",
+ "avatar": "https://pbs.twimg.com/profile_images/2460325455/hix7so4b0h0miq9gwkw8_normal.png"
+ },
+ "crunchbase": {
+ "handle": null
+ },
+ "emailProvider": false,
+ "type": "private",
+ "ticker": null,
+ "phone": null,
+ "metrics": {
+ "alexaUsRank": null,
+ "alexaGlobalRank": 10086408,
+ "googleRank": 3,
+ "employees": null,
+ "marketCap": null,
+ "raised": null,
+ "annualRevenue": null
+ },
+ "tech": [
+ "google_analytics",
+ "ruby_on_rails"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test/integration/clearbit_test.rb b/test/integration/clearbit_test.rb
new file mode 100644
index 000000000..c04de701f
--- /dev/null
+++ b/test/integration/clearbit_test.rb
@@ -0,0 +1,281 @@
+# encoding: utf-8
+require 'integration_test_helper'
+
+class ClearbitTest < ActiveSupport::TestCase
+
+ # check
+ test 'base' do
+ if !ENV['CLEARBIT_CI_API_KEY']
+ raise "ERROR: Need CLEARBIT_CI_API_KEY - hint CLEARBIT_CI_API_KEY='abc...'"
+ end
+
+ # set system mode to done / to activate
+ Setting.set('system_init_done', true)
+
+ Setting.set('clearbit_integration', true)
+ Setting.set('clearbit_config', {
+ api_key: ENV['CLEARBIT_CI_API_KEY'],
+ organization_autocreate: true,
+ organization_shared: false,
+ user_sync: {
+ 'person.name.givenName' => 'user.firstname',
+ 'person.name.familyName' => 'user.lastname',
+ 'person.email' => 'user.email',
+ 'person.bio' => 'user.note',
+ 'company.url' => 'user.web',
+ 'person.site' => 'user.web',
+ 'company.location' => 'user.address',
+ 'person.location' => 'user.address',
+ #'person.timeZone' => 'user.preferences[:timezone]',
+ #'person.gender' => 'user.preferences[:gender]',
+ },
+ organization_sync: {
+ 'company.legalName' => 'organization.name',
+ 'company.name' => 'organization.name',
+ 'company.description' => 'organization.note',
+ },
+ })
+
+ # case 1 - person + company (demo data set)
+ customer1 = User.create(
+ firstname: '',
+ lastname: 'Should be still there',
+ email: 'alex@alexmaccaw.com',
+ note: '',
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ assert(customer1)
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'User', o_id: customer1.id))
+
+ customer1_lookup = User.lookup(id: customer1.id)
+
+ assert_equal('Alex', customer1_lookup.firstname)
+ assert_equal('Should be still there', customer1_lookup.lastname)
+ assert_equal('O\'Reilly author, software engineer & traveller. Founder of https://clearbit.com', customer1_lookup.note)
+ assert_equal('1455 Market Street, San Francisco, CA 94103, USA', customer1_lookup.address)
+
+ organization1_lookup = Organization.find_by(name: 'Uber, Inc.')
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'Organization', o_id: organization1_lookup.id))
+ assert_equal(false, organization1_lookup.shared)
+ assert_equal('Uber is a mobile app connecting passengers with drivers for hire.', organization1_lookup.note)
+
+ # case 2 - person + company
+ customer2 = User.create(
+ firstname: '',
+ lastname: '',
+ email: 'me@example.com',
+ note: '',
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ assert(customer2)
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'User', o_id: customer2.id))
+
+ customer2_lookup = User.lookup(id: customer2.id)
+
+ assert_equal('Martin', customer2_lookup.firstname)
+ assert_equal('Edenhofer', customer2_lookup.lastname)
+ assert_equal("Open Source professional and geek. Also known as OTRS inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.", customer2_lookup.note)
+ assert_equal('Norsk-Data-Straße 1, 61352 Bad Homburg vor der Höhe, Germany', customer2_lookup.address)
+
+ organization2_lookup = Organization.find_by(name: 'OTRS')
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'Organization', o_id: organization2_lookup.id))
+ assert_equal(false, organization2_lookup.shared)
+ assert_equal('OTRS is an Open Source helpdesk software and an IT Service Management software free of licence costs. Improve your Customer Service Management with OTRS.', organization2_lookup.note)
+
+ # update with own values (do not overwrite)
+ customer2.update_attributes(
+ firstname: 'Martini',
+ note: 'changed by my self',
+ )
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'User', o_id: customer2.id))
+
+ customer2_lookup = User.lookup(id: customer2.id)
+
+ assert_equal('Martini', customer2_lookup.firstname)
+ assert_equal('Edenhofer', customer2_lookup.lastname)
+ assert_equal('changed by my self', customer2_lookup.note)
+ assert_equal('Norsk-Data-Straße 1, 61352 Bad Homburg vor der Höhe, Germany', customer2_lookup.address)
+
+ Transaction::ClearbitEnrichment.sync_user(customer2)
+ Delayed::Worker.new.work_off
+
+ customer2_lookup = User.lookup(id: customer2.id)
+
+ assert_equal('Martini', customer2_lookup.firstname)
+ assert_equal('Edenhofer', customer2_lookup.lastname)
+ assert_equal('changed by my self', customer2_lookup.note)
+ assert_equal('Norsk-Data-Straße 1, 61352 Bad Homburg vor der Höhe, Germany', customer2_lookup.address)
+
+ # update with own values (do not overwrite)
+ customer2.update_attributes(
+ firstname: '',
+ note: 'changed by my self',
+ )
+
+ Transaction::ClearbitEnrichment.sync_user(customer2)
+ Delayed::Worker.new.work_off
+
+ customer2_lookup = User.lookup(id: customer2.id)
+
+ assert_equal('Martin', customer2_lookup.firstname)
+ assert_equal('Edenhofer', customer2_lookup.lastname)
+ assert_equal('changed by my self', customer2_lookup.note)
+ assert_equal('Norsk-Data-Straße 1, 61352 Bad Homburg vor der Höhe, Germany', customer2_lookup.address)
+
+ # update with changed values at clearbit site (do overwrite)
+ customer2.update_attributes(
+ email: 'me2@example.com',
+ )
+
+ Transaction::ClearbitEnrichment.sync_user(customer2)
+ Delayed::Worker.new.work_off
+
+ customer2_lookup = User.lookup(id: customer2.id)
+
+ assert_equal('Martini', customer2_lookup.firstname)
+ assert_equal('Edenhofer', customer2_lookup.lastname)
+ assert_equal('changed by my self', customer2_lookup.note)
+ assert_equal('Norsk-Data-Straße 1, 61352 Bad Homburg vor der Höhe, Germany', customer2_lookup.address)
+
+ organization2_lookup = Organization.find_by(name: 'OTRS AG')
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'Organization', o_id: organization2_lookup.id))
+ assert_equal(false, organization2_lookup.shared)
+ assert_equal('OTRS is an Open Source helpdesk software and an IT Service Management software free of licence costs. Improve your Customer Service Management with OTRS.', organization2_lookup.note)
+
+ # case 3 - no person
+ customer3 = User.create(
+ firstname: '',
+ lastname: '',
+ email: 'testing4@znuny.com',
+ note: '',
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ assert(customer3)
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ assert_not(ExternalSync.find_by(source: 'clearbit', object: 'User', o_id: customer3.id))
+
+ customer3_lookup = User.lookup(id: customer3.id)
+ assert_not_equal(customer3.updated_at, customer3_lookup.updated_at)
+
+ assert_equal('', customer3_lookup.firstname)
+ assert_equal('', customer3_lookup.lastname)
+ assert_equal('', customer3_lookup.note)
+ assert_equal('http://znuny.com', customer3_lookup.web)
+ assert_equal('Marienstraße 11, 10117 Berlin, Germany', customer3_lookup.address)
+
+ organization3_lookup = Organization.find_by(name: 'Znuny / ES for OTRS')
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'Organization', o_id: organization3_lookup.id))
+ assert_equal(false, organization3_lookup.shared)
+ assert_equal('OTRS Support, Consulting, Development, Training and Customizing - Znuny GmbH', organization3_lookup.note)
+
+ # case 4 - no person / real api call
+ customer4 = User.create(
+ firstname: '',
+ lastname: '',
+ email: 'testing5@clearbit.com',
+ note: '',
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ assert(customer4)
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ assert_not(ExternalSync.find_by(source: 'clearbit', object: 'User', o_id: customer4.id))
+
+ customer4_lookup = User.lookup(id: customer4.id)
+ assert_not_equal(customer4.updated_at, customer4_lookup.updated_at)
+
+ assert_equal('', customer4_lookup.firstname)
+ assert_equal('', customer4_lookup.lastname)
+ assert_equal('', customer4_lookup.note)
+ assert_equal('http://clearbit.com', customer4_lookup.web)
+ assert_equal('', customer4_lookup.address)
+
+ organization4_lookup = Organization.find_by(name: 'Clearbit')
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'Organization', o_id: organization4_lookup.id))
+ assert_equal(false, organization4_lookup.shared)
+ assert_equal('Clearbit provides powerful products and data APIs to help your business grow. Contact enrichment, lead generation, financial compliance, and more...', organization4_lookup.note)
+
+ end
+
+ # check
+ test 'base with invalid input' do
+ if !ENV['CLEARBIT_CI_API_KEY']
+ raise "ERROR: Need CLEARBIT_CI_API_KEY - hint CLEARBIT_CI_API_KEY='abc...'"
+ end
+
+ # set system mode to done / to activate
+ Setting.set('system_init_done', true)
+
+ Setting.set('clearbit_integration', true)
+ Setting.set('clearbit_config', {
+ api_key: ENV['CLEARBIT_CI_API_KEY'],
+ organization_autocreate: true,
+ organization_shared: true,
+ user_sync: {
+ 'person.name.givenName' => 'user.firstname',
+ 'person.name.familyName' => 'user.lastname',
+ 'person.email' => 'user.email',
+ 'person.bio' => 'user.note_not_existing',
+ 'company.url' => 'user.web',
+ 'person.site' => 'user.web',
+ 'company.location' => 'user.address',
+ 'person.location' => 'user.address',
+ },
+ organization_sync: {
+ 'company.legalName' => 'organization.name',
+ 'company.name' => 'organization.name',
+ 'company.description' => 'organization.note_not_existing',
+ },
+ })
+
+ # case 1 - person + company (demo data set)
+ customer1 = User.create(
+ firstname: '',
+ lastname: 'Should be still there',
+ email: 'testing5@znuny.com',
+ note: '',
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ assert(customer1)
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'User', o_id: customer1.id))
+
+ customer1_lookup = User.lookup(id: customer1.id)
+
+ assert_equal('Bob', customer1_lookup.firstname)
+ assert_equal('Should be still there', customer1_lookup.lastname)
+ assert_equal('', customer1_lookup.note)
+ assert_equal('Marienstraße 11, 10117 Berlin, Germany', customer1_lookup.address)
+
+ organization1_lookup = Organization.find_by(name: 'Znuny2')
+ assert(ExternalSync.find_by(source: 'clearbit', object: 'Organization', o_id: organization1_lookup.id))
+ assert_equal(true, organization1_lookup.shared)
+ assert_equal('', organization1_lookup.note)
+ end
+
+end