From 56fbb853a45217657ae5e3887840aad2915e4f1b Mon Sep 17 00:00:00 2001 From: Alex Sadleir Date: Sun, 20 Oct 2013 02:04:28 +1100 Subject: [PATCH] Adds Google Analytics Event Tracking to some API calls so that usage of the CKAN API can be reported on via Google Analytics. --- README.rst | 3 + ckanext/googleanalytics/controller.py | 81 +++++++++++++++++++++++++++ ckanext/googleanalytics/plugin.py | 53 ++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/README.rst b/README.rst index 5fb1a1e..ce6ad12 100644 --- a/README.rst +++ b/README.rst @@ -18,6 +18,9 @@ Features resource downloads will be displayed as Events in the Google Analytics reporting interface. +* Adds Google Analytics Event Tracking to some API calls so that usage of the + API can be reported on via Google Analytics. + * Adds Google Analytics Event Tracking to group links on the home page, user profile links, editing and saving user profiles, etc. diff --git a/ckanext/googleanalytics/controller.py b/ckanext/googleanalytics/controller.py index e226db8..1efceda 100644 --- a/ckanext/googleanalytics/controller.py +++ b/ckanext/googleanalytics/controller.py @@ -2,6 +2,19 @@ from ckan.lib.base import BaseController, c, render import dbutil +import urllib +from pprint import pprint +import logging +import ckan.logic as logic +import hashlib +import threading +from pylons import config + +from webob.multidict import UnicodeMultiDict +from paste.util.multidict import MultiDict + +from ckan.controllers.api import ApiController + log = logging.getLogger('ckanext.googleanalytics') class GAController(BaseController): @@ -10,3 +23,71 @@ def view(self): c.top_packages = dbutil.get_top_packages(limit=10) c.top_resources = dbutil.get_top_resources(limit=10) return render('summary.html') + +class GAApiController(ApiController): + # intercept API calls to record via google analytics + def _post_analytics(self,user,request_obj_type,request_function,request_id): + if (config.get('googleanalytics.id') != None): + data = urllib.urlencode({ + "v":1, + "tid":config.get('googleanalytics.id'), + "cid":hashlib.md5(user).hexdigest(), #customer id should be obfuscated + "t":"event", + "dh":c.environ['HTTP_HOST'], + "dp":c.environ['PATH_INFO'], + "dr":c.environ.get('HTTP_REFERER',''), + "ec":"CKAN API Request", + "ea":request_obj_type+request_function, + "el":request_id, + }) + log.debug("Sending API event to Google Analytics: "+data) + # send analytics asynchronously + threading.Thread(target=urllib.urlopen,args=("http://www.google-analytics.com/collect", data)).start() + + + def action(self, logic_function, ver=None): + try: + function = logic.get_action(logic_function) + except Exception,e: + log.debug(e) + pass + try: + side_effect_free = getattr(function, 'side_effect_free', False) + request_data = self._get_request_data(try_url_params=side_effect_free) + if isinstance(request_data, dict): + id = request_data.get('id','') + if 'q' in request_data.keys(): + id = request_data['q'] + if 'query' in request_data.keys(): + id = request_data['query'] + self._post_analytics(c.user,logic_function,'', id) + except Exception,e: + print log.debug(e) + pass + + return ApiController.action(self,logic_function, ver) + + def list(self, ver=None, register=None, subregister=None, id=None): + self._post_analytics(c.user,register+("_"+str(subregister) if subregister else ""),"list",id) + return ApiController.list(self,ver, register, subregister, id) + def show(self, ver=None, register=None, subregister=None, id=None, id2=None): + self._post_analytics(c.user,register+("_"+str(subregister) if subregister else ""),"show",id) + return ApiController.show(self,ver, register, subregister, id,id2) + def update(self, ver=None, register=None, subregister=None, id=None, id2=None): + self._post_analytics(c.user,register+("_"+str(subregister) if subregister else ""),"update",id) + return ApiController.update(self,ver, register, subregister, id,id2) + def delete(self, ver=None, register=None, subregister=None, id=None, id2=None): + self._post_analytics(c.user,register+("_"+str(subregister) if subregister else ""),"delete",id) + return ApiController.delete(self,ver, register, subregister, id,id2) + def search(self, ver=None, register=None): + id = None + try: + params = MultiDict(self._get_search_params(request.params)) + if 'q' in params.keys(): + id = params['q'] + if 'query' in params.keys(): + id = params['query'] + except ValueError, e: + print str(e) + pass + self._post_analytics(c.user,register,"search",id) diff --git a/ckanext/googleanalytics/plugin.py b/ckanext/googleanalytics/plugin.py index febcee8..bfebc54 100644 --- a/ckanext/googleanalytics/plugin.py +++ b/ckanext/googleanalytics/plugin.py @@ -8,6 +8,7 @@ import ckan.lib.helpers as h import ckan.plugins as p import gasnippet +from routes.mapper import SubMapper, Mapper as _Mapper log = logging.getLogger('ckanext.googleanalytics') @@ -67,6 +68,58 @@ def update_config(self, config): else: p.toolkit.add_template_directory(config, 'templates') + def before_map(self, map): + '''Add new routes that this extension's controllers handle. + + See IRoutes. + + ''' + # Helpers to reduce code clutter + GET = dict(method=['GET']) + PUT = dict(method=['PUT']) + POST = dict(method=['POST']) + DELETE = dict(method=['DELETE']) + GET_POST = dict(method=['GET', 'POST']) + # intercept API calls that we want to capture analytics on + register_list = [ + 'package', + 'dataset', + 'resource', + 'tag', + 'group', + 'related', + 'revision', + 'licenses', + 'rating', + 'user', + 'activity' + ] + register_list_str = '|'.join(register_list) + # /api ver 3 or none + with SubMapper(map, controller='ckanext.googleanalytics.controller:GAApiController', path_prefix='/api{ver:/3|}', + ver='/3') as m: + m.connect('/action/{logic_function}', action='action', + conditions=GET_POST) + + # /api ver 1, 2, 3 or none + with SubMapper(map, controller='ckanext.googleanalytics.controller:GAApiController', path_prefix='/api{ver:/1|/2|/3|}', + ver='/1') as m: + m.connect('/search/{register}', action='search') + + # /api/rest ver 1, 2 or none + with SubMapper(map, controller='ckanext.googleanalytics.controller:GAApiController', path_prefix='/api{ver:/1|/2|}', + ver='/1', requirements=dict(register=register_list_str) + ) as m: + + m.connect('/rest/{register}', action='list', conditions=GET) + m.connect('/rest/{register}', action='create', conditions=POST) + m.connect('/rest/{register}/{id}', action='show', conditions=GET) + m.connect('/rest/{register}/{id}', action='update', conditions=PUT) + m.connect('/rest/{register}/{id}', action='update', conditions=POST) + m.connect('/rest/{register}/{id}', action='delete', conditions=DELETE) + + return map + def after_map(self, map): '''Add new routes that this extension's controllers handle.