From e06f4ee7fe9c39571472f8f29d2a41922bd33d2b Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 26 Apr 2016 01:04:59 +0200 Subject: [PATCH] Added init version of clearbit integration. --- .gitlab-ci.yml | 11 + Gemfile | 4 +- Gemfile.lock | 6 +- .../controllers/_integration/clearbit.coffee | 165 +++++++++ .../_integration/sipgate_io.coffee | 2 +- .../app/controllers/_settings/area.coffee | 94 +---- .../app/controllers/_settings/form.coffee | 89 +++++ app/assets/javascripts/app/index.coffee | 1 + .../app/views/integration/clearbit.jst.eco | 75 ++++ .../app/views/integration/index.jst.eco | 2 +- app/models/external_sync.rb | 5 + app/models/transaction/background_job.rb | 9 +- app/models/transaction/clearbit_enrichment.rb | 321 ++++++++++++++++++ .../20160424000002_create_external_sync.rb | 15 + ...20160425000001_add_clearbit_integration.rb | 46 +++ db/seeds.rb | 42 +++ .../clearbit/alex@alexmaccaw.com.json | 188 ++++++++++ test/fixtures/clearbit/me2@example.com.json | 196 +++++++++++ test/fixtures/clearbit/me@example.com.json | 196 +++++++++++ .../fixtures/clearbit/testing4@znuny.com.json | 96 ++++++ .../fixtures/clearbit/testing5@znuny.com.json | 120 +++++++ test/integration/clearbit_test.rb | 281 +++++++++++++++ 22 files changed, 1865 insertions(+), 99 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/_integration/clearbit.coffee create mode 100644 app/assets/javascripts/app/controllers/_settings/form.coffee create mode 100644 app/assets/javascripts/app/views/integration/clearbit.jst.eco create mode 100644 app/models/external_sync.rb create mode 100644 app/models/transaction/clearbit_enrichment.rb create mode 100644 db/migrate/20160424000002_create_external_sync.rb create mode 100644 db/migrate/20160425000001_add_clearbit_integration.rb create mode 100644 test/fixtures/clearbit/alex@alexmaccaw.com.json create mode 100644 test/fixtures/clearbit/me2@example.com.json create mode 100644 test/fixtures/clearbit/me@example.com.json create mode 100644 test/fixtures/clearbit/testing4@znuny.com.json create mode 100644 test/fixtures/clearbit/testing5@znuny.com.json create mode 100644 test/integration/clearbit_test.rb 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 @@ +
+ +
+ + + + + +<% for setting in @settings: %> + + +
<%- @T('Title') %> + <%- @T('Value') %> + <%- @T('Description') %> +
<%- @T(setting.display) %> + +

<%- @RichText(setting.note) %>

+<% end %> +
+
+ +

<%- @T('Mapping') %>

+ +

<%- @T('What values of %s should be synced to users.', 'Clearbit') %>

+ +
+ + + + + +<% for source, destination of @config.user_sync: %> + + + +
<%- @T('Clearbit') %> + <%- @T('Zammad') %> + <%- @T('Action') %> +
+ +
<%- @Icon('trash') %> <%- @T('Remove') %>
+<% end %> +
+ +
<%- @Icon('plus-small') %> <%- @T('Add') %>
+
+
+ +

<%- @T('What values of %s should be synced to organization.', 'Clearbit') %>

+ +
+ + + + + +<% for source, destination of @config.organization_sync: %> + + + +
<%- @T('Clearbit') %> + <%- @T('Zammad') %> + <%- @T('Action') %> +
+ +
<%- @Icon('trash') %> <%- @T('Remove') %>
+<% end %> +
+ +
<%- @Icon('plus-small') %> <%- @T('Add') %>
+
+
+ + +
\ 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