Finalize houdini_full_contact

This commit is contained in:
Eric 2020-06-22 15:41:32 -05:00 committed by Eric Schultz
parent a3b2e9f954
commit 7d8698a502
11 changed files with 72 additions and 82 deletions

View file

@ -5,7 +5,8 @@
Wisper.clear if Rails.env.development? Wisper.clear if Rails.env.development?
Rails.application.config.houdini.listeners << [NonprofitMailerListener, Rails.application.config.houdini.listeners.push(NonprofitMailerListener,
CreditCardPaymentListener, CreditCardPaymentListener,
SepaPaymentListener, SepaPaymentListener,
TicketListener] TicketListener
)

View file

@ -104,7 +104,7 @@ in the provided locales: #{Houdini.intl.available_locales.join(', ')}") if Houdi
Houdini.show_state_fields = app.config.houdini.show_state_fields Houdini.show_state_fields = app.config.houdini.show_state_fields
Houdini.default_bp = app.config.houdini.default_bp.id Houdini.default_bp = app.config.houdini.default_bp.id
Houdini.event_publisher.subscribe_all(app.config.houdini.listeners.flatten) Houdini.event_publisher.subscribe_all(app.config.houdini.listeners)
end end
end end
end end

View file

@ -1,8 +1,15 @@
# FullContact # FullContact
An Houdini add-on to use FullContact's Context API An Houdini add-on to use FullContact's Enrich API. This add-on provides a few features:
* a event listener for supporter_create which downloads information from the Enrich API.
* adds a has_many relation on Supporter for every set of data about that Supporter downloaded.
Each item is an instance of `Houdini::FullContact::Info`
## Usage ## Usage
How to use my plugin. You can provide your FullContact API key in one of two ways:
* Setting the `FULL_CONTACT_KEY` environment variable or
* Setting the `houdini.full_contact.api_key` configuration option
## Installation ## Installation
Add this line to your application's Gemfile: Add this line to your application's Gemfile:
@ -16,10 +23,7 @@ And then execute:
$ bundle $ bundle
``` ```
Or install it yourself as: And then install the database migrations for houdini_full_contact:
```bash ```bash
$ gem install full_contact bin/rails houdini_full_contact:install:migrations
``` ```
## Contributing
Contribution directions go here.

View file

@ -0,0 +1,6 @@
# frozen_string_literal: true
# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
class Houdini::FullContact::ApplicationJob < ActiveJob::Base
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
class Houdini::FullContact::FullContactJob < Houdini::FullContact::ApplicationJob
queue_as :full_contact_queue
retry_on Exception, wait: ->(executions) { executions **2.195 }, attempts: Houdini::FullContact.max_attempts || 1
def perform(supporter)
Houdini::FullContact::InsertInfos.single(supporter)
end
end

View file

@ -1,5 +1,13 @@
module Houdini::FullContact::FullContactListener # frozen_string_literal: true
def supporter_create(supporter)
Houdini::FullContact::InsertInfos.enqueue(supporter.id) # License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
class Houdini::FullContact::FullContactListener
def name
self.class.name
end
def self.supporter_create(supporter)
FullContactJob.perform_later(supporter)
end end
end end

View file

@ -5,5 +5,5 @@ module Houdini::FullContact
autoload :InsertInfos autoload :InsertInfos
mattr_accessor :api_key mattr_accessor :api_key, :max_attempts
end end

View file

@ -7,6 +7,7 @@ module Houdini::FullContact
config.generators.api_only = true config.generators.api_only = true
config.houdini.full_contact = ActiveSupport::OrderedOptions.new config.houdini.full_contact = ActiveSupport::OrderedOptions.new
config.houdini.full_contact.max_attempts = 5
initializer 'houdini.full_contact.supporter_extension' do initializer 'houdini.full_contact.supporter_extension' do
ActiveSupport.on_load(:houdini_supporter) do ActiveSupport.on_load(:houdini_supporter) do
@ -18,6 +19,7 @@ module Houdini::FullContact
config.before_initialize do |app| config.before_initialize do |app|
Houdini::FullContact.api_key = app.config.houdini.full_contact.api_key || Houdini::FullContact.api_key = app.config.houdini.full_contact.api_key ||
ENV.fetch('FULL_CONTACT_KEY') ENV.fetch('FULL_CONTACT_KEY')
Houdini::FullContact.max_attempts = app.config.houdini.full_contact.max_attempts
end end
end end
end end

View file

@ -1,60 +1,34 @@
# frozen_string_literal: true
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
require 'qx' require 'qx'
require 'rest-client' require 'rest-client'
module Houdini::FullContact::InsertInfos module Houdini::FullContact::InsertInfos
# Work off of the full_contact_jobs queue
def self.work_queue
ids = Houdini::FullContact::Job.pluck(:supporter_id)
Houdini::FullContact::Job.delete_all
self.bulk(ids) if ids.any?
end
# Enqueue full contact jobs for a set of supporter ids
def self.enqueue(supporter_ids)
Qx.insert_into(:full_contact_jobs)
.values(supporter_ids.map{|id| {supporter_id: id}})
.ex
end
# We need to throttle our requests by 10ms since that is our rate limit on FullContact
def self.bulk(supporter_ids)
created_ids = []
supporter_ids.each do |id|
now = Time.current
result = self.single id
created_ids.push(GetData.hash(result, 'full_contact_info', 'id')) if result.is_a?(Hash)
interval = 0.1 - (Time.current - now) # account for time taken in .single
sleep interval if interval > 0
end
return created_ids
end
# Fetch and persist a single full contact record for a single supporter # Fetch and persist a single full contact record for a single supporter
# return an exception if 404 or something else went poop # return an exception if 404 or something else went poop
def self.single(supporter_id) # @param supporter Supporter
supporter = Houdini.core_classes.fetch(:supporter).constantize def self.single(supporter)
supp = supporter.find(supporter_id) return if supporter.nil? || supporter.email.blank?
return if supp.nil? || supp.email.blank?
begin begin
response = RestClient.post("https://api.fullcontact.com/v3/person.enrich", response = RestClient.post("https://api.fullcontact.com/v3/person.enrich",
{ {
"email" => supp.email, "email" => supporter.email,
}.to_json, }.to_json,
{ {
:authorization => "Bearer #{Houdini::FullContact.api_key}", :authorization => "Bearer #{Houdini::FullContact.api_key}",
"Reporting-Key" => supp.nonprofit_id "Reporting-Key" => supporter.nonprofit_id
}) })
result = JSON.parse(response.body) result = JSON.parse(response.body)
rescue Exception => e rescue RestClient::NotFound => e
return e # this means there's no information about this contact so there's nothing to do.
# We just return and end
# NOTE: We pass on other errors because that means something failed. *shrug*
return
end end
location = result['location'] && result['details']['locations'] && result['details']['locations'][0] location = result['location'] && result['details']['locations'] && result['details']['locations'][0]
existing = supp.full_contact_info existing = supporter.full_contact_infos.last
info_data = { info_data = {
full_name: result['fullName'], full_name: result['fullName'],
gender: result['gender'], gender: result['gender'],
@ -64,7 +38,13 @@ module Houdini::FullContact::InsertInfos
age_range: result['ageRange'], age_range: result['ageRange'],
location_general: result['location'], location_general: result['location'],
websites: ((result['details'] && result['details']['urls']) || []).map{|h| h['value']}.join(','), websites: ((result['details'] && result['details']['urls']) || []).map{|h| h['value']}.join(','),
supporter_id: supporter_id supporter_id: supporter.id
}
return {
'full_contact_info' => full_contact_info,
'full_contact_photos' => full_contact_photos,
'full_contact_social_profiles' => full_contact_social_profiles,
'full_contact_orgs' => full_contact_orgs
} }
if existing if existing

View file

@ -1,23 +0,0 @@
# frozen_string_literal: true
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
desc 'For generating Full Contact data'
# Clear old activerecord sessions tables daily
namespace :houdini do
namespace :full_contact do
task work_queue: :environment do
loop do
sleep(10) until Qx.select('COUNT(*)').from('full_contact_jobs').execute.first['count'] > 0
puts 'working...'
begin
Houdini::FullContact::InsertInfos.work_queue
rescue Exception => e
puts "Exception thrown: #{e}"
end
end
end
end
end

View file

@ -41,7 +41,6 @@ module InsertSupporter
# GeocodeModel.delay.supporter(supporter['id']) # GeocodeModel.delay.supporter(supporter['id'])
Houdini.event_publisher.announce(:supporter_create, Supporter.find(supporter['id'])) Houdini.event_publisher.announce(:supporter_create, Supporter.find(supporter['id']))
Houdini::FullContact::InsertInfos.enqueue([supporter['id']])
supporter supporter
end end