Merge branch 'master' of git+ssh://maxious.lambdacomplex.org/git/ckanext-ga-report
--- a/ckanext/ga_report/command.py
+++ b/ckanext/ga_report/command.py
@@ -52,9 +52,7 @@
assuming it is correct.
"""
from ga_auth import init_service
- init_service('token.dat',
- self.args[0] if self.args
- else 'credentials.json')
+ init_service('token.dat', 'credentials.json')
class FixTimePeriods(CkanCommand):
"""
@@ -115,6 +113,7 @@
default=False,
dest='skip_url_stats',
help='Skip the download of URL data - just do site-wide stats')
+ self.token = ""
def command(self):
self._load_config()
@@ -129,14 +128,14 @@
return
try:
- svc = init_service(ga_token_filepath, None)
+ self.token, svc = init_service(ga_token_filepath, None)
except TypeError:
print ('Have you correctly run the getauthtoken task and '
'specified the correct token file in the CKAN config under '
'"googleanalytics.token.filepath"?')
return
- downloader = DownloadAnalytics(svc, profile_id=get_profile_id(svc),
+ downloader = DownloadAnalytics(svc, self.token, profile_id=get_profile_id(svc),
delete_first=self.options.delete_first,
skip_url_stats=self.options.skip_url_stats)
--- a/ckanext/ga_report/controller.py
+++ b/ckanext/ga_report/controller.py
@@ -211,14 +211,14 @@
graph_dict = {}
for stat in graph_query:
graph_dict[ stat.key ] = graph_dict.get(stat.key,{
- 'name':stat.key,
- 'data': []
+ 'name':stat.key,
+ 'raw': {}
})
- graph_dict[ stat.key ]['data'].append({
- 'x':_get_unix_epoch(stat.period_name),
- 'y':float(stat.value)
- })
- graph = [ graph_dict[x[0]] for x in entries ]
+ graph_dict[ stat.key ]['raw'][stat.period_name] = float(stat.value)
+ stats_in_table = [x[0] for x in entries]
+ stats_not_in_table = set(graph_dict.keys()) - set(stats_in_table)
+ stats = stats_in_table + sorted(list(stats_not_in_table))
+ graph = [graph_dict[x] for x in stats]
setattr(c, v+'_graph', json.dumps( _to_rickshaw(graph,percentageMode=True) ))
# Get the total for each set of values and then set the value as
@@ -249,7 +249,7 @@
writer = csv.writer(response)
writer.writerow(["Publisher Title", "Publisher Name", "Views", "Visits", "Period Name"])
- top_publishers, top_publishers_graph = _get_top_publishers(None)
+ top_publishers = _get_top_publishers(limit=None)
for publisher,view,visit in top_publishers:
writer.writerow([publisher.title.encode('utf-8'),
@@ -271,7 +271,7 @@
if not c.publisher:
abort(404, 'A publisher with that name could not be found')
- packages = self._get_packages(c.publisher)
+ packages = self._get_packages(publisher=c.publisher, month=c.month)
response.headers['Content-Type'] = "text/csv; charset=utf-8"
response.headers['Content-Disposition'] = \
str('attachment; filename=datasets_%s_%s.csv' % (c.publisher_name, month,))
@@ -300,15 +300,18 @@
if c.month:
c.month_desc = ''.join([m[1] for m in c.months if m[0]==c.month])
- c.top_publishers, graph_data = _get_top_publishers()
+ c.top_publishers = _get_top_publishers()
+ graph_data = _get_top_publishers_graph()
c.top_publishers_graph = json.dumps( _to_rickshaw(graph_data) )
- return render('ga_report/publisher/index.html')
-
- def _get_packages(self, publisher=None, count=-1):
+ x = render('ga_report/publisher/index.html')
+
+ return x
+
+ def _get_packages(self, publisher=None, month='', count=-1):
'''Returns the datasets in order of views'''
have_download_data = True
- month = c.month or 'All'
+ month = month or 'All'
if month != 'All':
have_download_data = month >= DOWNLOADS_AVAILABLE_FROM
@@ -339,7 +342,8 @@
downloads += int(x.value)
else:
downloads = 'No data'
- top_packages.append((package, entry.pageviews, entry.visits, downloads))
+ if package.private == False:
+ top_packages.append((package, entry.pageviews, entry.visits, downloads))
else:
log.warning('Could not find package associated package')
@@ -385,28 +389,25 @@
entry = q.filter(GA_Url.period_name==c.month).first()
c.publisher_page_views = entry.pageviews if entry else 0
- c.top_packages = self._get_packages(c.publisher, 20)
+ c.top_packages = self._get_packages(publisher=c.publisher, count=20, month=c.month)
# Graph query
- top_package_names = [ x[0].name for x in c.top_packages ]
+ top_packages_all_time = self._get_packages(publisher=c.publisher, count=20, month='All')
+ top_package_names = [ x[0].name for x in top_packages_all_time ]
graph_query = model.Session.query(GA_Url,model.Package)\
.filter(model.Package.name==GA_Url.package_id)\
.filter(GA_Url.url.like('/dataset/%'))\
.filter(GA_Url.package_id.in_(top_package_names))
- graph_dict = {}
+ all_series = {}
for entry,package in graph_query:
if not package: continue
if entry.period_name=='All': continue
- graph_dict[package.name] = graph_dict.get(package.name,{
+ all_series[package.name] = all_series.get(package.name,{
'name':package.title,
- 'data':[]
+ 'raw': {}
})
- graph_dict[package.name]['data'].append({
- 'x':_get_unix_epoch(entry.period_name),
- 'y':int(entry.pageviews),
- })
- graph = [ graph_dict[x] for x in top_package_names ]
-
+ all_series[package.name]['raw'][entry.period_name] = int(entry.pageviews)
+ graph = [ all_series[series_name] for series_name in top_package_names ]
c.graph_data = json.dumps( _to_rickshaw(graph) )
return render('ga_report/publisher/read.html')
@@ -414,52 +415,45 @@
def _to_rickshaw(data, percentageMode=False):
if data==[]:
return data
- # Create a consistent x-axis between all series
- num_points = [ len(series['data']) for series in data ]
- ideal_index = num_points.index( max(num_points) )
- x_axis = []
+ # x-axis is every month in c.months. Note that data might not exist
+ # for entire history, eg. for recently-added datasets
+ x_axis = [x[0] for x in c.months]
+ x_axis.reverse() # Ascending order
+ x_axis = x_axis[:-1] # Remove latest month
+ totals = {}
for series in data:
+ series['data'] = []
+ for x_string in x_axis:
+ x = _get_unix_epoch( x_string )
+ y = series['raw'].get(x_string,0)
+ series['data'].append({'x':x,'y':y})
+ totals[x] = totals.get(x,0)+y
+ if not percentageMode:
+ return data
+ # Turn all data into percentages
+ # Roll insignificant series into a catch-all
+ THRESHOLD = 1
+ raw_data = data
+ data = []
+ for series in raw_data:
for point in series['data']:
- x_axis.append(point['x'])
- x_axis = sorted( list( set(x_axis) ) )
- # Zero pad any missing values
- for series in data:
- xs = [ point['x'] for point in series['data'] ]
- for x in set(x_axis).difference(set(xs)):
- series['data'].append( {'x':x, 'y':0} )
- if percentageMode:
- def get_totals(series_list):
- totals = {}
- for series in series_list:
- for point in series['data']:
- totals[point['x']] = totals.get(point['x'],0) + point['y']
- return totals
- # Transform data into percentage stacks
- totals = get_totals(data)
- # Roll insignificant series into a catch-all
- THRESHOLD = 0.01
- raw_data = data
- data = []
- for series in raw_data:
- for point in series['data']:
- fraction = float(point['y']) / totals[point['x']]
- if not (series in data) and fraction>THRESHOLD:
- data.append(series)
- # Overwrite data with a set of intereting series
- others = [ x for x in raw_data if not (x in data) ]
- data.append({
+ percentage = (100*float(point['y'])) / totals[point['x']]
+ if not (series in data) and percentage>THRESHOLD:
+ data.append(series)
+ point['y'] = percentage
+ others = [ x for x in raw_data if not (x in data) ]
+ if len(others):
+ data_other = []
+ for i in range(len(x_axis)):
+ x = _get_unix_epoch(x_axis[i])
+ y = 0
+ for series in others:
+ y += series['data'][i]['y']
+ data_other.append({'x':x,'y':y})
+ data.append({
'name':'Other',
- 'data': [ {'x':x,'y':y} for x,y in get_totals(others).items() ]
+ 'data': data_other
})
- # Turn each point into a percentage
- for series in data:
- for point in series['data']:
- point['y'] = (point['y']*100) / totals[point['x']]
- # Sort the points
- for series in data:
- series['data'] = sorted( series['data'], key=lambda x:x['x'] )
- # Strip the latest month's incomplete analytics
- series['data'] = series['data'][:-1]
return data
@@ -484,39 +478,51 @@
top_publishers = []
res = connection.execute(q, month)
- department_ids = []
for row in res:
g = model.Group.get(row[0])
if g:
- department_ids.append(row[0])
top_publishers.append((g, row[1], row[2]))
-
- graph = []
- if limit is not None:
- # Query for a history graph of these publishers
- q = model.Session.query(
- GA_Url.department_id,
- GA_Url.period_name,
- func.sum(cast(GA_Url.pageviews,sqlalchemy.types.INT)))\
- .filter( GA_Url.department_id.in_(department_ids) )\
- .filter( GA_Url.period_name!='All' )\
- .filter( GA_Url.url.like('/dataset/%') )\
- .filter( GA_Url.package_id!='' )\
- .group_by( GA_Url.department_id, GA_Url.period_name )
- graph_dict = {}
- for dept_id,period_name,views in q:
- graph_dict[dept_id] = graph_dict.get( dept_id, {
- 'name' : model.Group.get(dept_id).title,
- 'data' : []
- })
- graph_dict[dept_id]['data'].append({
- 'x': _get_unix_epoch(period_name),
- 'y': views
- })
- # Sort dict into ordered list
- for id in department_ids:
- graph.append( graph_dict[id] )
- return top_publishers, graph
+ return top_publishers
+
+
+def _get_top_publishers_graph(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
+ from ga_url
+ where department_id <> ''
+ and package_id <> ''
+ and url like '/dataset/%%'
+ and period_name='All'
+ group by department_id order by views desc
+ """
+ if limit:
+ q = q + " limit %s;" % (limit)
+
+ res = connection.execute(q)
+ department_ids = [ row[0] for row in res ]
+
+ # Query for a history graph of these department ids
+ q = model.Session.query(
+ GA_Url.department_id,
+ GA_Url.period_name,
+ func.sum(cast(GA_Url.pageviews,sqlalchemy.types.INT)))\
+ .filter( GA_Url.department_id.in_(department_ids) )\
+ .filter( GA_Url.url.like('/dataset/%') )\
+ .filter( GA_Url.package_id!='' )\
+ .group_by( GA_Url.department_id, GA_Url.period_name )
+ graph_dict = {}
+ for dept_id,period_name,views in q:
+ graph_dict[dept_id] = graph_dict.get( dept_id, {
+ 'name' : model.Group.get(dept_id).title,
+ 'raw' : {}
+ })
+ graph_dict[dept_id]['raw'][period_name] = views
+ return [ graph_dict[id] for id in department_ids ]
def _get_publishers():
@@ -526,7 +532,7 @@
'''
publishers = []
for pub in model.Session.query(model.Group).\
- filter(model.Group.type=='publisher').\
+ filter(model.Group.type=='organization').\
filter(model.Group.state=='active').\
order_by(model.Group.name):
publishers.append((pub.name, pub.title))
--- a/ckanext/ga_report/download_analytics.py
+++ b/ckanext/ga_report/download_analytics.py
@@ -1,7 +1,12 @@
import os
import logging
import datetime
+import httplib
+import urllib
import collections
+import requests
+import json
+import re
from pylons import config
from ga_model import _normalize_url
import ga_model
@@ -18,13 +23,14 @@
class DownloadAnalytics(object):
'''Downloads and stores analytics info'''
- def __init__(self, service=None, profile_id=None, delete_first=False,
+ def __init__(self, service=None, token=None, profile_id=None, delete_first=False,
skip_url_stats=False):
self.period = config['ga-report.period']
self.service = service
self.profile_id = profile_id
self.delete_first = delete_first
self.skip_url_stats = skip_url_stats
+ self.token = token
def specific_month(self, date):
import calendar
@@ -117,13 +123,13 @@
accountName = config.get('googleanalytics.account')
log.info('Downloading analytics for dataset views')
- data = self.download(start_date, end_date, '~/%s/dataset/[a-z0-9-_]+' % accountName)
+ data = self.download(start_date, end_date, '~^/dataset/[a-z0-9-_]+')
log.info('Storing dataset views (%i rows)', len(data.get('url')))
self.store(period_name, period_complete_day, data, )
log.info('Downloading analytics for publisher views')
- data = self.download(start_date, end_date, '~/%s/publisher/[a-z0-9-_]+' % accountName)
+ data = self.download(start_date, end_date, '~^/organization/[a-z0-9-_]+')
log.info('Storing publisher views (%i rows)', len(data.get('url')))
self.store(period_name, period_complete_day, data,)
@@ -149,21 +155,31 @@
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()
+ try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = dict(ids='ga:' + self.profile_id,
+ filters=query,
+ metrics=metrics,
+ sort=sort,
+ dimensions="ga:landingPagePath,ga:socialNetwork",
+ max_results=10000)
+
+ args['start-date'] = start_date
+ args['end-date'] = end_date
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
+
data = collections.defaultdict(list)
rows = results.get('rows',[])
for row in rows:
- url = _normalize_url('http:/' + row[0])
+ url = row[0]
data[url].append( (row[1], int(row[2]),) )
ga_model.update_social(period_name, data)
@@ -178,23 +194,34 @@
# 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:pagePath",
- max_results=10000,
- end_date=end_date).execute()
+ # https://ga-dev-tools.appspot.com/explorer/
+ try:
+ args = {}
+ args["sort"] = "-ga:pageviews"
+ args["max-results"] = 100000
+ args["dimensions"] = "ga:pagePath"
+ args["start-date"] = start_date
+ args["end-date"] = end_date
+ args["metrics"] = metrics
+ args["ids"] = "ga:" + self.profile_id
+ args["filters"] = query
+ args["alt"] = "json"
+ print args
+ results = self._get_json(args)
+
+ except Exception, e:
+ log.exception(e)
+ return dict(url=[])
packages = []
log.info("There are %d results" % results['totalResults'])
- for entry in results.get('rows'):
+ if results['totalResults'] > 0:
+ for entry in results.get('rows'):
(loc,pageviews,visits) = entry
- url = _normalize_url('http:/' + loc) # strips off domain e.g. www.data.gov.uk or data.gov.uk
-
- if not url.startswith('/dataset/') and not url.startswith('/publisher/'):
+ #url = _normalize_url('http:/' + loc) # strips off domain e.g. www.data.gov.uk or data.gov.uk
+ url = loc
+ #print url
+ if not url.startswith('/dataset/') and not url.startswith('/organization/'):
# filter out strays like:
# /data/user/login?came_from=http://data.gov.uk/dataset/os-code-point-open
# /403.html?page=/about&from=http://data.gov.uk/publisher/planning-inspectorate
@@ -226,25 +253,78 @@
data[key] = data.get(key,0) + result[1]
return data
+ def _get_json(self, params, prev_fail=False):
+ ga_token_filepath = os.path.expanduser(config.get('googleanalytics.token.filepath', ''))
+ if not ga_token_filepath:
+ print 'ERROR: In the CKAN config you need to specify the filepath of the ' \
+ 'Google Analytics token file under key: googleanalytics.token.filepath'
+ return
+
+ log.info("Trying to refresh our OAuth token")
+ try:
+ from ga_auth import init_service
+ self.token, svc = init_service(ga_token_filepath, None)
+ log.info("OAuth token refreshed")
+ except Exception, auth_exception:
+ log.error("Oauth refresh failed")
+ log.exception(auth_exception)
+ return
+
+ try:
+ headers = {'authorization': 'Bearer ' + self.token}
+ r = requests.get("https://www.googleapis.com/analytics/v3/data/ga", params=params, headers=headers)
+ if r.status_code != 200:
+ log.info("STATUS: %s" % (r.status_code,))
+ log.info("CONTENT: %s" % (r.content,))
+ raise Exception("Request with params: %s failed" % params)
+
+ return json.loads(r.content)
+ except Exception, e:
+ log.exception(e)
+
+ return dict(url=[])
+
def _totals_stats(self, start_date, end_date, period_name, period_complete_day):
""" Fetches distinct totals, total pageviews etc """
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- start_date=start_date,
- metrics='ga:pageviews',
- sort='-ga:pageviews',
- max_results=10000,
- end_date=end_date).execute()
+ try:
+ args = {}
+ args["max-results"] = 100000
+ args["start-date"] = start_date
+ args["end-date"] = end_date
+ args["ids"] = "ga:" + self.profile_id
+
+ args["metrics"] = "ga:pageviews"
+ args["sort"] = "-ga:pageviews"
+ args["alt"] = "json"
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
result_data = results.get('rows')
ga_model.update_sitewide_stats(period_name, "Totals", {'Total page views': result_data[0][0]},
period_complete_day)
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- start_date=start_date,
- metrics='ga:pageviewsPerVisit,ga:avgTimeOnSite,ga:percentNewVisits,ga:visits',
- max_results=10000,
- end_date=end_date).execute()
+ try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = {}
+ args["max-results"] = 100000
+ args["start-date"] = start_date
+ args["end-date"] = end_date
+ args["ids"] = "ga:" + self.profile_id
+
+ args["metrics"] = "ga:pageviewsPerVisit,ga:avgTimeOnSite,ga:percentNewVisits,ga:visits"
+ args["alt"] = "json"
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
result_data = results.get('rows')
data = {
'Pages per visit': result_data[0][0],
@@ -255,16 +335,29 @@
ga_model.update_sitewide_stats(period_name, "Totals", data, period_complete_day)
# Bounces from / or another configurable page.
- path = '/%s%s' % (config.get('googleanalytics.account'),
- config.get('ga-report.bounce_url', '/'))
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- filters='ga:pagePath==%s' % (path,),
- start_date=start_date,
- metrics='ga:visitBounceRate',
- dimensions='ga:pagePath',
- max_results=10000,
- end_date=end_date).execute()
+ path = '/' #% (config.get('googleanalytics.account'), config.get('ga-report.bounce_url', '/'))
+
+ try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = {}
+ args["max-results"] = 100000
+ args["start-date"] = start_date
+ args["end-date"] = end_date
+ args["ids"] = "ga:" + self.profile_id
+
+ args["filters"] = 'ga:pagePath==%s' % (path,)
+ args["dimensions"] = 'ga:pagePath'
+ args["metrics"] = "ga:visitBounceRate"
+ args["alt"] = "json"
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
result_data = results.get('rows')
if not result_data or len(result_data) != 1:
log.error('Could not pinpoint the bounces for path: %s. Got results: %r',
@@ -280,14 +373,28 @@
def _locale_stats(self, start_date, end_date, period_name, period_complete_day):
""" Fetches stats about language and country """
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- start_date=start_date,
- metrics='ga:pageviews',
- sort='-ga:pageviews',
- dimensions="ga:language,ga:country",
- max_results=10000,
- end_date=end_date).execute()
+
+ try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = {}
+ args["max-results"] = 100000
+ args["start-date"] = start_date
+ args["end-date"] = end_date
+ args["ids"] = "ga:" + self.profile_id
+
+ args["dimensions"] = "ga:language,ga:country"
+ args["metrics"] = "ga:pageviews"
+ args["sort"] = "-ga:pageviews"
+ args["alt"] = "json"
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
result_data = results.get('rows')
data = {}
for result in result_data:
@@ -308,15 +415,27 @@
data = {}
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- start_date=start_date,
- filters='ga:eventAction==download',
- metrics='ga:totalEvents',
- sort='-ga:totalEvents',
- dimensions="ga:eventLabel",
- max_results=10000,
- end_date=end_date).execute()
+ try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = {}
+ args["max-results"] = 100000
+ args["start-date"] = start_date
+ args["end-date"] = end_date
+ args["ids"] = "ga:" + self.profile_id
+
+ args["filters"] = 'ga:eventAction==Download'
+ args["dimensions"] = "ga:eventLabel"
+ args["metrics"] = "ga:totalEvents"
+ args["alt"] = "json"
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
result_data = results.get('rows')
if not result_data:
# We may not have data for this time period, so we need to bail
@@ -333,7 +452,7 @@
if progress_count % 100 == 0:
log.debug('.. %d/%d done so far', progress_count, progress_total)
- url = result[0].strip()
+ url = urllib.unquote(result[0].strip())
# Get package id associated with the resource that has this URL.
q = model.Session.query(model.Resource)
@@ -341,8 +460,15 @@
r = q.filter(model.Resource.cache_url.like("%s%%" % url)).first()
else:
r = q.filter(model.Resource.url.like("%s%%" % url)).first()
+
+ # new style internal download links
+ if re.search('(?:/resource/)(.*)(?:/download/)',url):
+ resource_id = re.search('(?:/resource/)(.*)(?:/download/)',url)
+ r = q.filter(model.Resource.id.like("%s%%" % resource_id.group(1))).first()
package_name = r.resource_group.package.name if r else ""
+
+
if package_name:
data[package_name] = data.get(package_name, 0) + int(result[1])
else:
@@ -355,31 +481,52 @@
log.info('Associating downloads of resource URLs with their respective datasets')
process_result_data(results.get('rows'))
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- start_date=start_date,
- filters='ga:eventAction==download-cache',
- metrics='ga:totalEvents',
- sort='-ga:totalEvents',
- dimensions="ga:eventLabel",
- max_results=10000,
- end_date=end_date).execute()
+ '''try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = dict( ids='ga:' + self.profile_id,
+ filters='ga:eventAction==download-cache',
+ metrics='ga:totalEvents',
+ sort='-ga:totalEvents',
+ dimensions="ga:eventLabel",
+ max_results=10000)
+ args['start-date'] = start_date
+ args['end-date'] = end_date
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
log.info('Associating downloads of cache resource URLs with their respective datasets')
- process_result_data(results.get('rows'), cached=False)
+ process_result_data(results.get('rows'), cached=False)'''
self._filter_out_long_tail(data, MIN_DOWNLOADS)
ga_model.update_sitewide_stats(period_name, "Downloads", data, period_complete_day)
def _social_stats(self, start_date, end_date, period_name, period_complete_day):
""" Finds out which social sites people are referred from """
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- start_date=start_date,
- metrics='ga:pageviews',
- sort='-ga:pageviews',
- dimensions="ga:socialNetwork,ga:referralPath",
- max_results=10000,
- end_date=end_date).execute()
+
+ try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = dict( ids='ga:' + self.profile_id,
+ metrics='ga:pageviews',
+ sort='-ga:pageviews',
+ dimensions="ga:socialNetwork,ga:referralPath",
+ max_results=10000)
+ args['start-date'] = start_date
+ args['end-date'] = end_date
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
result_data = results.get('rows')
data = {}
for result in result_data:
@@ -391,14 +538,24 @@
def _os_stats(self, start_date, end_date, period_name, period_complete_day):
""" Operating system stats """
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- start_date=start_date,
- metrics='ga:pageviews',
- sort='-ga:pageviews',
- dimensions="ga:operatingSystem,ga:operatingSystemVersion",
- max_results=10000,
- end_date=end_date).execute()
+ try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = dict( ids='ga:' + self.profile_id,
+ metrics='ga:pageviews',
+ sort='-ga:pageviews',
+ dimensions="ga:operatingSystem,ga:operatingSystemVersion",
+ max_results=10000)
+ args['start-date'] = start_date
+ args['end-date'] = end_date
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
result_data = results.get('rows')
data = {}
for result in result_data:
@@ -416,14 +573,27 @@
def _browser_stats(self, start_date, end_date, period_name, period_complete_day):
""" Information about browsers and browser versions """
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- start_date=start_date,
- metrics='ga:pageviews',
- sort='-ga:pageviews',
- dimensions="ga:browser,ga:browserVersion",
- max_results=10000,
- end_date=end_date).execute()
+
+ try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = dict( ids='ga:' + self.profile_id,
+ metrics='ga:pageviews',
+ sort='-ga:pageviews',
+ dimensions="ga:browser,ga:browserVersion",
+ max_results=10000)
+
+ args['start-date'] = start_date
+ args['end-date'] = end_date
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
+
result_data = results.get('rows')
# e.g. [u'Firefox', u'19.0', u'20']
@@ -465,14 +635,24 @@
def _mobile_stats(self, start_date, end_date, period_name, period_complete_day):
""" Info about mobile devices """
- results = self.service.data().ga().get(
- ids='ga:' + self.profile_id,
- start_date=start_date,
- metrics='ga:pageviews',
- sort='-ga:pageviews',
- dimensions="ga:mobileDeviceBranding, ga:mobileDeviceInfo",
- max_results=10000,
- end_date=end_date).execute()
+ try:
+ # Because of issues of invalid responses, we are going to make these requests
+ # ourselves.
+ headers = {'authorization': 'Bearer ' + self.token}
+
+ args = dict( ids='ga:' + self.profile_id,
+ metrics='ga:pageviews',
+ sort='-ga:pageviews',
+ dimensions="ga:mobileDeviceBranding, ga:mobileDeviceInfo",
+ max_results=10000)
+ args['start-date'] = start_date
+ args['end-date'] = end_date
+
+ results = self._get_json(args)
+ except Exception, e:
+ log.exception(e)
+ results = dict(url=[])
+
result_data = results.get('rows')
data = {}
--- a/ckanext/ga_report/ga_auth.py
+++ b/ckanext/ga_report/ga_auth.py
@@ -36,7 +36,7 @@
credentials = _prepare_credentials(token_file, credentials_file)
http = credentials.authorize(http) # authorize the http object
- return build('analytics', 'v3', http=http)
+ return credentials.access_token, build('analytics', 'v3', http=http)
def get_profile_id(service):
--- a/ckanext/ga_report/ga_model.py
+++ b/ckanext/ga_report/ga_model.py
@@ -114,7 +114,7 @@
>>> normalize_url('http://data.gov.uk/dataset/weekly_fuel_prices')
'/dataset/weekly_fuel_prices'
'''
- return '/' + '/'.join(url.split('/')[3:])
+ return url #'/' + '/'.join(url.split('/')[3:])
def _get_package_and_publisher(url):
@@ -125,12 +125,12 @@
dataset_ref = dataset_match.groups()[0]
dataset = model.Package.get(dataset_ref)
if dataset:
- publisher_groups = dataset.get_groups('publisher')
+ publisher_groups = dataset.get_groups('organization')
if publisher_groups:
return dataset_ref,publisher_groups[0].name
return dataset_ref, None
else:
- publisher_match = re.match('/publisher/([^/]+)(/.*)?', url)
+ publisher_match = re.match('/organization/([^/]+)(/.*)?', url)
if publisher_match:
return None, publisher_match.groups()[0]
return None, None
@@ -323,11 +323,11 @@
"""
toplevel = get_top_level()
publishers = model.Session.query(model.Group).\
- filter(model.Group.type=='publisher').\
+ filter(model.Group.type=='organization').\
filter(model.Group.state=='active').all()
for publisher in publishers:
views, visits, subpub = update_publisher(period_name, publisher, publisher.name)
- parent, parents = '', publisher.get_groups('publisher')
+ parent, parents = '', publisher.get_parent_groups(type='organization')
if parents:
parent = parents[0].name
item = model.Session.query(GA_Publisher).\
@@ -377,15 +377,12 @@
model.Member.table_name == 'group' and \
model.Member.state == 'active').\
filter(model.Member.id==None).\
- filter(model.Group.type=='publisher').\
+ filter(model.Group.type=='organization').\
order_by(model.Group.name).all()
def get_children(publisher):
- '''Finds child publishers for the given publisher (object). (Not recursive)'''
- from ckan.model.group import HIERARCHY_CTE
- return model.Session.query(model.Group).\
- from_statement(HIERARCHY_CTE).params(id=publisher.id, type='publisher').\
- all()
+ '''Finds child publishers for the given publisher (object). (Not recursive i.e. returns one level)'''
+ return publisher.get_children_groups(type='organization')
def go_down_tree(publisher):
'''Provided with a publisher object, it walks down the hierarchy and yields each publisher,
--- a/ckanext/ga_report/helpers.py
+++ b/ckanext/ga_report/helpers.py
@@ -71,7 +71,7 @@
def single_popular_dataset_html(top=20):
dataset_dict = single_popular_dataset(top)
groups = package.get('groups', [])
- publishers = [ g for g in groups if g.get('type') == 'publisher' ]
+ publishers = [ g for g in groups if g.get('type') == 'organization' ]
publisher = publishers[0] if publishers else {'name':'', 'title': ''}
context = {
'dataset': dataset_dict,
@@ -80,7 +80,7 @@
return base.render_snippet('ga_report/ga_popular_single.html', **context)
-def most_popular_datasets(publisher, count=20):
+def most_popular_datasets(publisher, count=20, preview_image=None):
if not publisher:
_log.error("No valid publisher passed to 'most_popular_datasets'")
@@ -92,7 +92,8 @@
'dataset_count': len(results),
'datasets': results,
- 'publisher': publisher
+ 'publisher': publisher,
+ 'preview_image': preview_image
}
return base.render_snippet('ga_report/publisher/popular.html', **ctx)
@@ -106,12 +107,22 @@
for entry in entries:
if len(datasets) < count:
p = model.Package.get(entry.url[len('/dataset/'):])
+
if not p:
_log.warning("Could not find Package for {url}".format(url=entry.url))
continue
+ if not p.state == 'active':
+ _log.warning("Package {0} is not active, it is {1}".format(p.name, p.state))
+ continue
+
+ if not p.private == False:
+ _log.warning("Package {0} is private {1}".format(p.name, p.state))
+ continue
+
if not p in datasets:
datasets[p] = {'views':0, 'visits': 0}
+
datasets[p]['views'] = datasets[p]['views'] + int(entry.pageviews)
datasets[p]['visits'] = datasets[p]['visits'] + int(entry.visits)
@@ -121,3 +132,17 @@
return sorted(results, key=operator.itemgetter(1), reverse=True)
+def month_option_title(month_iso, months, day):
+ month_isos = [ iso_code for (iso_code,name) in months ]
+ try:
+ index = month_isos.index(month_iso)
+ except ValueError:
+ _log.error('Month "%s" not found in list of months.' % month_iso)
+ return month_iso
+ month_name = months[index][1]
+ if index==0:
+ return month_name + (' (up to %s)'%day)
+ return month_name
+
+
+
--- a/ckanext/ga_report/plugin.py
+++ b/ckanext/ga_report/plugin.py
@@ -5,7 +5,8 @@
from ckanext.ga_report.helpers import (most_popular_datasets,
popular_datasets,
- single_popular_dataset)
+ single_popular_dataset,
+ month_option_title)
log = logging.getLogger('ckanext.ga-report')
@@ -27,55 +28,56 @@
'ga_report_installed': lambda: True,
'popular_datasets': popular_datasets,
'most_popular_datasets': most_popular_datasets,
- 'single_popular_dataset': single_popular_dataset
+ 'single_popular_dataset': single_popular_dataset,
+ 'month_option_title': month_option_title
}
def after_map(self, map):
# GaReport
map.connect(
- '/data/site-usage',
+ '/site-usage',
controller='ckanext.ga_report.controller:GaReport',
action='index'
)
map.connect(
- '/data/site-usage/data_{month}.csv',
+ '/site-usage_{month}.csv',
controller='ckanext.ga_report.controller:GaReport',
action='csv'
)
map.connect(
- '/data/site-usage/downloads',
+ '/site-usage/downloads',
controller='ckanext.ga_report.controller:GaReport',
action='downloads'
)
map.connect(
- '/data/site-usage/downloads_{month}.csv',
+ '/site-usage/downloads_{month}.csv',
controller='ckanext.ga_report.controller:GaReport',
action='csv_downloads'
)
# GaDatasetReport
map.connect(
- '/data/site-usage/publisher',
+ '/site-usage/publisher',
controller='ckanext.ga_report.controller:GaDatasetReport',
action='publishers'
)
map.connect(
- '/data/site-usage/publishers_{month}.csv',
+ '/site-usage/publishers_{month}.csv',
controller='ckanext.ga_report.controller:GaDatasetReport',
action='publisher_csv'
)
map.connect(
- '/data/site-usage/dataset/datasets_{id}_{month}.csv',
+ '/site-usage/dataset/datasets_{id}_{month}.csv',
controller='ckanext.ga_report.controller:GaDatasetReport',
action='dataset_csv'
)
map.connect(
- '/data/site-usage/dataset',
+ '/site-usage/dataset',
controller='ckanext.ga_report.controller:GaDatasetReport',
action='read'
)
map.connect(
- '/data/site-usage/dataset/{id}',
+ '/site-usage/dataset/{id}',
controller='ckanext.ga_report.controller:GaDatasetReport',
action='read_publisher'
)
--- a/ckanext/ga_report/public/css/ga_report.css
+++ b/ckanext/ga_report/public/css/ga_report.css
@@ -2,6 +2,11 @@
padding: 1px 0 0 0;
width: 108px;
text-align: center;
+ /* Hack to hide the momentary flash of text
+ * before sparklines are fully rendered */
+ font-size: 1px;
+ color: transparent;
+ overflow: hidden;
}
.rickshaw_chart_container {
position: relative;
@@ -18,6 +23,7 @@
.rickshaw_legend {
background: transparent;
width: 100%;
+ padding-top: 4px;
}
.rickshaw_y_axis {
position: absolute;
@@ -29,6 +35,10 @@
background: transparent !important;
color: #000000 !important;
font-weight: normal !important;
+}
+.rickshaw_legend .instructions {
+ color: #000;
+ margin-bottom: 6px;
}
.rickshaw_legend .line .action {
@@ -44,8 +54,19 @@
float: left;
width: 200px;
}
+.rickshaw_legend .line .label:hover {
+ text-decoration: underline;
+}
.ga-reports-table .td-numeric {
text-align: center;
}
+.ga-reports-heading {
+ padding-right: 10px;
+ margin-top: 4px;
+ float: left;
+}
+.tab-content {
+ padding-top: 12px;
+}
--- a/ckanext/ga_report/public/scripts/ckanext_ga_reports.js
+++ b/ckanext/ga_report/public/scripts/ckanext_ga_reports.js
@@ -4,14 +4,22 @@
CKAN.GA_Reports.render_rickshaw = function( css_name, data, mode, colorscheme ) {
var graphLegends = $('#graph-legend-container');
- if (!Modernizr.svg) {
+ function renderError(alertClass,alertText,legendText) {
$("#chart_"+css_name)
- .html( '<div class="alert">Your browser does not support vector graphics. No graphs can be rendered.</div>')
+ .html( '<div class="alert '+alertClass+'">'+alertText+'</div>')
.closest('.rickshaw_chart_container').css('height',50);
var myLegend = $('<div id="legend_'+css_name+'"/>')
- .html('(Graph cannot be rendered)')
+ .html(legendText)
.appendTo(graphLegends);
+ }
+
+ if (!Modernizr.svg) {
+ renderError('','Your browser does not support vector graphics. No graphs can be rendered.','(Graph cannot be rendered)');
return;
+ }
+ if (data.length==0) {
+ renderError('alert-info','There is not enough data to render a graph.','(No graph available)');
+ return
}
var myLegend = $('<div id="legend_'+css_name+'"/>').appendTo(graphLegends);
@@ -30,7 +38,9 @@
series: data ,
height: 328
});
- var x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph } );
+ var x_axis = new Rickshaw.Graph.Axis.Time( {
+ graph: graph
+ } );
var y_axis = new Rickshaw.Graph.Axis.Y( {
graph: graph,
orientation: 'left',
@@ -45,6 +55,7 @@
graph: graph,
legend: legend
} );
+ myLegend.prepend('<div class="instructions">Click on a series below to isolate its graph:</div>');
graph.render();
};
@@ -54,24 +65,29 @@
* Sparkline graphs should be drawn.
* Note that they cannot be drawn sooner.
*/
+ var created = false;
$('a[href="#totals"]').on(
'shown',
- function() {
- var sparkOptions = {
- enableTagOptions: true,
- type: 'line',
- width: 100,
- height: 26,
- chartRangeMin: 0,
- spotColor: '',
- maxSpotColor: '',
- minSpotColor: '',
- highlightSpotColor: '000000',
- lineColor: '3F8E6D',
- fillColor: 'B7E66B'
- };
- $('.sparkline').sparkline('html',sparkOptions);
- }
+ function() {
+ if (!created) {
+ var sparkOptions = {
+ enableTagOptions: true,
+ type: 'line',
+ width: 100,
+ height: 26,
+ chartRangeMin: 0,
+ spotColor: '',
+ maxSpotColor: '',
+ minSpotColor: '',
+ highlightSpotColor: '#000000',
+ lineColor: '#3F8E6D',
+ fillColor: '#B7E66B'
+ };
+ $('.sparkline').sparkline('html',sparkOptions);
+ created = true;
+ }
+ $.sparkline_display_visible();
+ }
);
};
@@ -81,7 +97,7 @@
* Show the correct rickshaw graph in the sidebar.
* Not to be called before all graphs load.
*/
- $('a[data-toggle="hashchange"]').on(
+ $('a[data-toggle="tab"]').on(
'shown',
function(e) {
var href = $(e.target).attr('href');
@@ -94,9 +110,12 @@
}
legend_name = '#legend_'+legend_name;
$('#graph-legend-container > *').hide();
+ $('#graph-legend-container .instructions').show();
$(legend_name).show();
}
);
+ /* The first tab might already have been shown */
+ $('li.active > a[data-toggle="tab"]').trigger('shown');
};
CKAN.GA_Reports.bind_month_selector = function() {
@@ -107,26 +126,6 @@
window.location = url;
};
var selectors = $('select[name="month"]');
- assert(selectors.length>0);
selectors.bind('change', handler);
};
-/*
- * Custom bootstrap plugin for handling data-toggle="hashchange".
- * Behaves like data-toggle="tab" but I respond to the hashchange.
- * Page state is memo-ized in the URL this way. Why doesn't Bootstrap do this?
- */
-$(function() {
- var mapping = {};
- $('a[data-toggle="hashchange"]').each(
- function(i,link) {
- link = $(link);
- mapping[link.attr('href')] = link;
- }
- );
- $(window).hashchange(function() {
- var link = mapping[window.location.hash];
- if (link) { link.tab('show'); }
- });
-});
-
--- /dev/null
+++ b/ckanext/ga_report/public/scripts/modernizr-2.6.2.custom.js
@@ -1,1 +1,815 @@
-
+/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
+ * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
+ */
+;
+
+
+
+window.Modernizr = (function( window, document, undefined ) {
+
+ var version = '2.6.2',
+
+ Modernizr = {},
+
+ enableClasses = true,
+
+ docElement = document.documentElement,
+
+ mod = 'modernizr',
+ modElem = document.createElement(mod),
+ mStyle = modElem.style,
+
+ inputElem = document.createElement('input') ,
+
+ smile = ':)',
+
+ toString = {}.toString,
+
+ prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
+
+
+
+ omPrefixes = 'Webkit Moz O ms',
+
+ cssomPrefixes = omPrefixes.split(' '),
+
+ domPrefixes = omPrefixes.toLowerCase().split(' '),
+
+ ns = {'svg': 'http://www.w3.org/2000/svg'},
+
+ tests = {},
+ inputs = {},
+ attrs = {},
+
+ classes = [],
+
+ slice = classes.slice,
+
+ featureName,
+
+
+ injectElementWithStyles = function( rule, callback, nodes, testnames ) {
+
+ var style, ret, node, docOverflow,
+ div = document.createElement('div'),
+ body = document.body,
+ fakeBody = body || document.createElement('body');
+
+ if ( parseInt(nodes, 10) ) {
+ while ( nodes-- ) {
+ node = document.createElement('div');
+ node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
+ div.appendChild(node);
+ }
+ }
+
+ style = ['­','<style id="s', mod, '">', rule, '</style>'].join('');
+ div.id = mod;
+ (body ? div : fakeBody).innerHTML += style;
+ fakeBody.appendChild(div);
+ if ( !body ) {
+ fakeBody.style.background = '';
+ fakeBody.style.overflow = 'hidden';
+ docOverflow = docElement.style.overflow;
+ docElement.style.overflow = 'hidden';
+ docElement.appendChild(fakeBody);
+ }
+
+ ret = callback(div, rule);
+ if ( !body ) {
+ fakeBody.parentNode.removeChild(fakeBody);
+ docElement.style.overflow = docOverflow;
+ } else {
+ div.parentNode.removeChild(div);
+ }
+
+ return !!ret;
+
+ },
+
+
+
+ isEventSupported = (function() {
+
+ var TAGNAMES = {
+ 'select': 'input', 'change': 'input',
+ 'submit': 'form', 'reset': 'form',
+ 'error': 'img', 'load': 'img', 'abort': 'img'
+ };
+
+ function isEventSupported( eventName, element ) {
+
+ element = element || document.createElement(TAGNAMES[eventName] || 'div');
+ eventName = 'on' + eventName;
+
+ var isSupported = eventName in element;
+
+ if ( !isSupported ) {
+ if ( !element.setAttribute ) {
+ element = document.createElement('div');
+ }
+ if ( element.setAttribute && element.removeAttribute ) {
+ element.setAttribute(eventName, '');
+ isSupported = is(element[eventName], 'function');
+
+ if ( !is(element[eventName], 'undefined') ) {
+ element[eventName] = undefined;
+ }
+ element.removeAttribute(eventName);
+ }
+ }
+
+ element = null;
+ return isSupported;
+ }
+ return isEventSupported;
+ })(),
+
+
+ _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
+
+ if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
+ hasOwnProp = function (object, property) {
+ return _hasOwnProperty.call(object, property);
+ };
+ }
+ else {
+ hasOwnProp = function (object, property) {
+ return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
+ };
+ }
+
+
+ if (!Function.prototype.bind) {
+ Function.prototype.bind = function bind(that) {
+
+ var target = this;
+
+ if (typeof target != "function") {
+ throw new TypeError();
+ }
+
+ var args = slice.call(arguments, 1),
+ bound = function () {
+
+ if (this instanceof bound) {
+
+ var F = function(){};
+ F.prototype = target.prototype;
+ var self = new F();
+
+ var result = target.apply(
+ self,
+ args.concat(slice.call(arguments))
+ );
+ if (Object(result) === result) {
+ return result;
+ }
+ return self;
+
+ } else {
+
+ return target.apply(
+ that,
+ args.concat(slice.call(arguments))
+ );
+
+ }
+
+ };
+
+ return bound;
+ };
+ }
+
+ function setCss( str ) {
+ mStyle.cssText = str;
+ }
+
+ function setCssAll( str1, str2 ) {
+ return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
+ }
+
+ function is( obj, type ) {
+ return typeof obj === type;
+ }
+
+ function contains( str, substr ) {
+ return !!~('' + str).indexOf(substr);
+ }
+
+ function testProps( props, prefixed ) {
+ for ( var i in props ) {
+ var prop = props[i];
+ if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
+ return prefixed == 'pfx' ? prop : true;
+ }
+ }
+ return false;
+ }
+
+ function testDOMProps( props, obj, elem ) {
+ for ( var i in props ) {
+ var item = obj[props[i]];
+ if ( item !== undefined) {
+
+ if (elem === false) return props[i];
+
+ if (is(item, 'function')){
+ return item.bind(elem || obj);
+ }
+
+ return item;
+ }
+ }
+ return false;
+ }
+
+ function testPropsAll( prop, prefixed, elem ) {
+
+ var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
+ props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
+
+ if(is(prefixed, "string") || is(prefixed, "undefined")) {
+ return testProps(props, prefixed);
+
+ } else {
+ props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
+ return testDOMProps(props, prefixed, elem);
+ }
+ } tests['flexbox'] = function() {
+ return testPropsAll('flexWrap');
+ }; tests['canvas'] = function() {
+ var elem = document.createElement('canvas');
+ return !!(elem.getContext && elem.getContext('2d'));
+ };
+
+ tests['canvastext'] = function() {
+ return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
+ };
+
+
+
+ tests['webgl'] = function() {
+ return !!window.WebGLRenderingContext;
+ };
+
+
+ tests['touch'] = function() {
+ var bool;
+
+ if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
+ bool = true;
+ } else {
+ injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
+ bool = node.offsetTop === 9;
+ });
+ }
+
+ return bool;
+ };
+
+
+
+ tests['geolocation'] = function() {
+ return 'geolocation' in navigator;
+ };
+
+
+ tests['postmessage'] = function() {
+ return !!window.postMessage;
+ };
+
+
+ tests['websqldatabase'] = function() {
+ return !!window.openDatabase;
+ };
+
+ tests['indexedDB'] = function() {
+ return !!testPropsAll("indexedDB", window);
+ };
+
+ tests['hashchange'] = function() {
+ return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7);
+ };
+
+ tests['history'] = function() {
+ return !!(window.history && history.pushState);
+ };
+
+ tests['draganddrop'] = function() {
+ var div = document.createElement('div');
+ return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
+ };
+
+ tests['websockets'] = function() {
+ return 'WebSocket' in window || 'MozWebSocket' in window;
+ };
+
+
+ tests['rgba'] = function() {
+ setCss('background-color:rgba(150,255,150,.5)');
+
+ return contains(mStyle.backgroundColor, 'rgba');
+ };
+
+ tests['hsla'] = function() {
+ setCss('background-color:hsla(120,40%,100%,.5)');
+
+ return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla');
+ };
+
+ tests['multiplebgs'] = function() {
+ setCss('background:url(https://),url(https://),red url(https://)');
+
+ return (/(url\s*\(.*?){3}/).test(mStyle.background);
+ }; tests['backgroundsize'] = function() {
+ return testPropsAll('backgroundSize');
+ };
+
+ tests['borderimage'] = function() {
+ return testPropsAll('borderImage');
+ };
+
+
+
+ tests['borderradius'] = function() {
+ return testPropsAll('borderRadius');
+ };
+
+ tests['boxshadow'] = function() {
+ return testPropsAll('boxShadow');
+ };
+
+ tests['textshadow'] = function() {
+ return document.createElement('div').style.textShadow === '';
+ };
+
+
+ tests['opacity'] = function() {
+ setCssAll('opacity:.55');
+
+ return (/^0.55$/).test(mStyle.opacity);
+ };
+
+
+ tests['cssanimations'] = function() {
+ return testPropsAll('animationName');
+ };
+
+
+ tests['csscolumns'] = function() {
+ return testPropsAll('columnCount');
+ };
+
+
+ tests['cssgradients'] = function() {
+ var str1 = 'background-image:',
+ str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
+ str3 = 'linear-gradient(left top,#9f9, white);';
+
+ setCss(
+ (str1 + '-webkit- '.split(' ').join(str2 + str1) +
+ prefixes.join(str3 + str1)).slice(0, -str1.length)
+ );
+
+ return contains(mStyle.backgroundImage, 'gradient');
+ };
+
+
+ tests['cssreflections'] = function() {
+ return testPropsAll('boxReflect');
+ };
+
+
+ tests['csstransforms'] = function() {
+ return !!testPropsAll('transform');
+ };
+
+
+ tests['csstransforms3d'] = function() {
+
+ var ret = !!testPropsAll('perspective');
+
+ if ( ret && 'webkitPerspective' in docElement.style ) {
+
+ injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) {
+ ret = node.offsetLeft === 9 && node.offsetHeight === 3;
+ });
+ }
+ return ret;
+ };
+
+
+ tests['csstransitions'] = function() {
+ return testPropsAll('transition');
+ };
+
+
+
+ tests['fontface'] = function() {
+ var bool;
+
+ injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) {
+ var style = document.getElementById('smodernizr'),
+ sheet = style.sheet || style.styleSheet,
+ cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : '';
+
+ bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0;
+ });
+
+ return bool;
+ };
+
+ tests['generatedcontent'] = function() {
+ var bool;
+
+ injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) {
+ bool = node.offsetHeight >= 3;
+ });
+
+ return bool;
+ };
+ tests['video'] = function() {
+ var elem = document.createElement('video'),
+ bool = false;
+
+ try {
+ if ( bool = !!elem.canPlayType ) {
+ bool = new Boolean(bool);
+ bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,'');
+
+ bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,'');
+
+ bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,'');
+ }
+
+ } catch(e) { }
+
+ return bool;
+ };
+
+ tests['audio'] = function() {
+ var elem = document.createElement('audio'),
+ bool = false;
+
+ try {
+ if ( bool = !!elem.canPlayType ) {
+ bool = new Boolean(bool);
+ bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,'');
+ bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,'');
+
+ bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,'');
+ bool.m4a = ( elem.canPlayType('audio/x-m4a;') ||
+ elem.canPlayType('audio/aac;')) .replace(/^no$/,'');
+ }
+ } catch(e) { }
+
+ return bool;
+ };
+
+
+ tests['localstorage'] = function() {
+ try {
+ localStorage.setItem(mod, mod);
+ localStorage.removeItem(mod);
+ return true;
+ } catch(e) {
+ return false;
+ }
+ };
+
+ tests['sessionstorage'] = function() {
+ try {
+ sessionStorage.setItem(mod, mod);
+ sessionStorage.removeItem(mod);
+ return true;
+ } catch(e) {
+ return false;
+ }
+ };
+
+
+ tests['webworkers'] = function() {
+ return !!window.Worker;
+ };
+
+
+ tests['applicationcache'] = function() {
+ return !!window.applicationCache;
+ };
+
+
+ tests['svg'] = function() {
+ return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
+ };
+
+ tests['inlinesvg'] = function() {
+ var div = document.createElement('div');
+ div.innerHTML = '<svg/>';
+ return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
+ };
+
+ tests['smil'] = function() {
+ return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate')));
+ };
+
+
+ tests['svgclippaths'] = function() {
+ return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
+ };
+
+ function webforms() {
+ Modernizr['input'] = (function( props ) {
+ for ( var i = 0, len = props.length; i < len; i++ ) {
+ attrs[ props[i] ] = !!(props[i] in inputElem);
+ }
+ if (attrs.list){
+ attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
+ }
+ return attrs;
+ })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' '));
+ Modernizr['inputtypes'] = (function(props) {
+
+ for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) {
+
+ inputElem.setAttribute('type', inputElemType = props[i]);
+ bool = inputElem.type !== 'text';
+
+ if ( bool ) {
+
+ inputElem.value = smile;
+ inputElem.style.cssText = 'position:absolute;visibility:hidden;';
+
+ if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
+
+ docElement.appendChild(inputElem);
+ defaultView = document.defaultView;
+
+ bool = defaultView.getComputedStyle &&
+ defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
+ (inputElem.offsetHeight !== 0);
+
+ docElement.removeChild(inputElem);
+
+ } else if ( /^(search|tel)$/.test(inputElemType) ){
+ } else if ( /^(url|email)$/.test(inputElemType) ) {
+ bool = inputElem.checkValidity && inputElem.checkValidity() === false;
+
+ } else {
+ bool = inputElem.value != smile;
+ }
+ }
+
+ inputs[ props[i] ] = !!bool;
+ }
+ return inputs;
+ })('search tel url email datetime date month week time datetime-local number range color'.split(' '));
+ }
+ for ( var feature in tests ) {
+ if ( hasOwnProp(tests, feature) ) {
+ featureName = feature.toLowerCase();
+ Modernizr[featureName] = tests[feature]();
+
+ classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
+ }
+ }
+
+ Modernizr.input || webforms();
+
+
+ Modernizr.addTest = function ( feature, test ) {
+ if ( typeof feature == 'object' ) {
+ for ( var key in feature ) {
+ if ( hasOwnProp( feature, key ) ) {
+ Modernizr.addTest( key, feature[ key ] );
+ }
+ }
+ } else {
+
+ feature = feature.toLowerCase();
+
+ if ( Modernizr[feature] !== undefined ) {
+ return Modernizr;
+ }
+
+ test = typeof test == 'function' ? test() : test;
+
+ if (typeof enableClasses !== "undefined" && enableClasses) {
+ docElement.className += ' ' + (test ? '' : 'no-') + feature;
+ }
+ Modernizr[feature] = test;
+
+ }
+
+ return Modernizr;
+ };
+
+
+ setCss('');
+ modElem = inputElem = null;
+
+ ;(function(window, document) {
+ var options = window.html5 || {};
+
+ var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
+
+ var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
+
+ var supportsHtml5Styles;
+
+ var expando = '_html5shiv';
+
+ var expanID = 0;
+
+ var expandoData = {};
+
+ var supportsUnknownElements;
+
+ (function() {
+ try {
+ var a = document.createElement('a');
+ a.innerHTML = '<xyz></xyz>';
+ supportsHtml5Styles = ('hidden' in a);
+
+ supportsUnknownElements = a.childNodes.length == 1 || (function() {
+ (document.createElement)('a');
+ var frag = document.createDocumentFragment();
+ return (
+ typeof frag.cloneNode == 'undefined' ||
+ typeof frag.createDocumentFragment == 'undefined' ||
+ typeof frag.createElement == 'undefined'
+ );
+ }());
+ } catch(e) {
+ supportsHtml5Styles = true;
+ supportsUnknownElements = true;
+ }
+
+ }()); function addStyleSheet(ownerDocument, cssText) {
+ var p = ownerDocument.createElement('p'),
+ parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
+
+ p.innerHTML = 'x<style>' + cssText + '</style>';
+ return parent.insertBefore(p.lastChild, parent.firstChild);
+ }
+
+ function getElements() {
+ var elements = html5.elements;
+ return typeof elements == 'string' ? elements.split(' ') : elements;
+ }
+
+ function getExpandoData(ownerDocument) {
+ var data = expandoData[ownerDocument[expando]];
+ if (!data) {
+ data = {};
+ expanID++;
+ ownerDocument[expando] = expanID;
+ expandoData[expanID] = data;
+ }
+ return data;
+ }
+
+ function createElement(nodeName, ownerDocument, data){
+ if (!ownerDocument) {
+ ownerDocument = document;
+ }
+ if(supportsUnknownElements){
+ return ownerDocument.createElement(nodeName);
+ }
+ if (!data) {
+ data = getExpandoData(ownerDocument);
+ }
+ var node;
+
+ if (data.cache[nodeName]) {
+ node = data.cache[nodeName].cloneNode();
+ } else if (saveClones.test(nodeName)) {
+ node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
+ } else {
+ node = data.createElem(nodeName);
+ }
+
+ return node.canHaveChildren && !reSkip.test(nodeName) ? data.frag.appendChild(node) : node;
+ }
+
+ function createDocumentFragment(ownerDocument, data){
+ if (!ownerDocument) {
+ ownerDocument = document;
+ }
+ if(supportsUnknownElements){
+ return ownerDocument.createDocumentFragment();
+ }
+ data = data || getExpandoData(ownerDocument);
+ var clone = data.frag.cloneNode(),
+ i = 0,
+ elems = getElements(),
+ l = elems.length;
+ for(;i<l;i++){
+ clone.createElement(elems[i]);
+ }
+ return clone;
+ }
+
+ function shivMethods(ownerDocument, data) {
+ if (!data.cache) {
+ data.cache = {};
+ data.createElem = ownerDocument.createElement;
+ data.createFrag = ownerDocument.createDocumentFragment;
+ data.frag = data.createFrag();
+ }
+
+
+ ownerDocument.createElement = function(nodeName) {
+ if (!html5.shivMethods) {
+ return data.createElem(nodeName);
+ }
+ return createElement(nodeName, ownerDocument, data);
+ };
+
+ ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
+ 'var n=f.cloneNode(),c=n.createElement;' +
+ 'h.shivMethods&&(' +
+ getElements().join().replace(/\w+/g, function(nodeName) {
+ data.createElem(nodeName);
+ data.frag.createElement(nodeName);
+ return 'c("' + nodeName + '")';
+ }) +
+ ');return n}'
+ )(html5, data.frag);
+ } function shivDocument(ownerDocument) {
+ if (!ownerDocument) {
+ ownerDocument = document;
+ }
+ var data = getExpandoData(ownerDocument);
+
+ if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
+ data.hasCSS = !!addStyleSheet(ownerDocument,
+ 'article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}' +
+ 'mark{background:#FF0;color:#000}'
+ );
+ }
+ if (!supportsUnknownElements) {
+ shivMethods(ownerDocument, data);
+ }
+ return ownerDocument;
+ } var html5 = {
+
+ 'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video',
+
+ 'shivCSS': (options.shivCSS !== false),
+
+ 'supportsUnknownElements': supportsUnknownElements,
+
+ 'shivMethods': (options.shivMethods !== false),
+
+ 'type': 'default',
+
+ 'shivDocument': shivDocument,
+
+ createElement: createElement,
+
+ createDocumentFragment: createDocumentFragment
+ }; window.html5 = html5;
+
+ shivDocument(document);
+
+ }(this, document));
+
+ Modernizr._version = version;
+
+ Modernizr._prefixes = prefixes;
+ Modernizr._domPrefixes = domPrefixes;
+ Modernizr._cssomPrefixes = cssomPrefixes;
+
+
+ Modernizr.hasEvent = isEventSupported;
+
+ Modernizr.testProp = function(prop){
+ return testProps([prop]);
+ };
+
+ Modernizr.testAllProps = testPropsAll;
+
+
+ Modernizr.testStyles = injectElementWithStyles;
+ Modernizr.prefixed = function(prop, obj, elem){
+ if(!obj) {
+ return testPropsAll(prop, 'pfx');
+ } else {
+ return testPropsAll(prop, obj, elem);
+ }
+ };
+
+
+ docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') +
+
+ (enableClasses ? ' js ' + classes.join(' ') : '');
+
+ return Modernizr;
+
+})(this, this.document);
+/*yepnope1.5.4|WTFPL*/
+(function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}})(this,document);
+Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0));};
+;
--- a/ckanext/ga_report/templates/ga_report/ga_util.html
+++ b/ckanext/ga_report/templates/ga_report/ga_util.html
@@ -8,8 +8,8 @@
<select name="month" py:def="month_selector(current_month, months, day)">
<option value='' py:attrs="{'selected': 'selected' if not current_month else None}">All months</option>
- <py:for each="i, (val,desc) in enumerate(months)">
- <option value='${val}' py:attrs="{'selected': 'selected' if current_month == val else None}">${desc}<py:if test="i == 0 and day"> (up to ${day})</py:if></option>
+ <py:for each="(iso_code,string_name) in months">
+ <option value='${iso_code}' py:attrs="{'selected': 'selected' if current_month == iso_code else None}">${h.month_option_title(iso_code,months,day)}</option>
</py:for>
</select>
@@ -37,7 +37,6 @@
<script type="text/javascript">
$(function() {
var items = $items_json;
- if (items.length==0) { return; }
CKAN.GA_Reports.render_rickshaw('$id', items, '$mode', '$colorscheme');
});
</script>
@@ -58,23 +57,19 @@
</py:for>
</table>
-
-<div py:def="usage_nav(active_name)" id="minornavigation">
- <div id="minornavigation-bg-left">
- <div id="minornavigation-bg-right">
- <ul class="nav nav-pills">
- <?python querystring='?month='+c.month if c.month else '' ?>
- <li py:attrs="{'class': 'active' if active_name=='Site-wide' else None}"><a py:attrs="{'class': 'active' if active_name=='Site-wide' else None}" href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}${querystring}"><img src="/images/icons/page_white.png" height="16px" width="16px" alt="None" class="inline-icon "/> Site-wide</a></li>
- <li py:attrs="{'class': 'active' if active_name=='Publishers' else None}">
- <a py:attrs="{'class': 'active' if active_name=='Publishers' else None}" href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publishers')}${querystring}"><img src="/images/icons/page_white.png" height="16px" width="16px" alt="None" class="inline-icon "/> Publishers</a>
- </li>
- <li py:attrs="{'class': 'active' if active_name=='Datasets' else None}">
- <a py:attrs="{'class': 'active' if active_name=='Datasets' else None}" href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read')}${querystring}"><img src="/images/icons/page_white.png" height="16px" width="16px" alt="None" class="inline-icon "/> Datasets</a>
- </li>
+<py:def function="ga_footer()">
+ <div class="panel panel-default">
+ <div class="panel-heading"><strong>Notes</strong></div>
+ <div class="panel-body">
+ <ul>
+ <li>"Views" is the number of times a page was loaded in users' browsers.</li>
+ <li>"Downloads" is the number of times a user has clicked to download either an original or cached resource for a particular dataset. Download information is only available from 2nd December 2012; 'No data' is shown for records before that date.</li>
+ <li>These usage statistics are confined to users with javascript enabled, which excludes web crawlers and API calls.</li>
+ <li>The results are not shown when the number of views/visits is tiny. Where these relate to site pages, results are available in full in the CSV download. Where these relate to users' web browser information, results are not disclosed, for privacy reasons.</li>
</ul>
</div>
- </div>
-</div>
+ </div>
+</py:def>
</html>
--- a/ckanext/ga_report/templates/ga_report/notes.html
+++ /dev/null
@@ -1,16 +1,1 @@
-<html xmlns:py="http://genshi.edgewall.org/"
- xmlns:i18n="http://genshi.edgewall.org/i18n"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- py:strip="">
- <li class="widget-container boxed widget_text">
- <h4>Notes</h4>
- <ul>
- <li>"Views" is the number of times a page was loaded in users' browsers.</li>
- <li>"Downloads" is the number of times a user has clicked to download either an original or cached resource for a particular dataset. Download information is only available from 2nd December 2012; 'No data' is shown for records before that date.</li>
- <li>These usage statistics are confined to users with javascript enabled, which excludes web crawlers and API calls.</li>
- <li>The results are not shown when the number of views/visits is tiny. Where these relate to site pages, results are available in full in the CSV download. Where these relate to users' web browser information, results are not disclosed, for privacy reasons.</li>
- </ul>
- </li>
-</html>
-
--- a/ckanext/ga_report/templates/ga_report/publisher/index.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/index.html
@@ -7,25 +7,11 @@
<py:def function="page_title">Usage by Publisher</py:def>
- <py:match path="primarysidebar">
- <li class="widget-container boxed widget_text">
- <h4>Download</h4>
- <p><center>
- <a class="btn button btn-primary" href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publisher_csv',month=c.month or 'all')}">Download as CSV</a></center>
- </p>
- </li>
- <li class="widget-container boxed widget_text">
- <h4>Graph Legend</h4>
- <div id="graph-legend-container">
- </div>
- </li>
- <xi:include href="../notes.html" />
- </py:match>
-
<py:def function="optional_head">
<link rel="stylesheet" type="text/css" href="/scripts/vendor/rickshaw.min.css"/>
- <link rel="stylesheet" type="text/css" href="/css/ga_report.css"/>
- <script type="text/javascript" src="/scripts/ckanext_ga_reports.js"></script>
+ <link rel="stylesheet" type="text/css" href="/css/ga_report.css?1"/>
+ <script type="text/javascript" src="/scripts/modernizr-2.6.2.custom.js"></script>
+ <script type="text/javascript" src="/scripts/ckanext_ga_reports.js?1"></script>
<script type="text/javascript" src="/scripts/vendor/jquery.sparkline.modified.js"></script>
<script type="text/javascript" src="/scripts/rickshaw_ie7_shim.js"></script>
<script type="text/javascript" src="/scripts/vendor/d3.v2.js"></script>
@@ -33,38 +19,49 @@
<script type="text/javascript" src="/scripts/vendor/rickshaw.min.js"></script>
</py:def>
+ <py:match path="breadcrumbs">
+ <li><a href="/site-usage">Site Analytics</a></li>
+ <li><a href="/site-usage/publisher">Publishers</a></li>
+ </py:match>
+
<div py:match="content">
+ <py:with vars="download_link=h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publisher_csv',month=c.month or 'all')">
+ <a class="btn button btn-primary btn-sm pull-right" href="${download_link}"><i class="icon-download"></i> Download as CSV</a>
+ </py:with>
+ <h1>Site Usage</h1>
- <h1>Site Usage</h1>
+ <div class="row" style="background: #fff;">
+ <div class="col-md-8">
+ <div class="whitebox">
+ <strong>Publishers</strong>
+ ${rickshaw_graph(c.top_publishers_graph,'publishers')}
+ </div>
+ </div>
+ </div>
- ${usage_nav('Publishers')}
-
-
- ${rickshaw_graph(c.top_publishers_graph,'publishers')}
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publishers')}" method="get">
+ <hr/>
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='publishers')}" method="get">
<div class="controls">
- <label>Show stats table for:</label>
- ${month_selector(c.month, c.months, c.day)}
+ <h2 class="ga-reports-heading">Statistics for</h2>
+ ${month_selector(c.month, c.months, c.day)}
</div>
</form>
- <table class="ga-reports-table table table-condensed table-bordered table-striped">
- <tr>
- <th>Publisher</th>
- <th class="td-numeric">Dataset Views</th>
- </tr>
- <py:for each="publisher, views, visits in c.top_publishers">
- <tr>
- <td>
- ${h.link_to(publisher.title, h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport', action='read_publisher', id=publisher.name) + (("?month=" + c.month) if c.month else ''))}
- </td>
- <td class="td-numeric">${views}</td>
- </tr>
- </py:for>
- </table>
-
-
- </div>
+ <table class="ga-reports-table table table-condensed table-bordered table-striped">
+ <tr>
+ <th>Publisher</th>
+ <th class="td-numeric">Dataset Views</th>
+ </tr>
+ <py:for each="publisher, views, visits in c.top_publishers">
+ <tr>
+ <td>
+ ${h.link_to(publisher.title, h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport', action='read_publisher', id=publisher.name) + (("?month=" + c.month) if c.month else ''))}
+ </td>
+ <td class="td-numeric">${views}</td>
+ </tr>
+ </py:for>
+ </table>
+ </div><!--/content-->
<py:def function="optional_footer">
<script type="text/javascript">
@@ -73,7 +70,7 @@
});
</script>
</py:def>
- <xi:include href="../../layout.html" />
+ <xi:include href="../site/layout.html" />
</html>
--- a/ckanext/ga_report/templates/ga_report/publisher/popular.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/popular.html
@@ -15,7 +15,12 @@
</li>
</py:for>
</ul>
- <p class="">${h.link_to("More usage data for " + publisher.title, h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read_publisher',id=publisher.name))}</p>
+ <p>
+ <a href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read_publisher',id=publisher.name)}">
+ <img py:if="preview_image" src="${preview_image}" /><br/>
+ More usage data for ${publisher.title}
+ </a>
+ </p>
</div>
</py:if>
</html>
--- a/ckanext/ga_report/templates/ga_report/publisher/read.html
+++ b/ckanext/ga_report/templates/ga_report/publisher/read.html
@@ -9,8 +9,9 @@
<py:def function="optional_head">
<link rel="stylesheet" type="text/css" href="/scripts/vendor/rickshaw.min.css"/>
- <link rel="stylesheet" type="text/css" href="/css/ga_report.css"/>
- <script type="text/javascript" src="/scripts/ckanext_ga_reports.js"></script>
+ <link rel="stylesheet" type="text/css" href="/css/ga_report.css?1"/>
+ <script type="text/javascript" src="/scripts/modernizr-2.6.2.custom.js"></script>
+ <script type="text/javascript" src="/scripts/ckanext_ga_reports.js?1"></script>
<script type="text/javascript" src="/scripts/vendor/jquery.sparkline.modified.js"></script>
<script type="text/javascript" src="/scripts/rickshaw_ie7_shim.js"></script>
<script type="text/javascript" src="/scripts/vendor/d3.v2.js"></script>
@@ -18,72 +19,83 @@
<script type="text/javascript" src="/scripts/vendor/rickshaw.min.js"></script>
</py:def>
- <py:match path="primarysidebar">
- <li class="widget-container boxed widget_text">
- <h4>Download</h4>
- <p><center>
- <a class="btn button btn-primary" href="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='dataset_csv',id=c.publisher_name or 'all',month=c.month or 'all')}">Download as CSV</a></center>
- </p>
- </li>
- <li class="widget-container boxed widget_text">
- <h4>Graph Legend</h4>
- <div id="graph-legend-container">
- </div>
- </li>
- <xi:include href="../notes.html" />
+
+ <py:match path="breadcrumbs">
+ <li><a href="/site-usage">Site Analytics</a></li>
+ <py:if test="c.publisher">
+ <li><a href="/site-usage/publisher">Publishers</a></li>
+ <li py:if="c.publisher"><a href="/site-usage/publisher/${c.publisher.name}">${c.publisher.title}</a></li>
+ </py:if>
+ <py:if test="not c.publisher">
+ <li><a href="${request.url}">Usage By Dataset</a></li>
+ </py:if>
</py:match>
+ <div py:match="content">
- <div py:match="content">
- <h1>Site Usage</h1>
+ <py:with vars="download_link=h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='dataset_csv',id=c.publisher_name or 'all',month=c.month or 'all')">
+ <a class="btn button btn-primary btn-sm pull-right" href="${download_link}"><i class="icon-download"></i> Download as CSV</a>
+ </py:with>
+ <h1>Site Usage
+ <small py:if="c.publisher">${c.publisher.title}</small>
+ <small py:if="not c.publisher">All datasets</small>
+ </h1>
- ${usage_nav('Datasets')}
+ <div class="row" style="background: #fff;">
+ <div class="col-md-8">
+ <div class="whitebox">
+ <py:if test="c.graph_data">
+ ${rickshaw_graph(c.graph_data,'dataset-downloads',debug=True)}
+ </py:if>
+ </div>
+ </div>
+ </div>
+ <hr/>
+ <py:if test="c.month">
+ <h4>Statistics for ${h.month_option_title(c.month,c.months,c.day)}:</h4>
+ </py:if>
+ <py:if test="not c.month">
+ <h2>Statistics for all months</h2>
+ </py:if>
+ <form style="margin-bottom:10px;" class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read')}" method="get">
+ <div class="controls">
+ ${month_selector(c.month, c.months, c.day)}
- <h3 py:if="c.publisher"><a href="${h.url_for(controller='ckanext.dgu.controllers.publisher:PublisherController',action='read',id=c.publisher.name)}">${c.publisher.title}</a></h3>
+ <select name="publisher">
+ <option value='' py:attrs="{'selected': 'selected' if not c.publisher else None}">All publishers</option>
+ <py:for each="val,desc in c.publishers">
+ <option value='${val}' py:attrs="{'selected': 'selected' if c.publisher_name == val else None}">${desc}</option>
+ </py:for>
+ </select>
+ <input class="btn button btn-primary btn-xs" type='submit' value="Update"/>
+ </div>
+ </form>
+ <div class="alert alert-info" py:if="not c.top_packages">No page views in this period.</div>
+ <py:if test="c.top_packages">
+ <table class="ga-reports-table table table-condensed table-bordered">
+ <tr>
+ <th>Dataset</th>
+ <th>Views</th>
+ <th>Downloads</th>
+ </tr>
+ <py:for each="package, views, visits,downloads in c.top_packages">
+ <tr>
+ <td>
+ ${h.link_to(package.title or package.name, h.url_for(controller='package', action='read', id=package.name))}
+ </td>
+ <td class="td-numeric">${views}</td>
+ <td class="td-numeric">${downloads}</td>
+ </tr>
+ </py:for>
+ </table>
+ </py:if>
- <p py:if="not c.top_packages">No page views in this period</p>
-
- <py:if test="c.top_packages">
- ${rickshaw_graph(c.graph_data,'dataset-downloads',debug=True)}
- </py:if>
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaDatasetReport',action='read')}" method="get">
- <div class="controls">
-
- ${month_selector(c.month, c.months, c.day)}
-
- <select name="publisher">
- <option value='' py:attrs="{'selected': 'selected' if not c.publisher else None}">All publishers</option>
- <py:for each="val,desc in c.publishers">
- <option value='${val}' py:attrs="{'selected': 'selected' if c.publisher_name == val else None}">${desc}</option>
- </py:for>
- </select>
- <input class="btn button btn-primary" type='submit' value="Update"/>
- </div>
- </form>
- <py:if test="c.top_packages">
- <table class="ga-reports-table table table-condensed table-bordered table-striped">
- <tr>
- <th>Dataset</th>
- <th>Views</th>
- <th>Downloads</th>
- </tr>
- <py:for each="package, views, visits,downloads in c.top_packages">
- <tr>
- <td>
- ${h.link_to(package.title or package.name, h.url_for(controller='package', action='read', id=package.name))}
- </td>
- <td class="td-numeric">${views}</td>
- <td class="td-numeric">${downloads}</td>
- </tr>
- </py:for>
- </table>
- </py:if>
-
+ ${ga_footer()}
</div>
- <xi:include href="../../layout.html" />
+ <xi:include href="../site/layout.html" />
</html>
--- a/ckanext/ga_report/templates/ga_report/site/downloads.html
+++ b/ckanext/ga_report/templates/ga_report/site/downloads.html
@@ -7,20 +7,14 @@
<py:def function="page_title">Downloads</py:def>
- <py:match path="primarysidebar">
- <li py:if="c.downloads" class="widget-container boxed widget_text">
- <h4>Download</h4>
- <p><center>
- <a class="btn button btn-primary" href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='csv_downloads',month=c.month or 'all')}">Download as CSV</a></center>
- </p>
- </li>
- <xi:include href="../notes.html" />
+ <py:def path="sidebar">
+ ${ga_sidebar(download_link=h.url_for(controller='ckanext.ga_report.controller:GaReport',action='csv_downloads',month=c.month or 'all'))}
+ </py:def>
- </py:match>
+ <py:def function="page_heading">Downloads ${usage_nav('Downloads')}</py:def>
+ <div py:match="content">
- <div py:match="content">
- <h1>Downloads</h1>
- ${usage_nav('Downloads')}
+ <div class="boxed">
<form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='downloads')}" method="get">
<div class="controls">
@@ -38,6 +32,7 @@
<h4>No data</h4>
<p>There is no download data available for this month</p>
</py:if>
+ </div>
</div>
<xi:include href="../../layout.html" />
--- a/ckanext/ga_report/templates/ga_report/site/index.html
+++ b/ckanext/ga_report/templates/ga_report/site/index.html
@@ -8,156 +8,178 @@
<py:def function="page_title">Site usage</py:def>
<py:def function="optional_head">
- <link rel="stylesheet" type="text/css" href="/scripts/vendor/rickshaw.min.css"/>
- <link rel="stylesheet" type="text/css" href="/css/ga_report.css"/>
- <script type="text/javascript" src="/scripts/ckanext_ga_reports.js"></script>
- <script type="text/javascript" src="/scripts/vendor/jquery.sparkline.modified.js"></script>
- <script type="text/javascript" src="/scripts/rickshaw_ie7_shim.js"></script>
- <script type="text/javascript" src="/scripts/vendor/d3.v2.js"></script>
- <script type="text/javascript" src="/scripts/vendor/d3.layout.min.js"></script>
- <script type="text/javascript" src="/scripts/vendor/rickshaw.min.js"></script>
</py:def>
- <py:match path="primarysidebar">
- <li class="widget-container boxed widget_text">
- <h4>Download</h4>
- <p><center>
- <a class="btn button btn-primary" href="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='csv',month=c.month or 'all')}">Download as CSV</a></center>
- </p>
- </li>
- <li class="widget-container boxed widget_text">
- <h4>Graph Legend</h4>
- <div id="graph-legend-container">
- <div id="legend_none">(No graph loaded)</div>
+ <py:match path="breadcrumbs">
+ <li><a href="/site-usage">Site Analytics</a></li>
+ <li><a href="/site-usage">Site-wide</a></li>
+ </py:match>
+
+ <div py:match="content">
+ <div class="row">
+ <div class="col-sm-7 col-md-8 col-lg-9">
+ <py:with vars="download_link=h.url_for(controller='ckanext.ga_report.controller:GaReport',action='csv',month=c.month or 'all')">
+ <a class="btn button btn-primary btn-sm pull-right" href="${download_link}"><i class="icon-download"></i> Download as CSV</a>
+ </py:with>
+ <h1>Site Usage</h1>
</div>
- </li>
- <xi:include href="../notes.html" />
-
- </py:match>
-
- <div py:match="content">
- <h1>Site Usage</h1>
- ${usage_nav('Site-wide')}
-
- <div class="tabbable">
- <ul class="nav nav-tabs">
- <li><a href="#totals" data-toggle="hashchange">Totals</a></li>
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown">Browsers
- <b class="caret"></b></a>
- <ul class="dropdown-menu">
- <li><a href="#browsers_names" data-toggle="hashchange">Browsers</a></li>
- <li><a href="#browsers_versions" data-toggle="hashchange">Versions</a></li>
+ <div class="col-sm-5 col-md-4 col-lg-3">
+ <div class="panel panel-default">
+ <div class="panel-heading"><strong>Jump To...</strong></div>
+ <div class="panel-body">
+ <ul>
+ <li><a href="/site-usage/publisher">Publisher Usage Statistics</a></li>
+ <li><a href="/site-usage/dataset">Dataset Usage Statistics</a></li>
</ul>
- </li>
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown">Operating Systems
- <b class="caret"></b></a>
- <ul class="dropdown-menu">
- <li><a href="#os" data-toggle="hashchange">Operating Systems</a></li>
- <li><a href="#os_versions" data-toggle="hashchange">Versions</a></li>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="row" style="background: #fff;">
+ <div class="col-md-8">
+ <div class="whitebox">
+ <div class="tabbable">
+ <ul class="nav nav-tabs">
+ <li class="active"><a href="#totals" data-hash="totals" data-toggle="tab">Totals</a></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Browsers
+ <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#browsers_names" data-hash="browsers_names" data-toggle="tab">Browsers</a></li>
+ <li><a href="#browsers_versions" data-hash="browsers_versions" data-toggle="tab">Versions</a></li>
+ </ul>
+ </li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Operating Systems
+ <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#os" data-hash="os" data-toggle="tab">Operating Systems</a></li>
+ <li><a href="#os_versions" data-hash="os_versions" data-toggle="tab">Versions</a></li>
+ </ul>
+ </li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Social
+ <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#social_networks" data-hash="social_networks" data-toggle="tab">All networks</a></li>
+ <li><a href="#social_referrals_totals" data-hash="social_referrals_totals" data-toggle="tab">Referral links</a></li>
+ </ul>
+ </li>
+ <li><a href="#languages" data-hash="languages" data-toggle="tab">Languages</a></li>
+ <li><a href="#country" data-hash="country" data-toggle="tab">Country</a></li>
</ul>
- </li>
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown">Social
- <b class="caret"></b></a>
- <ul class="dropdown-menu">
- <li><a href="#social_networks" data-toggle="hashchange">All networks</a></li>
- <li><a href="#social_referrals_totals" data-toggle="hashchange">Referral links</a></li>
- </ul>
- </li>
- <li><a href="#languages" data-toggle="hashchange">Languages</a></li>
- <li><a href="#country" data-toggle="hashchange">Country</a></li>
- </ul>
- <div class="tab-content">
- <div class="tab-pane" id="totals">
- <table class="ga-reports-table table table-condensed table-bordered table-striped">
- <tr>
- <th>Name</th>
- <th class="td-numeric">Value</th>
- <th>History</th>
- </tr>
- <py:for each="name, value, graph in c.global_totals">
- <tr>
- <td>${name}</td>
- <td class="td-numeric">${value}</td>
- <td class="sparkline-cell">
- <span class="sparkline" sparkTooltips="${','.join([x for x,y in graph])}">
- ${','.join([y for x,y in graph])}
- </span>
- </td>
- </tr>
- </py:for>
- </table>
- </div>
- <div class="tab-pane" id="browsers_versions">
- ${rickshaw_graph(c.browser_versions_graph,'browser-versions',mode='stack')}
- <p>Note: Where a browser has a large number of versions, these have been grouped together.</p>
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
- <label>Show stats table for:</label>
- ${month_selector(c.month, c.months, c.day)}
- </form>
- ${stat_table(c.browser_versions)}
- </div>
- <div class="tab-pane" id="browsers_names">
- ${rickshaw_graph(c.browsers_graph,'browsers',mode='stack')}
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
- <label>Show stats table for:</label>
- ${month_selector(c.month, c.months, c.day)}
- </form>
- ${stat_table(c.browsers)}
- </div>
- <div class="tab-pane" id="os">
- ${rickshaw_graph(c.os_graph,'os',mode='stack')}
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
- <label>Show stats table for:</label>
- ${month_selector(c.month, c.months, c.day)}
- </form>
- ${stat_table(c.os)}
- </div>
- <div class="tab-pane" id="os_versions">
- ${rickshaw_graph(c.os_versions_graph,'os_versions',mode='stack')}
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
- <label>Show stats table for:</label>
- ${month_selector(c.month, c.months, c.day)}
- </form>
- ${stat_table(c.os_versions)}
- </div>
- <div class="tab-pane" id="social_referrals_totals">
- <p>Number of visits that were referred from social networks</p>
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
- <label>Show stats table for:</label>
- ${month_selector(c.month, c.months, c.day)}
- </form>
- ${social_table(c.social_referrer_totals)}
- </div>
- <div class="tab-pane" id="social_networks">
- ${rickshaw_graph(c.social_networks_graph, 'social_networks',mode='stack')}
- <p>Percentage of visits that were referred from these social networks</p>
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
- <label>Show stats table for:</label>
- ${month_selector(c.month, c.months, c.day)}
- </form>
- ${stat_table(c.social_networks, 'Visits')}
- </div>
- <div class="tab-pane" id="languages">
- ${rickshaw_graph(c.languages_graph,'languages',mode='stack')}
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
- <label>Show stats table for:</label>
- ${month_selector(c.month, c.months, c.day)}
- </form>
- ${stat_table(c.languages)}
- </div>
- <div class="tab-pane" id="country">
- ${rickshaw_graph(c.country_graph,'country',mode='stack')}
- <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
- <label>Show stats table for:</label>
- ${month_selector(c.month, c.months, c.day)}
- </form>
- ${stat_table(c.country)}
- </div>
+ <div class="tab-content">
+ <div class="tab-pane active" id="totals">
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+ <h4 class="ga-reports-heading">Show stats table for:</h4>
+ ${month_selector(c.month, c.months, c.day)}
+ </form>
+ <table class="ga-reports-table table table-condensed table-bordered table-striped">
+ <tr>
+ <th>Name</th>
+ <th class="td-numeric">Value</th>
+ <th>History</th>
+ </tr>
+ <py:for each="name, value, graph in c.global_totals">
+ <tr>
+ <td>${name}</td>
+ <td class="td-numeric">${value}</td>
+ <td class="sparkline-cell">
+ <span class="sparkline" sparkTooltips="${','.join([x for x,y in graph])}">
+ ${','.join([y for x,y in graph])}
+ </span>
+ </td>
+ </tr>
+ </py:for>
+ </table>
+ </div>
+ <div class="tab-pane" id="browsers_versions">
+ ${rickshaw_graph(c.browser_versions_graph,'browser-versions',mode='stack')}
+ <hr/>
+ <p>Note: Where a browser has a large number of versions, these have been grouped together.</p>
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+ <h4 class="ga-reports-heading">Show stats table for:</h4>
+ ${month_selector(c.month, c.months, c.day)}
+ </form>
+ <hr/>
+ ${stat_table(c.browser_versions)}
+ </div>
+ <div class="tab-pane" id="browsers_names">
+ ${rickshaw_graph(c.browsers_graph,'browsers',mode='stack')}
+ <hr/>
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+ <h4 class="ga-reports-heading">Show stats table for:</h4>
+ ${month_selector(c.month, c.months, c.day)}
+ </form>
+ <hr/>
+ ${stat_table(c.browsers)}
+ </div>
+ <div class="tab-pane" id="os">
+ ${rickshaw_graph(c.os_graph,'os',mode='stack')}
+ <hr/>
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+ <h4 class="ga-reports-heading">Show stats table for:</h4>
+ ${month_selector(c.month, c.months, c.day)}
+ </form>
+ <hr/>
+ ${stat_table(c.os)}
+ </div>
+ <div class="tab-pane" id="os_versions">
+ ${rickshaw_graph(c.os_versions_graph,'os_versions',mode='stack')}
+ <hr/>
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+ <h4 class="ga-reports-heading">Show stats table for:</h4>
+ ${month_selector(c.month, c.months, c.day)}
+ </form>
+ <hr/>
+ ${stat_table(c.os_versions)}
+ </div>
+ <div class="tab-pane" id="social_referrals_totals">
+ <p>Number of visits that were referred from social networks</p>
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+ <h4 class="ga-reports-heading">Show stats table for:</h4>
+ ${month_selector(c.month, c.months, c.day)}
+ </form>
+ ${social_table(c.social_referrer_totals)}
+ </div>
+ <div class="tab-pane" id="social_networks">
+ ${rickshaw_graph(c.social_networks_graph, 'social_networks',mode='stack')}
+ <hr/>
+ <p>Percentage of visits that were referred from these social networks</p>
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+ <h4 class="ga-reports-heading">Show stats table for:</h4>
+ ${month_selector(c.month, c.months, c.day)}
+ </form>
+ <hr/>
+ ${stat_table(c.social_networks, 'Visits')}
+ </div>
+ <div class="tab-pane" id="languages">
+ ${rickshaw_graph(c.languages_graph,'languages',mode='stack')}
+ <hr/>
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+ <h4 class="ga-reports-heading">Show stats table for:</h4>
+ ${month_selector(c.month, c.months, c.day)}
+ </form>
+ <hr/>
+ ${stat_table(c.languages)}
+ </div>
+ <div class="tab-pane" id="country">
+ ${rickshaw_graph(c.country_graph,'country',mode='stack')}
+ <hr/>
+ <form class="form-inline" action="${h.url_for(controller='ckanext.ga_report.controller:GaReport',action='index')}" method="get">
+ <h4 class="ga-reports-heading">Show stats table for:</h4>
+ ${month_selector(c.month, c.months, c.day)}
+ </form>
+ <hr/>
+ ${stat_table(c.country)}
+ </div>
+ </div>
+ </div>
</div>
- </div>
+ </div>
+ </div>
+
</div>
@@ -167,17 +189,11 @@
CKAN.GA_Reports.bind_sparklines();
CKAN.GA_Reports.bind_sidebar();
CKAN.GA_Reports.bind_month_selector();
- if (!window.location.hash) {
- window.location.hash='totals';
- }
- else {
- $(window).trigger('hashchange');
- }
- });
+ });
</script>
</py:def>
- <xi:include href="../../layout.html" />
+ <xi:include href="layout.html" />
</html>
--- /dev/null
+++ b/ckanext/ga_report/templates/ga_report/site/layout.html
@@ -1,1 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:i18n="http://genshi.edgewall.org/i18n"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ py:strip="">
+ <xi:include href="layout_base.html" />
+</html>
--- /dev/null
+++ b/ckanext/ga_report/templates/ga_report/site/layout_base.html
@@ -1,1 +1,357 @@
-
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!--[if IE 8 ]> <html class="no-js ie8" lang="en"> <![endif]-->
+ <!--[if (gte IE 9)|!(IE)]><!--> <html class="no-js" lang="en"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:i18n="http://genshi.edgewall.org/i18n"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ > <!--<![endif]-->
+<xi:include href="../../_util.html" />
+<head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+
+ <title>${page_title()} - ${g.site_title}</title>
+ <meta name="description" content="" />
+ <meta name="author" content="" />
+
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <link rel="shortcut icon" href="${h.url_for_static(g.favicon)}" />
+
+ <py:choose>
+ <py:when test="defined('optional_feed')">
+ ${optional_feed()}
+ </py:when>
+ <py:otherwise>
+ <link rel="alternate" type="application/atom+xml" title="${g.site_title} - Recent Revision History" href="${h.url_for(controller='revision', action='list', format='atom', days=1)}" />
+ </py:otherwise>
+ </py:choose>
+ <link href='http://fonts.googleapis.com/css?family=Ubuntu:400,700' rel='stylesheet' type='text/css' />
+
+ <link rel="stylesheet" href="${h.url_for_static('/scripts/vendor/jqueryui/1.8.14/css/jquery-ui.custom.css')}" type="text/css" media="screen, print" />
+ <link rel="stylesheet" href="${h.url_for_static('/css/bootstrap.min.css')}" type="text/css" media="screen, projection" />
+ <link rel="stylesheet" href="${h.url_for_static('/css/chosen.css')}" type="text/css" />
+ <link rel="stylesheet" href="${h.url_for_static('/css/style.css?v=2')}" />
+ ${jsConditionalForIe(9, '<script type="text/javascript" src="' + h.url_for_static('/scripts/vendor/html5shiv/html5.js') + '"></script>')}
+<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
+<script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
+ <link rel="stylesheet" type="text/css" href="/scripts/vendor/rickshaw.min.css"/>
+ <link rel="stylesheet" type="text/css" href="/css/ga_report.css?1"/>
+ <script type="text/javascript" src="/scripts/modernizr-2.6.2.custom.js"></script>
+ <script type="text/javascript" src="/scripts/vendor/jquery.sparkline.modified.js"></script>
+ <script type="text/javascript" src="/scripts/ckanext_ga_reports.js?1"></script>
+ <script type="text/javascript" src="/scripts/rickshaw_ie7_shim.js"></script>
+ <script type="text/javascript" src="/scripts/vendor/d3.v2.js"></script>
+ <script type="text/javascript" src="/scripts/vendor/d3.layout.min.js"></script>
+ <script type="text/javascript" src="/scripts/vendor/rickshaw.min.js"></script>
+<style>
+#pre-content #breadcrumbs {
+ padding-left: 0;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ margin-bottom: 0;
+ list-style-type: none;
+ color: #ccc
+}
+
+#pre-content #breadcrumbs li {
+ color: #999;
+ display: inline-block;
+ zoom: 1;
+ *display: inline;
+ margin-right: 8px
+}
+
+#pre-content #breadcrumbs li:after {
+ content: '/';
+ display: inline-block;
+ zoom: 1;
+ *display: inline;
+ margin-left: 12px
+}
+
+#pre-content #breadcrumbs li:last-child:after {
+ display: none
+}
+
+#pre-content #breadcrumbs a {
+ color: #999
+}
+
+#pre-content #breadcrumbs a:hover {
+ color: #000;
+ text-decoration: none
+}
+
+#pre-content #breadcrumbs .spacer {
+ color: #ccc
+}
+</style>
+ <py:if test="defined('optional_head')">
+ ${optional_head()}
+ </py:if>
+
+ ${h.literal(getattr(g, 'template_head_end', ''))}
+</head>
+
+<body class="${request.environ.get('pylons.routes_dict', {}).get('action')}
+ ${request.environ.get('pylons.routes_dict', {}).get('controller').split(':')[-1]}
+ ${defined('body_class') and body_class()}
+ ">
+
+ <div id="wrap">
+ <div class="header outer">
+ <header class="container">
+ <div class="menu account">
+ <span class="ckan-logged-in" style="display: none;">
+ <a href="${h.url_for(controller='user',action='me')}">${h.gravatar((c.userobj.email_hash if c and c.userobj else ''),size=20)}${c.user}</a>
+ <a href="${h.url_for('/user/_logout')}">Logout</a>
+ </span>
+ <span class="ckan-logged-out">
+ <a href="${h.url_for(controller='user',action='login')}">Login</a>
+ <a href="${h.url_for(controller='user',action='register')}">Register</a>
+ </span>
+ </div>
+ <a href="${h.url('home')}">
+ <img width="196" src="${h.url_for_static(g.site_logo)}" alt="${g.site_title} Logo" title="${g.site_title} Logo" id="logo" />
+ </a>
+ <div id="site-name">
+<!-- <a href="${h.url('home')}">${g.site_title} — ${g.site_description}</a>-->
+ </div>
+ <div class="menu">
+<!-- <span id="menusearch">
+ <form action="${h.url(controller='package', action='search')}" method="GET">
+ <input name="q" value="${c.q if hasattr(c, 'q') else ''}" class="search" placeholder="${_('Find datasets')}" />
+ </form>
+ </span>
+ <div id="mainmenu">
+ <span py:if="h.check_access('package_create')">${h.nav_link(_('Add a dataset'), controller='package', action='new')}</span>
+ ${h.nav_link(_('Search'), controller='package', action='search', highlight_actions = 'new index')}
+ ${h.nav_link(_('Groups'), named_route='%s_index' % h.default_group_type())}
+ ${h.nav_link(_('About'), controller='home', action='about')}
+ </div>-->
+ </div>
+ </header>
+ </div>
+ <div id="pre-content">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-12">
+ <ul id="breadcrumbs">
+ <li><a href="/"><i class="icon-home"></i></a></li>
+ <breadcrumbs>
+ </breadcrumbs>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <py:with vars="messages = list(h.flash.pop_messages())">
+ <div class="flash-messages container">
+ <div class="alert ${m.category}" py:for="m in messages">
+ ${h.literal(m)}
+ </div>
+ </div>
+ </py:with>
+
+ <div id="main" class="container" role="main">
+ <h1 py:if="defined('page_heading')" class="page_heading">
+ <img py:if="defined('page_logo')" id="page-logo" src="${page_logo()}" alt="Page Logo" />
+<!-- ${page_heading()} -->
+ </h1>
+ <div class="row">
+ <div class="span12">
+ <div id="minornavigation">
+ <minornavigation></minornavigation>
+ </div>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="span9 content-outer">
+ <div id="content">
+ <py:if test="defined('content')">
+ ${content()}
+ </py:if>
+ <content>
+ <p>Master content template placeholder … please replace me.</p>
+ </content>
+ </div> <!-- /content -->
+ </div>
+ <div class="span3 sidebar-outer">
+ <div id="sidebar">
+ <div class="col-md-4">
+ <div class="whitebox">
+ <strong>Graph Legend</strong>
+ <div id="graph-legend-container">
+ <div style="display: none;" id="legend_none">(No graph is loaded)</div>
+ </div>
+ </div>
+ </div>
+
+ <ul class="widget-list">
+ <py:if test="defined('primary_sidebar_extras')">
+ ${primary_sidebar_extras()}
+ </py:if>
+ <primarysidebar>
+ <!-- Primary Side Bar Goes Here -->
+ </primarysidebar>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ <br/><br/>
+ </div>
+ <div class="clearfix"></div>
+ <div class="footer outer">
+ <footer class="container">
+ <div class="row">
+<!-- <div class="span3">
+ <h3 class="widget-title">About ${g.site_title}</h3>
+ <div class="textwidget">
+ <ul>
+ <li>${h.link_to(_('About'), h.url_for(controller='home', action='about'))}</li>
+ <li>
+ <a href="http://twitter.com/ckanproject">Twitter @ckanproject</a>
+ </li>
+ <li>${h.link_to(_('API'), h.url_for(controller='api', action='get_api', ver=1))}</li>
+ <li>${h.link_to(_('API Docs'), 'http://docs.ckan.org/en/latest/api.html')}</li>
+ <li>
+ <a href="http://ckan.org/contact/">Contact Us</a>
+ </li>
+ <li>
+ <a href="http://okfn.org/privacy-policy/">Privacy Policy</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="span3">
+ <h3 class="widget-title">Sections</h3>
+ <div class="textwidget">
+ <ul>
+ <li>
+ <a href="${h.url(controller='user', action='index')}">
+ Users
+ </a>
+ </li>
+ <li>
+ <a href="${h.url(controller='tag', action='index')}">
+ Tags
+ </a>
+ </li>
+ <li py:if="'stats' in config.get('ckan.plugins','').split(' ')">
+ <a href="${h.url('stats')}">
+ Statistics
+ </a>
+ </li>
+ <li>
+ <a href="${h.url(controller='revision', action='index')}">
+ Revisions
+ </a>
+ </li>
+ <li>
+ <a href="${h.url_for('ckanadmin_index')}">
+ Site Admin
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="span3">
+ <h3 class="widget-title">Languages</h3>
+ <div class="textwidget">
+ <ul>
+<?python
+ current_url = request.environ['CKAN_CURRENT_URL']
+?>
+ <li py:for="locale in h.get_available_locales()">
+ <a href="${h.url(current_url, locale=str(locale))}">
+ ${locale.display_name or locale.english_name}
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>-->
+ <div class="span12">
+ <p id="credits">
+ © 2012-2014
+ <img src="//assets.okfn.org/images/logo/okf_logo_white_and_green_tiny.png" id="footer-okf-logo" />
+ <a href="http://okfn.org/">Open Knowledge Foundation</a>
+ Licensed under the <a href="http://opendatacommons.org/licenses/odbl/1.0/">Open Database License</a>
+ <a href="http://opendefinition.org/"><img alt="This Content and Data is Open" src="//assets.okfn.org/images/ok_buttons/od_80x15_blue.png" style="border: none;"/></a>
+
+ <br/><br/>
+ Powered by <a href="http://ckan.org">CKAN</a> v${c.__version__}.<br/>
+ </p>
+ </div>
+ </div>
+ </footer>
+ </div> <!-- eo #container -->
+
+ <div style="display:none;" id="scripts">
+ <!--<script src="${h.url_for_static('/scripts/vendor/jquery/1.7.1/jquery.js')}"></script>-->
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/json2.js')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/jquery.tmpl/beta1/jquery.tmpl.js')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/jquery.cookie/jquery.cookie.min.js')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/jquery.chosen/0.9.7/chosen.js')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/jquery.placeholder/jquery.placeholder.js')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/jqueryui/1.8.14/jquery-ui.min.js')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/bootstrap/2.0.3/bootstrap.min.js')}"></script>
+
+ <!-- for application.js -->
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/underscore/1.1.6/underscore.js')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/backbone/0.5.1/backbone.js')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/jquery.fileupload/20110801/jquery.iframe-transport.js')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/vendor/jquery.fileupload/20110801/jquery.fileupload.js')}"></script>
+
+ <!-- Translated js strings live inside an html template. -->
+ <xi:include href="../../js_strings.html" />
+ <!-- finally our application js that sets everything up-->
+ <script type="text/javascript" src="${h.url_for_static('/scripts/application.js?lang=${c.locale}')}"></script>
+ <script type="text/javascript" src="${h.url_for_static('/scripts/templates.js')}"></script>
+
+ <script type="text/javascript">
+ CKAN.plugins = [
+ // Declare js array from Python string
+ ${['\'%s\', '%s for s in config.get('ckan.plugins','').split(' ')]}
+ ];
+ <py:if test="config.get('ckan.storage.bucket', '')">
+ CKAN.plugins.push('storage');
+ </py:if>
+ CKAN.SITE_URL = '${h.url('/')}';
+ CKAN.SITE_URL_NO_LOCALE = '${h.url('/', locale='default')}';
+ CKAN.LANG = '${h.lang()}';
+ // later use will add offsets with leading '/' so ensure no trailing slash
+ CKAN.SITE_URL = CKAN.SITE_URL.replace(/\/$/, '');
+ CKAN.SITE_URL_NO_LOCALE = CKAN.SITE_URL_NO_LOCALE.replace(/\/$/, '');
+ $(document).ready(function() {
+ var ckan_user = '${c.user}';
+ if (ckan_user) {
+ $(".ckan-logged-out").hide();
+ $(".ckan-logged-in").show();
+ }
+ $('input[placeholder], textarea[placeholder]').placeholder();
+
+ $(".chzn-select").chosen();
+ });
+ </script>
+
+ <py:if test="h.asbool(config.get('ckan.tracking_enabled', 'false'))">
+ ${ h.snippet('snippets/internal-tracking.html') }
+ </py:if>
+ </div> <!-- #scripts -->
+
+ <py:if test="defined('optional_footer')">
+ ${optional_footer()}
+ </py:if>
+
+${h.literal(getattr(g, 'template_footer_end', ''))}
+</body>
+</html>
+
+
+
+