Show top datasets cross-publisher. Drop-down for the publisher. Browser version numbers filtered on download, so you get this version in the CSV too - for privacy. single_popular_dataset now copes when not much data, and can return the figures so DGU can reskin it in its own repo. Notes about usage stats centralised to notes.html.
Show top datasets cross-publisher. Drop-down for the publisher. Browser version numbers filtered on download, so you get this version in the CSV too - for privacy. single_popular_dataset now copes when not much data, and can return the figures so DGU can reskin it in its own repo. Notes about usage stats centralised to notes.html.

file:a/README.rst -> file:b/README.rst
--- a/README.rst
+++ b/README.rst
@@ -31,14 +31,8 @@
 2. Ensure you development.ini (or similar) contains the info about your Google Analytics account and configuration::
 
       googleanalytics.id = UA-1010101-1
-      googleanalytics.account = Account name (i.e. data.gov.uk, see top level item at https://www.google.com/analytics)
+      googleanalytics.account = Account name (e.g. data.gov.uk, see top level item at https://www.google.com/analytics)
       ga-report.period = monthly
-      ga-report.bounce_url = /data
-
-   The ga-report.bounce_url specifies the path to use when calculating bounces. For DGU this is /data
-   but you may want to set this to /.
-
-   Note that your credentials will be readable by system administrators on your server. Rather than use sensitive account details, it is suggested you give access to the GA account to a new Google account that you create just for this purpose.
 
 3. Set up this extension's database tables using a paster command. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file)::
 
@@ -47,6 +41,12 @@
 4. Enable the extension in your CKAN config file by adding it to ``ckan.plugins``::
 
     ckan.plugins = ga-report
+
+Problem shooting
+----------------
+
+* ``(ProgrammingError) relation "ga_url" does not exist``
+  This means that the ``paster initdb`` step has not been run successfully. Refer to the installation instructions for this extension.
 
 
 Authorization
@@ -83,7 +83,7 @@
 Tutorial
 --------
 
-Download some GA data and store it in CKAN's db. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file) and specifying the name of your auth file (token.dat by default) from the previous step::
+Download some GA data and store it in CKAN's database. (Ensure your CKAN pyenv is still activated, run the command from ``src/ckanext-ga-report``, alter the ``--config`` option to point to your site config file) and specifying the name of your auth file (token.dat by default) from the previous step::
 
     $ paster loadanalytics token.dat latest --config=../ckan/development.ini
 

--- a/ckanext/ga_report/command.py
+++ b/ckanext/ga_report/command.py
@@ -73,6 +73,14 @@
     max_args = 2
     min_args = 1
 
+    def __init__(self, name):
+        super(LoadAnalytics, self).__init__(name)
+        self.parser.add_option('-d', '--delete-first',
+                               action='store_true',
+                               default=False,
+                               dest='delete_first',
+                               help='Delete data for the period first')
+
     def command(self):
         self._load_config()
 
@@ -83,10 +91,11 @@
             svc = init_service(self.args[0], None)
         except TypeError:
             print ('Have you correctly run the getauthtoken task and '
-                   'specified the correct file here')
+                   'specified the correct token file?')
             return
 
-        downloader = DownloadAnalytics(svc, profile_id=get_profile_id(svc))
+        downloader = DownloadAnalytics(svc, profile_id=get_profile_id(svc),
+                                       delete_first=self.options.delete_first)
 
         time_period = self.args[1] if self.args and len(self.args) > 1 \
             else 'latest'

--- a/ckanext/ga_report/controller.py
+++ b/ckanext/ga_report/controller.py
@@ -70,15 +70,15 @@
         entries = q.order_by('ga_stat.key').all()
 
         def clean_key(key, val):
-            if key in ['Average time on site', 'Pages per visit', 'New visits', 'Bounces']:
+            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)
-                if key in ['New visits','Bounces']:
+                if key == 'New visits':
                     val = "%s%%" % val
-            if key in ['Total page views', 'Total visits']:
+            if key in ['Bounces', 'Total page views', 'Total visits']:
                 val = int(val)
 
             return key, val
@@ -93,12 +93,11 @@
             for e in entries:
                 d[e.key].append(float(e.value))
             for k, v in d.iteritems():
-                if k in ['Total page views', 'Total visits']:
+                if k in ['Bounces', 'Total page views', 'Total visits']:
                     v = sum(v)
                 else:
                     v = float(sum(v))/len(v)
                 key, val = clean_key(k,v)
-
                 c.global_totals.append((key, val))
                 c.global_totals = sorted(c.global_totals, key=operator.itemgetter(0))
 
@@ -167,11 +166,15 @@
 
             d = collections.defaultdict(int)
             for e in q.all():
-                d[clean_field(e.key)] += int(e.value)
+                d[e.key] += int(e.value)
             entries = []
             for key, val in d.iteritems():
                 entries.append((key,val,))
             entries = sorted(entries, key=operator.itemgetter(1), reverse=True)
+
+            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
@@ -179,57 +182,65 @@
                 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 ])
+            setattr(c, v, [(k,percent(v,total)) for k,v in entries ])
 
         return render('ga_report/site/index.html')
 
 
-class GaPublisherReport(BaseController):
+class GaDatasetReport(BaseController):
     """
-    Displays the pageview and visit count for specific publishers based on
-    the datasets associated with the publisher.
+    Displays the pageview and visit count for datasets
+    with options to filter by publisher and time period.
     """
-    def csv(self, month):
-
-        c.month = month if not month =='all' else ''
+    def publisher_csv(self, month):
+        '''
+        Returns a CSV of each publisher with the total number of dataset
+        views & visits.
+        '''
+        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", "Publisher Name", "Views", "Visits", "Period Name"])
+
+        for publisher,view,visit in _get_top_publishers(None):
             writer.writerow([publisher.title.encode('utf-8'),
+                             publisher.name.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')
+    def dataset_csv(self, id='all', month='all'):
+        '''
+        Returns a CSV with the number of views & visits for each dataset.
+
+        :param id: A Publisher ID or None if you want for all
+        :param month: The time period, or 'all'
+        '''
+        c.month = month if not month == 'all' else ''
+        if id != 'all':
+            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,))
+            str('attachment; filename=datasets_%s_%s.csv' % (c.publisher_name, month,))
 
         writer = csv.writer(response)
-        writer.writerow(["Publisher", "Views", "Visits", "Period Name"])
+        writer.writerow(["Dataset Title", "Dataset Name", "Views", "Visits", "Period Name"])
 
         for package,view,visit in packages:
             writer.writerow([package.title.encode('utf-8'),
+                             package.name.encode('utf-8'),
                              view,
                              visit,
                              month])
 
-
-
-    def index(self):
+    def publishers(self):
+        '''A list of publishers and the number of views/visits for each'''
 
         # Get the month details by fetching distinct values and determining the
         # month names from the values.
@@ -241,37 +252,46 @@
         if c.month:
             c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month])
 
-        c.top_publishers = _get_publishers()
+        c.top_publishers = _get_top_publishers()
 
         return render('ga_report/publisher/index.html')
 
-
-    def _get_packages(self, publisher, count=-1):
+    def _get_packages(self, publisher=None, count=-1):
+        '''Returns the datasets in order of visits'''
         if count == -1:
             count = sys.maxint
 
-        top_packages = []
-        q =  model.Session.query(GA_Url).\
-            filter(GA_Url.department_id==publisher.name).\
-            filter(GA_Url.url.like('/dataset/%'))
+        q = model.Session.query(GA_Url)\
+            .filter(GA_Url.url.like('/dataset/%'))
+        if publisher:
+            q = q.filter(GA_Url.department_id==publisher.name)
         if c.month:
             q = q.filter(GA_Url.period_name==c.month)
-        q = q.order_by('ga_url.pageviews::int desc')
-
-        if c.month:
-            for entry in q[:count]:
-                p = model.Package.get(entry.url[len('/dataset/'):])
-                top_packages.append((p,entry.pageviews,entry.visitors))
+        q = q.order_by('ga_url.visitors::int desc')
+
+        if c.month:
+            top_packages = []
+            for entry in q.limit(count):
+                package_name = entry.url[len('/dataset/'):]
+                p = model.Package.get(package_name)
+                if p:
+                    top_packages.append((p, entry.pageviews, entry.visitors))
+                else:
+                    log.warning('Could not find package "%s"', package_name)
         else:
             ds = {}
-            for entry in q.all():
+            for entry in q:
                 if len(ds) >= count:
                     break
-                p = model.Package.get(entry.url[len('/dataset/'):])
-                if not p in ds:
-                    ds[p] = {'views':0, 'visits': 0}
-                ds[p]['views'] = ds[p]['views'] + int(entry.pageviews)
-                ds[p]['visits'] = ds[p]['visits'] + int(entry.visitors)
+                package_name = entry.url[len('/dataset/'):]
+                p = model.Package.get(package_name)
+                if p:
+                    if not p in ds:
+                        ds[p] = {'views': 0, 'visits': 0}
+                    ds[p]['views'] = ds[p]['views'] + int(entry.pageviews)
+                    ds[p]['visits'] = ds[p]['visits'] + int(entry.visitors)
+                else:
+                    log.warning('Could not find package "%s"', package_name)
 
             results = []
             for k, v in ds.iteritems():
@@ -280,13 +300,26 @@
             top_packages = sorted(results, key=operator.itemgetter(1), reverse=True)
         return top_packages
 
-
-    def read(self, id):
+    def read(self):
+        '''
+        Lists the most popular datasets across all publishers
+        '''
+        return self.read_publisher(None)
+
+    def read_publisher(self, id):
+        '''
+        Lists the most popular datasets for a publisher (or across all publishers)
+        '''
         count = 20
 
-        c.publisher = model.Group.get(id)
-        if not c.publisher:
-            abort(404, 'A publisher with that name could not be found')
+        c.publishers = _get_publishers()
+
+        id = request.params.get('publisher', id)
+        if id and id != 'all':
+            c.publisher = model.Group.get(id)
+            if not c.publisher:
+                abort(404, 'A publisher with that name could not be found')
+            c.publisher_name = c.publisher.name
         c.top_packages = [] # package, dataset_views in c.top_packages
 
         # Get the month details by fetching distinct values and determining the
@@ -302,7 +335,7 @@
 
         c.publisher_page_views = 0
         q = model.Session.query(GA_Url).\
-            filter(GA_Url.url=='/publisher/%s' % c.publisher.name)
+            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
@@ -314,7 +347,11 @@
 
         return render('ga_report/publisher/read.html')
 
-def _get_publishers(limit=20):
+def _get_top_publishers(limit=20):
+    '''
+    Returns a list of the top 20 publishers by dataset visits.
+    (The number to show can be varied with 'limit')
+    '''
     connection = model.Session.connection()
     q = """
         select department_id, sum(pageviews::int) views, sum(visitors::int) visits
@@ -325,7 +362,7 @@
                 and period_name=%s
         """
     q = q + """
-            group by department_id order by views desc
+            group by department_id order by visits desc
         """
     if limit:
         q = q + " limit %s;" % (limit)
@@ -343,8 +380,16 @@
             top_publishers.append((g, row[1], row[2]))
     return top_publishers
 
-
-def _percent(num, total):
-    p = 100 * float(num)/float(total)
-    return "%.2f%%" % round(p, 2)
-
+def _get_publishers():
+    '''
+    Returns a list of all publishers. Each item is a tuple:
+      (names, title)
+    '''
+    publishers = []
+    for pub in model.Session.query(model.Group).\
+               filter(model.Group.type=='publisher').\
+               filter(model.Group.state=='active').\
+               order_by(model.Group.name):
+        publishers.append((pub.name, pub.title))
+    return publishers
+

--- a/ckanext/ga_report/download_analytics.py
+++ b/ckanext/ga_report/download_analytics.py
@@ -11,15 +11,17 @@
 log = logging.getLogger('ckanext.ga-report')
 
 FORMAT_MONTH = '%Y-%m'
+MIN_VIEWS = 50
+MIN_VISITS = 20
 
 class DownloadAnalytics(object):
     '''Downloads and stores analytics info'''
 
-    def __init__(self, service=None, profile_id=None):
+    def __init__(self, service=None, profile_id=None, delete_first=False):
         self.period = config['ga-report.period']
         self.service = service
         self.profile_id = profile_id
-
+        self.delete_first = delete_first
 
     def specific_month(self, date):
         import calendar
@@ -90,11 +92,14 @@
 
     def download_and_store(self, periods):
         for period_name, period_complete_day, start_date, end_date in periods:
+            if self.delete_first:
+                log.info('Deleting existing Analytics for period "%s"',
+                         period_name)
+                ga_model.delete(period_name)
             log.info('Downloading Analytics for period "%s" (%s - %s)',
                      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))
@@ -156,6 +161,11 @@
                                  max_results=10000,
                                  end_date=end_date).execute()
 
+        if os.getenv('DEBUG'):
+            import pprint
+            pprint.pprint(results)
+            print 'Total results: %s' % results.get('totalResults')
+
         packages = []
         for entry in results.get('rows'):
             (loc,pageviews,visits) = entry
@@ -203,38 +213,19 @@
         results = self.service.data().ga().get(
                                  ids='ga:' + self.profile_id,
                                  start_date=start_date,
-                                 metrics='ga:pageviewsPerVisit,ga:avgTimeOnSite,ga:percentNewVisits,ga:visitors',
+                                 metrics='ga:pageviewsPerVisit,ga:bounces,ga:avgTimeOnSite,ga:percentNewVisits,ga:visitors',
                                  max_results=10000,
                                  end_date=end_date).execute()
         result_data = results.get('rows')
         data = {
             'Pages per visit': result_data[0][0],
-            'Average time on site': result_data[0][1],
-            'New visits': result_data[0][2],
-            'Total visits': result_data[0][3],
+            'Bounces': result_data[0][1],
+            'Average time on site': result_data[0][2],
+            'New visits': result_data[0][3],
+            'Total visits': result_data[0][4],
         }
         ga_model.update_sitewide_stats(period_name, "Totals", data)
 
-        # Bounces from /data. This url is specified in configuration because
-        # for DGU we don't want /.
-        path = config.get('ga-report.bounce_url','/')
-        print path
-        results = self.service.data().ga().get(
-                                 ids='ga:' + self.profile_id,
-                                 filters='ga:pagePath=~%s$' % (path,),
-                                 start_date=start_date,
-                                 metrics='ga:bounces,ga:uniquePageviews',
-                                 dimensions='ga:pagePath',
-                                 max_results=10000,
-                                 end_date=end_date).execute()
-        result_data = results.get('rows')
-        for results in result_data:
-            if results[0] == path:
-                bounce, total = [float(x) for x in results[1:]]
-                pct = 100 * bounce/total
-                print "%d bounces from %d total == %s" % (bounce, total, pct)
-                ga_model.update_sitewide_stats(period_name, "Totals", {'Bounces': pct})
-
 
     def _locale_stats(self, start_date, end_date, period_name):
         """ Fetches stats about language and country """
@@ -250,11 +241,13 @@
         data = {}
         for result in result_data:
             data[result[0]] = data.get(result[0], 0) + int(result[2])
+        self._filter_out_long_tail(data, MIN_VIEWS)
         ga_model.update_sitewide_stats(period_name, "Languages", data)
 
         data = {}
         for result in result_data:
             data[result[1]] = data.get(result[1], 0) + int(result[2])
+        self._filter_out_long_tail(data, MIN_VIEWS)
         ga_model.update_sitewide_stats(period_name, "Country", data)
 
 
@@ -269,13 +262,11 @@
                                  max_results=10000,
                                  end_date=end_date).execute()
         result_data = results.get('rows')
-        twitter_links = []
         data = {}
         for result in result_data:
             if not result[0] == '(not set)':
                 data[result[0]] = data.get(result[0], 0) + int(result[2])
-                if result[0] == 'Twitter':
-                    twitter_links.append(result[1])
+        self._filter_out_long_tail(data, 3)
         ga_model.update_sitewide_stats(period_name, "Social sources", data)
 
 
@@ -293,12 +284,14 @@
         data = {}
         for result in result_data:
             data[result[0]] = data.get(result[0], 0) + int(result[2])
+        self._filter_out_long_tail(data, MIN_VIEWS)
         ga_model.update_sitewide_stats(period_name, "Operating Systems", data)
 
         data = {}
         for result in result_data:
-            key = "%s (%s)" % (result[0],result[1])
-            data[key] = result[2]
+            if int(result[2]) >= MIN_VIEWS:
+                key = "%s %s" % (result[0],result[1])
+                data[key] = result[2]
         ga_model.update_sitewide_stats(period_name, "Operating Systems versions", data)
 
 
@@ -313,17 +306,42 @@
                                  max_results=10000,
                                  end_date=end_date).execute()
         result_data = results.get('rows')
+        # e.g. [u'Firefox', u'19.0', u'20']
+
         data = {}
         for result in result_data:
             data[result[0]] = data.get(result[0], 0) + int(result[2])
+        self._filter_out_long_tail(data, MIN_VIEWS)
         ga_model.update_sitewide_stats(period_name, "Browsers", data)
 
         data = {}
         for result in result_data:
-            key = "%s (%s)" % (result[0], result[1])
-            data[key] = result[2]
+            key = "%s %s" % (result[0], self._filter_browser_version(result[0], result[1]))
+            data[key] = data.get(key, 0) + int(result[2])
+        self._filter_out_long_tail(data, MIN_VIEWS)
         ga_model.update_sitewide_stats(period_name, "Browser versions", data)
 
+    @classmethod
+    def _filter_browser_version(cls, browser, version_str):
+        '''
+        Simplifies a browser version string if it is detailed.
+        i.e. groups together Firefox 3.5.1 and 3.5.2 to be just 3.
+        This is helpful when viewing stats and good to protect privacy.
+        '''
+        ver = version_str
+        parts = ver.split('.')
+        if len(parts) > 1:
+            if parts[1][0] == '0':
+                ver = parts[0]
+            else:
+                ver = "%s" % (parts[0])
+        # Special case complex version nums
+        if browser in ['Safari', 'Android Browser']:
+            ver = parts[0]
+            if len(ver) > 2:
+                num_hidden_digits = len(ver) - 2
+                ver = ver[0] + ver[1] + 'X' * num_hidden_digits
+        return ver
 
     def _mobile_stats(self, start_date, end_date, period_name):
         """ Info about mobile devices """
@@ -341,10 +359,23 @@
         data = {}
         for result in result_data:
             data[result[0]] = data.get(result[0], 0) + int(result[2])
+        self._filter_out_long_tail(data, MIN_VIEWS)
         ga_model.update_sitewide_stats(period_name, "Mobile brands", data)
 
         data = {}
         for result in result_data:
             data[result[1]] = data.get(result[1], 0) + int(result[2])
+        self._filter_out_long_tail(data, MIN_VIEWS)
         ga_model.update_sitewide_stats(period_name, "Mobile devices", data)
 
+    @classmethod
+    def _filter_out_long_tail(cls, data, threshold=10):
+        '''
+        Given data which is a frequency distribution, filter out
+        results which are below a threshold count. This is good to protect
+        privacy.
+        '''
+        for key, value in data.items():
+            if value < threshold:
+                del data[key]
+

--- a/ckanext/ga_report/ga_auth.py
+++ b/ckanext/ga_report/ga_auth.py
@@ -53,7 +53,11 @@
         return None
 
     accountName = config.get('googleanalytics.account')
+    if not accountName:
+        raise Exception('googleanalytics.account needs to be configured')
     webPropertyId = config.get('googleanalytics.id')
+    if not webPropertyId:
+        raise Exception('googleanalytics.id needs to be configured')
     for acc in accounts.get('items'):
         if acc.get('name') == accountName:
             accountId = acc.get('id')

--- a/ckanext/ga_report/ga_model.py
+++ b/ckanext/ga_report/ga_model.py
@@ -295,3 +295,15 @@
         for grandchild in go_down_tree(child):
             yield grandchild
 
+def delete(period_name):
+    '''
+    Deletes table data for the specified period, or specify 'all'
+    for all periods.
+    '''
+    for object_type in (GA_Url, GA_Stat, GA_Publisher, GA_ReferralStat):
+        q = model.Session.query(object_type)
+        if period_name != 'all':
+            q = q.filter_by(period_name=period_name)
+        q.delete()
+    model.Session.commit()
+

--- a/ckanext/ga_report/helpers.py
+++ b/ckanext/ga_report/helpers.py
@@ -28,28 +28,37 @@
     return base.render_snippet('ga_report/ga_popular_datasets.html', **ctx)
 
 def single_popular_dataset(top=20):
+    '''Returns a random dataset from the most popular ones.
+
+    :param top: the number of top datasets to select from
+    '''
     import random
 
-    datasets = {}
-    rand = random.randrange(0, top)
-    entry = model.Session.query(GA_Url).\
-        filter(GA_Url.url.like('/dataset/%')).\
-        order_by('ga_url.pageviews::int desc')[rand]
+    top_datasets = model.Session.query(GA_Url).\
+                   filter(GA_Url.url.like('/dataset/%')).\
+                   order_by('ga_url.pageviews::int desc')
+    num_top_datasets = top_datasets.count()
 
-
-    dataset = None
-    while not dataset:
-        dataset = model.Package.get(entry.url[len('/dataset/'):])
-        if dataset and not dataset.state == 'active':
-            dataset = None
-        else:
-            publisher = model.Group.get(entry.department_id)
-
-    ctx = {
+    if num_top_datasets:
+        dataset = None
+        while not dataset:
+            rand = random.randrange(0, min(top, num_top_datasets))
+            ga_url = top_datasets[rand]
+            dataset = model.Package.get(ga_url.url[len('/dataset/'):])
+            if dataset and not dataset.state == 'active':
+                dataset = None
+    else:
+        dataset = model.Session.query(model.Package)\
+                  .filter_by(state='active').first()
+    publisher = dataset.get_groups('publisher')[0]
+    return {
         'dataset': dataset,
         'publisher': publisher
     }
-    return base.render_snippet('ga_report/ga_popular_single.html', **ctx)
+
+def single_popular_dataset_html(top=20):
+    context = single_popular_dataset(top)
+    return base.render_snippet('ga_report/ga_popular_single.html', **context)
 
 
 def most_popular_datasets(publisher, count=20):
@@ -61,7 +70,7 @@
     results = _datasets_for_publisher(publisher, count)
 
     ctx = {
-        'dataset_count': len(datasets),
+        'dataset_count': len(results),
         'datasets': results,
 
         'publisher': publisher

--- a/ckanext/ga_report/plugin.py
+++ b/ckanext/ga_report/plugin.py
@@ -31,26 +31,7 @@
         }
 
     def after_map(self, map):
-        map.connect(
-            '/data/site-usage/publisher',
-            controller='ckanext.ga_report.controller:GaPublisherReport',
-            action='index'
-        )
-        map.connect(
-            '/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'
-        )
+        # GaReport
         map.connect(
             '/data/site-usage',
             controller='ckanext.ga_report.controller:GaReport',
@@ -61,6 +42,33 @@
             controller='ckanext.ga_report.controller:GaReport',
             action='csv'
         )
+
+        # GaDatasetReport
+        map.connect(
+            '/data/site-usage/publisher',
+            controller='ckanext.ga_report.controller:GaDatasetReport',
+            action='publishers'
+        )
+        map.connect(
+            '/data/site-usage/publishers_{month}.csv',
+            controller='ckanext.ga_report.controller:GaDatasetReport',
+            action='publisher_csv'
+        )
+        map.connect(
+            '/data/site-usage/dataset/datasets_{id}_{month}.csv',
+            controller='ckanext.ga_report.controller:GaDatasetReport',
+            action='dataset_csv'
+        )
+        map.connect(
+            '/data/site-usage/dataset',
+            controller='ckanext.ga_report.controller:GaDatasetReport',
+            action='read'
+        )
+        map.connect(
+            '/data/site-usage/dataset/{id}',
+            controller='ckanext.ga_report.controller:GaDatasetReport',
+            action='read_publisher'
+        )
         return map
 
 

--- a/ckanext/ga_report/templates/ga_report/ga_popular_datasets.html
+++ b/ckanext/ga_report/templates/ga_report/ga_popular_datasets.html
@@ -5,7 +5,7 @@
 
 <div class="popular_datasets">
     <div class="pull-right">
-        <a href="${h.url_for(controller='ckanext.ga_report.controller:GaPublisherReport',action='index')}" class="btn btn-primary">More popular datasets »</a>
+        <a href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read')}" class="btn btn-primary">More popular datasets »</a>
     </div>
     <h2>Popular datasets</h2>
     <h4>${publisher.title}</h4>

--- a/ckanext/ga_report/templates/ga_report/ga_popular_single.html
+++ b/ckanext/ga_report/templates/ga_report/ga_popular_single.html
@@ -7,11 +7,19 @@
 <div class="popular_datasets">
     <h2>Featured dataset</h2>
 
-    <h3>${h.link_to(dataset.title, h.url_for(controller='package', action='read', id=dataset.name))}</h3>        <div>${h.truncate(dataset.notes, length=200, whole_word=True)}</div>
-    <p></p>
+    <div class="dataset-summary boxed">
+          <a class="dataset-header" href="${h.url_for(controller='package', action='read', id=dataset.name)}">
+            <h3>${dataset.title}</h3>
+          </a>
+          <h4>
+            <strong>Publisher</strong> :
+            <a href="/publisher/${publisher.name}">${publisher.title}</a>
+          </h4>