This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/util/saas/useranalytics.py
Brad Ison 73cb7f3228
endpoints/api: Allow null fields in user metadata
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.
2018-06-27 15:34:55 -04:00

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)