184 lines
5.4 KiB
Python
184 lines
5.4 KiB
Python
|
import logging
|
||
|
|
||
|
from hashlib import sha1
|
||
|
|
||
|
from concurrent.futures import ThreadPoolExecutor
|
||
|
from marketorestpython.client import MarketoClient
|
||
|
|
||
|
from util.asyncwrapper import AsyncExecutorWrapper, NullExecutor, NullExecutorCancelled
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
class LeadNotFoundException(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
|
def build_error_callback(message_when_exception):
|
||
|
def maybe_log_error(response_future):
|
||
|
try:
|
||
|
response_future.result()
|
||
|
except NullExecutorCancelled:
|
||
|
pass
|
||
|
except Exception:
|
||
|
logger.exception('User analytics: %s', message_when_exception)
|
||
|
|
||
|
return maybe_log_error
|
||
|
|
||
|
|
||
|
class _MarketoAnalyticsClient(object):
|
||
|
""" User analytics implementation which will report user changes to the
|
||
|
Marketo API.
|
||
|
"""
|
||
|
def __init__(self, marketo_client, munchkin_private_key, lead_source):
|
||
|
""" Instantiate with the given marketorestpython.client, the Marketo
|
||
|
Munchkin Private Key, and the Lead Source that we want to set when we
|
||
|
create new lead records in Marketo.
|
||
|
"""
|
||
|
self._marketo = marketo_client
|
||
|
self._munchkin_private_key = munchkin_private_key
|
||
|
self._lead_source = lead_source
|
||
|
|
||
|
def _get_lead_metadata(self, given_name, family_name, company, location):
|
||
|
metadata = {}
|
||
|
if given_name:
|
||
|
metadata['firstName'] = given_name
|
||
|
|
||
|
if family_name:
|
||
|
metadata['lastName'] = family_name
|
||
|
|
||
|
if company:
|
||
|
metadata['company'] = company
|
||
|
|
||
|
if location:
|
||
|
metadata['location'] = location
|
||
|
|
||
|
return metadata
|
||
|
|
||
|
def create_lead(self, email, username, given_name, family_name, company, location):
|
||
|
lead_data = dict(
|
||
|
email=email,
|
||
|
Quay_Username__c=username,
|
||
|
leadSource='Web - Product Trial',
|
||
|
Lead_Source_Detail__c=self._lead_source,
|
||
|
)
|
||
|
|
||
|
lead_data.update(self._get_lead_metadata(given_name, family_name,
|
||
|
company, location))
|
||
|
|
||
|
self._marketo.create_update_leads(
|
||
|
action='createOrUpdate',
|
||
|
leads=[lead_data],
|
||
|
asyncProcessing=True,
|
||
|
lookupField='email',
|
||
|
)
|
||
|
|
||
|
def _find_leads_by_email(self, email):
|
||
|
# Fetch the existing user from the database by email
|
||
|
found = self._marketo.get_multiple_leads_by_filter_type(
|
||
|
filterType='email',
|
||
|
filterValues=[email],
|
||
|
)
|
||
|
|
||
|
if not found:
|
||
|
raise LeadNotFoundException('No lead found with email: {}'.format(email))
|
||
|
|
||
|
return found
|
||
|
|
||
|
def change_email(self, old_email, new_email):
|
||
|
found = self._find_leads_by_email(old_email)
|
||
|
|
||
|
# Update using their user id.
|
||
|
updated = [dict(id=lead['id'], email=new_email) for lead in found]
|
||
|
self._marketo.create_update_leads(
|
||
|
action='updateOnly',
|
||
|
leads=updated,
|
||
|
asyncProcessing=True,
|
||
|
lookupField='id',
|
||
|
)
|
||
|
|
||
|
def change_metadata(self, email, given_name=None, family_name=None, company=None, location=None):
|
||
|
lead_data = self._get_lead_metadata(given_name, family_name, company, location)
|
||
|
if not lead_data:
|
||
|
return
|
||
|
|
||
|
# Update using their email address.
|
||
|
lead_data['email'] = email
|
||
|
self._marketo.create_update_leads(
|
||
|
action='updateOnly',
|
||
|
leads=[lead_data],
|
||
|
asyncProcessing=True,
|
||
|
lookupField='email',
|
||
|
)
|
||
|
|
||
|
def change_username(self, email, new_username):
|
||
|
# Update using their email.
|
||
|
self._marketo.create_update_leads(
|
||
|
action='updateOnly',
|
||
|
leads=[{
|
||
|
'email': email,
|
||
|
'Quay_Username__c': new_username,
|
||
|
}],
|
||
|
asyncProcessing=True,
|
||
|
lookupField='email',
|
||
|
)
|
||
|
|
||
|
@AsyncExecutorWrapper.sync
|
||
|
def get_user_analytics_metadata(self, user_obj):
|
||
|
""" Return a list of properties that should be added to the user object to allow
|
||
|
analytics associations.
|
||
|
"""
|
||
|
if not self._munchkin_private_key:
|
||
|
return dict()
|
||
|
|
||
|
marketo_user_hash = sha1(self._munchkin_private_key)
|
||
|
marketo_user_hash.update(user_obj.email)
|
||
|
|
||
|
return dict(
|
||
|
marketo_user_hash=marketo_user_hash.hexdigest(),
|
||
|
)
|
||
|
|
||
|
|
||
|
class UserAnalytics(object):
|
||
|
def __init__(self, app=None):
|
||
|
self.app = app
|
||
|
if app is not None:
|
||
|
self.state = self.init_app(app)
|
||
|
else:
|
||
|
self.state = None
|
||
|
|
||
|
def init_app(self, app):
|
||
|
analytics_type = app.config.get('USER_ANALYTICS_TYPE', 'FakeAnalytics')
|
||
|
|
||
|
marketo_munchkin_id = ''
|
||
|
marketo_munchkin_private_key = ''
|
||
|
marketo_client_id = ''
|
||
|
marketo_client_secret = ''
|
||
|
marketo_lead_source = ''
|
||
|
executor = NullExecutor()
|
||
|
|
||
|
if analytics_type == 'Marketo':
|
||
|
marketo_munchkin_id = app.config['MARKETO_MUNCHKIN_ID']
|
||
|
marketo_munchkin_private_key = app.config['MARKETO_MUNCHKIN_PRIVATE_KEY']
|
||
|
marketo_client_id = app.config['MARKETO_CLIENT_ID']
|
||
|
marketo_client_secret = app.config['MARKETO_CLIENT_SECRET']
|
||
|
marketo_lead_source = app.config['MARKETO_LEAD_SOURCE']
|
||
|
|
||
|
logger.debug('Initializing marketo with keys: %s %s %s', marketo_munchkin_id,
|
||
|
marketo_client_id, marketo_client_secret)
|
||
|
|
||
|
executor = ThreadPoolExecutor(max_workers=1)
|
||
|
|
||
|
marketo_client = MarketoClient(marketo_munchkin_id, marketo_client_id, marketo_client_secret)
|
||
|
client_wrapper = _MarketoAnalyticsClient(marketo_client, marketo_munchkin_private_key,
|
||
|
marketo_lead_source)
|
||
|
user_analytics = AsyncExecutorWrapper(client_wrapper, executor)
|
||
|
|
||
|
# register extension with app
|
||
|
app.extensions = getattr(app, 'extensions', {})
|
||
|
app.extensions['user_analytics'] = user_analytics
|
||
|
return user_analytics
|
||
|
|
||
|
def __getattr__(self, name):
|
||
|
return getattr(self.state, name, None)
|