From: Ross Jones Date: Fri, 02 Nov 2012 16:56:44 +0000 Subject: Added support for a popular datasets widget. X-Git-Url: http://maxious.lambdacomplex.org/git/?p=ckanext-ga-report.git&a=commitdiff&h=ed19878dad30571a3d7aea278d7a2db308ba8b34 --- Added support for a popular datasets widget. The widget will look in the ga_publishers for the 30 top publishers and then randomly choose one from that list to show the top 10 datasets of all time --- --- a/ckanext/ga_report/controller.py +++ b/ckanext/ga_report/controller.py @@ -1,13 +1,15 @@ import re +import csv +import sys import logging import operator import collections -from ckan.lib.base import BaseController, c, render, request, response, abort +from ckan.lib.base import (BaseController, c, g, render, request, response, abort) import sqlalchemy from sqlalchemy import func, cast, Integer import ckan.model as model -from ga_model import GA_Url, GA_Stat +from ga_model import GA_Url, GA_Stat, GA_ReferralStat log = logging.getLogger('ckanext.ga-report') @@ -38,6 +40,7 @@ entries = q.order_by('GA_Stat.period_name, GA_Stat.stat_name, GA_Stat.key').all() response.headers['Content-Type'] = "text/csv; charset=utf-8" + response.headers['Content-Disposition'] = str('attachment; filename=stats_%s.csv' % (month,)) writer = csv.writer(response) writer.writerow(["Period", "Statistic", "Key", "Value"]) @@ -55,7 +58,7 @@ c.months = _month_details(GA_Stat) # Work out which month to show, based on query params of the first item - c.month_desc = 'all time' + c.month_desc = 'all months' c.month = request.params.get('month', '') if c.month: c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month]) @@ -67,15 +70,17 @@ entries = q.order_by('ga_stat.key').all() def clean_key(key, val): - if key in ['Average time on site', 'Pages per visit', 'Percent new visits']: + if key in ['Average time on site', 'Pages per visit', 'New visits']: val = "%.2f" % round(float(val), 2) if key == 'Average time on site': mins, secs = divmod(float(val), 60) hours, mins = divmod(mins, 60) val = '%02d:%02d:%02d (%s seconds) ' % (hours, mins, secs, val) - key = '%s *' % key - if key in ['Bounces', 'Total pageviews']: + if key == 'New visits': + val = "%s%%" % val + if key in ['Bounces', 'Total page views', 'Total visits']: val = int(val) + return key, val c.global_totals = [] @@ -88,7 +93,7 @@ for e in entries: d[e.key].append(float(e.value)) for k, v in d.iteritems(): - if k in ['Bounces', 'Total pageviews']: + if k in ['Bounces', 'Total page views', 'Total visits']: v = sum(v) else: v = float(sum(v))/len(v) @@ -97,12 +102,38 @@ c.global_totals = sorted(c.global_totals, key=operator.itemgetter(0)) keys = { - 'Browser versions': 'browsers', - 'Operating Systems versions': 'os', + 'Browser versions': 'browser_versions', + 'Browsers': 'browsers', + 'Operating Systems versions': 'os_versions', + 'Operating Systems': 'os', 'Social sources': 'social_networks', 'Languages': 'languages', 'Country': 'country' } + + def shorten_name(name, length=60): + return (name[:length] + '..') if len(name) > 60 else name + + def fill_out_url(url): + import urlparse + return urlparse.urljoin(g.site_url, url) + + c.social_referrer_totals, c.social_referrers = [], [] + q = model.Session.query(GA_ReferralStat) + q = q.filter(GA_ReferralStat.period_name==c.month) if c.month else q + q = q.order_by('ga_referrer.count::int desc') + for entry in q.all(): + c.social_referrers.append((shorten_name(entry.url), fill_out_url(entry.url), + entry.source,entry.count)) + + q = model.Session.query(GA_ReferralStat.url, + func.sum(GA_ReferralStat.count).label('count')) + q = q.filter(GA_ReferralStat.period_name==c.month) if c.month else q + q = q.order_by('count desc').group_by(GA_ReferralStat.url) + for entry in q.all(): + c.social_referrer_totals.append((shorten_name(entry[0]), fill_out_url(entry[0]),'', + entry[1])) + browser_version_re = re.compile("(.*)\((.*)\)") for k, v in keys.iteritems(): @@ -141,9 +172,17 @@ entries.append((key,val,)) entries = sorted(entries, key=operator.itemgetter(1), reverse=True) - setattr(c, v, [(k,v) for k,v in entries ]) - - + def percent(num, total): + p = 100 * float(num)/float(total) + return "%.2f%%" % round(p, 2) + + # Get the total for each set of values and then set the value as + # a percentage of the total + if k == 'Social sources': + total = sum([x for n,x in c.global_totals if n == 'Total visits']) + else: + total = sum([num for _,num in entries]) + setattr(c, v, [(k,percent(v,total)) for k,v in entries ]) return render('ga_report/site/index.html') @@ -153,6 +192,45 @@ Displays the pageview and visit count for specific publishers based on the datasets associated with the publisher. """ + def csv(self, month): + + c.month = month if not month =='all' else '' + response.headers['Content-Type'] = "text/csv; charset=utf-8" + response.headers['Content-Disposition'] = str('attachment; filename=publishers_%s.csv' % (month,)) + + writer = csv.writer(response) + writer.writerow(["Publisher", "Views", "Visits", "Period Name"]) + + for publisher,view,visit in _get_publishers(None): + writer.writerow([publisher.title.encode('utf-8'), + view, + visit, + month]) + + + + def publisher_csv(self, id, month): + + c.month = month if not month =='all' else '' + c.publisher = model.Group.get(id) + if not c.publisher: + abort(404, 'A publisher with that name could not be found') + + packages = self._get_packages(c.publisher) + response.headers['Content-Type'] = "text/csv; charset=utf-8" + response.headers['Content-Disposition'] = \ + str('attachment; filename=%s_%s.csv' % (c.publisher.name, month,)) + + writer = csv.writer(response) + writer.writerow(["Publisher", "Views", "Visits", "Period Name"]) + + for package,view,visit in packages: + writer.writerow([package.title.encode('utf-8'), + view, + visit, + month]) + + def index(self): @@ -162,68 +240,22 @@ # Work out which month to show, based on query params of the first item c.month = request.params.get('month', '') - c.month_desc = 'all time' + c.month_desc = 'all months' if c.month: c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month]) - connection = model.Session.connection() - q = """ - select department_id, sum(pageviews::int) views, sum(visitors::int) visits - from ga_url - where department_id <> ''""" - if c.month: - q = q + """ - and period_name=%s - """ - q = q + """ - group by department_id order by views desc limit 20; - """ - - # Add this back (before and period_name =%s) if you want to ignore publisher - # homepage views - # and not url like '/publisher/%%' - - c.top_publishers = [] - res = connection.execute(q, c.month) - - for row in res: - c.top_publishers.append((model.Group.get(row[0]), row[1], row[2])) + c.top_publishers = _get_publishers() return render('ga_report/publisher/index.html') - def read(self, id): - count = 20 - - c.publisher = model.Group.get(id) - if not c.publisher: - abort(404, 'A publisher with that name could not be found') - c.top_packages = [] # package, dataset_views in c.top_packages - - # Get the month details by fetching distinct values and determining the - # month names from the values. - c.months = _month_details(GA_Url) - - # Work out which month to show, based on query params of the first item - c.month = request.params.get('month', '') - if not c.month: - c.month_desc = 'all time' - else: - c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month]) - - c.publisher_page_views = 0 - q = model.Session.query(GA_Url).\ - filter(GA_Url.url=='/publisher/%s' % c.publisher.name) - if c.month: - entry = q.filter(GA_Url.period_name==c.month).first() - c.publisher_page_views = entry.pageviews if entry else 0 - else: - for e in q.all(): - c.publisher_page_views = c.publisher_page_views + int(e.pageviews) - - + def _get_packages(self, publisher, count=-1): + if count == -1: + count = sys.maxint + + top_packages = [] q = model.Session.query(GA_Url).\ - filter(GA_Url.department_id==c.publisher.name).\ + filter(GA_Url.department_id==publisher.name).\ filter(GA_Url.url.like('/dataset/%')) if c.month: q = q.filter(GA_Url.period_name==c.month) @@ -232,7 +264,7 @@ if c.month: for entry in q[:count]: p = model.Package.get(entry.url[len('/dataset/'):]) - c.top_packages.append((p,entry.pageviews,entry.visitors)) + top_packages.append((p,entry.pageviews,entry.visitors)) else: ds = {} for entry in q.all(): @@ -248,7 +280,69 @@ for k, v in ds.iteritems(): results.append((k,v['views'],v['visits'])) - c.top_packages = sorted(results, key=operator.itemgetter(1), reverse=True) + top_packages = sorted(results, key=operator.itemgetter(1), reverse=True) + return top_packages + + + def read(self, id): + count = 20 + + c.publisher = model.Group.get(id) + if not c.publisher: + abort(404, 'A publisher with that name could not be found') + c.top_packages = [] # package, dataset_views in c.top_packages + + # Get the month details by fetching distinct values and determining the + # month names from the values. + c.months = _month_details(GA_Url) + + # Work out which month to show, based on query params of the first item + c.month = request.params.get('month', '') + if not c.month: + c.month_desc = 'all months' + else: + c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month]) + + c.publisher_page_views = 0 + q = model.Session.query(GA_Url).\ + filter(GA_Url.url=='/publisher/%s' % c.publisher.name) + if c.month: + entry = q.filter(GA_Url.period_name==c.month).first() + c.publisher_page_views = entry.pageviews if entry else 0 + else: + for e in q.all(): + c.publisher_page_views = c.publisher_page_views + int(e.pageviews) + + c.top_packages = self._get_packages(c.publisher, 20) return render('ga_report/publisher/read.html') +def _get_publishers(limit=20): + connection = model.Session.connection() + q = """ + select department_id, sum(pageviews::int) views, sum(visitors::int) visits + from ga_url + where department_id <> ''""" + if c.month: + q = q + """ + and period_name=%s + """ + q = q + """ + group by department_id order by views desc + """ + if limit: + q = q + " limit %s;" % (limit) + + # Add this back (before and period_name =%s) if you want to ignore publisher + # homepage views + # and not url like '/publisher/%%' + + top_publishers = [] + res = connection.execute(q, c.month) + + for row in res: + g = model.Group.get(row[0]) + if g: + top_publishers.append((g, row[1], row[2])) + return top_publishers + --- a/ckanext/ga_report/download_analytics.py +++ b/ckanext/ga_report/download_analytics.py @@ -1,7 +1,7 @@ import os import logging import datetime - +import collections from pylons import config import ga_model @@ -94,7 +94,6 @@ self.get_full_period_name(period_name, period_complete_day), start_date.strftime('%Y %m %d'), end_date.strftime('%Y %m %d')) - data = self.download(start_date, end_date, '~/dataset/[a-z0-9-_]+') log.info('Storing Dataset Analytics for period "%s"', self.get_full_period_name(period_name, period_complete_day)) @@ -107,6 +106,33 @@ ga_model.update_publisher_stats(period_name) # about 30 seconds. self.sitewide_stats( period_name ) + + self.update_social_info(period_name, start_date, end_date) + + def update_social_info(self, period_name, start_date, end_date): + start_date = start_date.strftime('%Y-%m-%d') + end_date = end_date.strftime('%Y-%m-%d') + query = 'ga:hasSocialSourceReferral=~Yes$' + metrics = 'ga:entrances' + sort = '-ga:entrances' + + # Supported query params at + # https://developers.google.com/analytics/devguides/reporting/core/v3/reference + results = self.service.data().ga().get( + ids='ga:' + self.profile_id, + filters=query, + start_date=start_date, + metrics=metrics, + sort=sort, + dimensions="ga:landingPagePath,ga:socialNetwork", + max_results=10000, + end_date=end_date).execute() + data = collections.defaultdict(list) + rows = results.get('rows',[]) + for row in rows: + from ga_model import _normalize_url + data[_normalize_url(row[0])].append( (row[1], int(row[2]),) ) + ga_model.update_social(period_name, data) def download(self, start_date, end_date, path='~/dataset/[a-z0-9-_]+'): @@ -176,12 +202,12 @@ max_results=10000, end_date=end_date).execute() result_data = results.get('rows') - ga_model.update_sitewide_stats(period_name, "Totals", {'Total pageviews': result_data[0][0]}) - - results = self.service.data().ga().get( - ids='ga:' + self.profile_id, - start_date=start_date, - metrics='ga:pageviewsPerVisit,ga:bounces,ga:avgTimeOnSite,ga:percentNewVisits', + ga_model.update_sitewide_stats(period_name, "Totals", {'Total page views': result_data[0][0]}) + + results = self.service.data().ga().get( + ids='ga:' + self.profile_id, + start_date=start_date, + metrics='ga:pageviewsPerVisit,ga:bounces,ga:avgTimeOnSite,ga:percentNewVisits,ga:visitors', max_results=10000, end_date=end_date).execute() result_data = results.get('rows') @@ -189,7 +215,8 @@ 'Pages per visit': result_data[0][0], 'Bounces': result_data[0][1], 'Average time on site': result_data[0][2], - 'Percent new visits': result_data[0][3], + 'New visits': result_data[0][3], + 'Total visits': result_data[0][4], } ga_model.update_sitewide_stats(period_name, "Totals", data) --- a/ckanext/ga_report/ga_model.py +++ b/ckanext/ga_report/ga_model.py @@ -13,6 +13,8 @@ def make_uuid(): return unicode(uuid.uuid4()) +metadata = MetaData() + class GA_Url(object): @@ -21,20 +23,6 @@ for k,v in kwargs.items(): setattr(self, k, v) -class GA_Stat(object): - - def __init__(self, **kwargs): - for k,v in kwargs.items(): - setattr(self, k, v) - -class GA_Publisher(object): - - def __init__(self, **kwargs): - for k,v in kwargs.items(): - setattr(self, k, v) - - -metadata = MetaData() url_table = Table('ga_url', metadata, Column('id', types.UnicodeText, primary_key=True, default=make_uuid), @@ -47,6 +35,13 @@ ) mapper(GA_Url, url_table) + +class GA_Stat(object): + + def __init__(self, **kwargs): + for k,v in kwargs.items(): + setattr(self, k, v) + stat_table = Table('ga_stat', metadata, Column('id', types.UnicodeText, primary_key=True, default=make_uuid), @@ -56,6 +51,12 @@ Column('value', types.UnicodeText), ) mapper(GA_Stat, stat_table) + +class GA_Publisher(object): + + def __init__(self, **kwargs): + for k,v in kwargs.items(): + setattr(self, k, v) pub_table = Table('ga_publisher', metadata, Column('id', types.UnicodeText, primary_key=True, @@ -71,6 +72,24 @@ mapper(GA_Publisher, pub_table) +class GA_ReferralStat(object): + + def __init__(self, **kwargs): + for k,v in kwargs.items(): + setattr(self, k, v) + +referrer_table = Table('ga_referrer', metadata, + Column('id', types.UnicodeText, primary_key=True, + default=make_uuid), + Column('period_name', types.UnicodeText), + Column('source', types.UnicodeText), + Column('url', types.UnicodeText), + Column('count', types.Integer), + ) +mapper(GA_ReferralStat, referrer_table) + + + def init_tables(): metadata.create_all(model.meta.engine) @@ -93,8 +112,9 @@ >>> normalize_url('http://data.gov.uk/dataset/weekly_fuel_prices') '/dataset/weekly_fuel_prices' ''' - url = re.sub('https?://(www\.)?data.gov.uk', '', url) - return url + # Deliberately leaving a / + url = url.replace('http:/','') + return '/' + '/'.join(url.split('/')[2:]) def _get_department_id_of_url(url): @@ -167,6 +187,33 @@ model.Session.commit() +def update_social(period_name, data): + # Clean up first. + model.Session.query(GA_ReferralStat).\ + filter(GA_ReferralStat.period_name==period_name).delete() + + for url,data in data.iteritems(): + for entry in data: + source = entry[0] + count = entry[1] + + item = model.Session.query(GA_ReferralStat).\ + filter(GA_ReferralStat.period_name==period_name).\ + filter(GA_ReferralStat.source==source).\ + filter(GA_ReferralStat.url==url).first() + if item: + item.count = item.count + count + model.Session.add(item) + else: + # create the row + values = {'id': make_uuid(), + 'period_name': period_name, + 'source': source, + 'url': url, + 'count': count, + } + model.Session.add(GA_ReferralStat(**values)) + model.Session.commit() def update_publisher_stats(period_name): """ --- a/ckanext/ga_report/helpers.py +++ b/ckanext/ga_report/helpers.py @@ -3,15 +3,49 @@ import ckan.lib.base as base import ckan.model as model +from ckanext.ga_report.ga_model import GA_Url, GA_Publisher +from ckanext.ga_report.controller import _get_publishers _log = logging.getLogger(__name__) +def popular_datasets(count=10): + import random + + publisher = None + publishers = _get_publishers(30) + total = len(publishers) + while not publisher or not datasets: + rand = random.randrange(0, total) + publisher = publishers[rand][0] + if not publisher.state == 'active': + publisher = None + continue + datasets = _datasets_for_publisher(publisher, 10)[:count] + + ctx = { + 'datasets': datasets, + 'publisher': publisher + } + return base.render_snippet('ga_report/ga_popular_datasets.html', **ctx) + + def most_popular_datasets(publisher, count=20): - from ckanext.ga_report.ga_model import GA_Url if not publisher: _log.error("No valid publisher passed to 'most_popular_datasets'") return "" + results = _datasets_for_publisher(publisher, count) + + ctx = { + 'dataset_count': len(datasets), + 'datasets': results, + + 'publisher': publisher + } + + return base.render_snippet('ga_report/publisher/popular.html', **ctx) + +def _datasets_for_publisher(publisher, count): datasets = {} entries = model.Session.query(GA_Url).\ filter(GA_Url.department_id==publisher.name).\ @@ -29,14 +63,5 @@ for k, v in datasets.iteritems(): results.append((k,v['views'],v['visits'])) - results = sorted(results, key=operator.itemgetter(1), reverse=True) + return sorted(results, key=operator.itemgetter(1), reverse=True) - ctx = { - 'dataset_count': len(datasets), - 'datasets': results, - - 'publisher': publisher - } - - return base.render_snippet('ga_report/publisher/popular.html', **ctx) - --- a/ckanext/ga_report/plugin.py +++ b/ckanext/ga_report/plugin.py @@ -2,6 +2,9 @@ import ckan.lib.helpers as h import ckan.plugins as p from ckan.plugins import implements, toolkit + +from ckanext.ga_report.helpers import (most_popular_datasets, + popular_datasets) log = logging.getLogger('ckanext.ga-report') @@ -19,30 +22,40 @@ A dictionary of extra helpers that will be available to provide ga report info to templates. """ - from ckanext.ga_report.helpers import most_popular_datasets return { 'ga_report_installed': lambda: True, + 'popular_datasets': popular_datasets, 'most_popular_datasets': most_popular_datasets, } def after_map(self, map): map.connect( - '/data/analytics/publisher', + '/data/site-usage/publisher', controller='ckanext.ga_report.controller:GaPublisherReport', action='index' ) map.connect( - '/data/analytics/publisher/{id}', + '/data/site-usage/publisher_{month}.csv', + controller='ckanext.ga_report.controller:GaPublisherReport', + action='csv' + ) + map.connect( + '/data/site-usage/publisher/{id}_{month}.csv', + controller='ckanext.ga_report.controller:GaPublisherReport', + action='publisher_csv' + ) + map.connect( + '/data/site-usage/publisher/{id}', controller='ckanext.ga_report.controller:GaPublisherReport', action='read' ) map.connect( - '/data/analytics', + '/data/site-usage', controller='ckanext.ga_report.controller:GaReport', action='index' ) map.connect( - '/data/analytics/data_{month}.csv', + '/data/site-usage/data_{month}.csv', controller='ckanext.ga_report.controller:GaReport', action='csv' ) --- a/ckanext/ga_report/report_model.py +++ /dev/null --- /dev/null +++ b/ckanext/ga_report/templates/ga_report/ga_popular_datasets.html @@ -1,1 +1,27 @@ + + + + + + + + --- a/ckanext/ga_report/templates/ga_report/ga_util.html +++ b/ckanext/ga_report/templates/ga_report/ga_util.html @@ -6,12 +6,33 @@ py:strip="" > - - - - - - +
${title}
+ + + + + + + + + + + + +
NameSourceVisits
${name}${source}${count}
+ + + + + + + + + + + + +
Name% ${title}
${name}${value}
@@ -19,12 +40,12 @@
--- a/ckanext/ga_report/templates/ga_report/publisher/index.html +++ b/ckanext/ga_report/templates/ga_report/publisher/index.html @@ -20,13 +20,14 @@
  • Download

    - Download as CSV
    + Download as CSV

  • +

    Site Usage

    ${usage_nav('Publishers', None)} @@ -35,7 +36,7 @@
    - + --- a/ckanext/ga_report/templates/ga_report/site/index.html +++ b/ckanext/ga_report/templates/ga_report/site/index.html @@ -25,13 +25,12 @@

    Site Usage

    - ${usage_nav('Site-wide', None)}