Skip to content

Commit

Permalink
Adds Google Analytics Event Tracking to some API calls so that usage
Browse files Browse the repository at this point in the history
of the CKAN API can be reported on via Google Analytics.
  • Loading branch information
maxious committed Nov 26, 2013
1 parent 6d7aa55 commit 56fbb85
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.rst
Expand Up @@ -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.

Expand Down
81 changes: 81 additions & 0 deletions ckanext/googleanalytics/controller.py
Expand Up @@ -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):
Expand All @@ -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)
53 changes: 53 additions & 0 deletions ckanext/googleanalytics/plugin.py
Expand Up @@ -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')

Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 56fbb85

Please sign in to comment.