73cb7f3228
The user metadata fields are nullable in the database, but were not in the json sechema. This prevented users from updating some of their information on the site if they hadn't set the metadata fields.
183 lines
5.4 KiB
Python
183 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)
|