[304] most_popular_datasets can render a preview image
[304] most_popular_datasets can render a preview image

import logging import logging
import operator import operator
   
import ckan.lib.base as base import ckan.lib.base as base
import ckan.model as model import ckan.model as model
from ckan.logic import get_action from ckan.logic import get_action
   
from ckanext.ga_report.ga_model import GA_Url, GA_Publisher from ckanext.ga_report.ga_model import GA_Url, GA_Publisher
from ckanext.ga_report.controller import _get_publishers from ckanext.ga_report.controller import _get_publishers
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
   
def popular_datasets(count=10): def popular_datasets(count=10):
import random import random
   
publisher = None publisher = None
publishers = _get_publishers(30) publishers = _get_publishers(30)
total = len(publishers) total = len(publishers)
while not publisher or not datasets: while not publisher or not datasets:
rand = random.randrange(0, total) rand = random.randrange(0, total)
publisher = publishers[rand][0] publisher = publishers[rand][0]
if not publisher.state == 'active': if not publisher.state == 'active':
publisher = None publisher = None
continue continue
datasets = _datasets_for_publisher(publisher, 10)[:count] datasets = _datasets_for_publisher(publisher, 10)[:count]
   
ctx = { ctx = {
'datasets': datasets, 'datasets': datasets,
'publisher': publisher 'publisher': publisher
} }
return base.render_snippet('ga_report/ga_popular_datasets.html', **ctx) return base.render_snippet('ga_report/ga_popular_datasets.html', **ctx)
   
def single_popular_dataset(top=20): def single_popular_dataset(top=20):
'''Returns a random dataset from the most popular ones. '''Returns a random dataset from the most popular ones.
   
:param top: the number of top datasets to select from :param top: the number of top datasets to select from
''' '''
import random import random
   
top_datasets = model.Session.query(GA_Url).\ top_datasets = model.Session.query(GA_Url).\
filter(GA_Url.url.like('/dataset/%')).\ filter(GA_Url.url.like('/dataset/%')).\
order_by('ga_url.pageviews::int desc') order_by('ga_url.pageviews::int desc')
num_top_datasets = top_datasets.count() num_top_datasets = top_datasets.count()
   
dataset = None dataset = None
if num_top_datasets: if num_top_datasets:
count = 0 count = 0
while not dataset: while not dataset:
rand = random.randrange(0, min(top, num_top_datasets)) rand = random.randrange(0, min(top, num_top_datasets))
ga_url = top_datasets[rand] ga_url = top_datasets[rand]
dataset = model.Package.get(ga_url.url[len('/dataset/'):]) dataset = model.Package.get(ga_url.url[len('/dataset/'):])
if dataset and not dataset.state == 'active': if dataset and not dataset.state == 'active':
dataset = None dataset = None
# When testing, it is possible that top datasets are not available # When testing, it is possible that top datasets are not available
# so only go round this loop a few times before falling back on # so only go round this loop a few times before falling back on
# a random dataset. # a random dataset.
count += 1 count += 1
if count > 10: if count > 10:
break break
if not dataset: if not dataset:
# fallback # fallback
dataset = model.Session.query(model.Package)\ dataset = model.Session.query(model.Package)\
.filter_by(state='active').first() .filter_by(state='active').first()
if not dataset: if not dataset:
return None return None
dataset_dict = get_action('package_show')({'model': model, dataset_dict = get_action('package_show')({'model': model,
'session': model.Session, 'session': model.Session,
'validate': False}, 'validate': False},
{'id':dataset.id}) {'id':dataset.id})
return dataset_dict return dataset_dict
   
def single_popular_dataset_html(top=20): def single_popular_dataset_html(top=20):
dataset_dict = single_popular_dataset(top) dataset_dict = single_popular_dataset(top)
groups = package.get('groups', []) 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') == 'publisher' ]
publisher = publishers[0] if publishers else {'name':'', 'title': ''} publisher = publishers[0] if publishers else {'name':'', 'title': ''}
context = { context = {
'dataset': dataset_dict, 'dataset': dataset_dict,
'publisher': publisher_dict 'publisher': publisher_dict
} }
return base.render_snippet('ga_report/ga_popular_single.html', **context) 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: if not publisher:
_log.error("No valid publisher passed to 'most_popular_datasets'") _log.error("No valid publisher passed to 'most_popular_datasets'")
return "" return ""
   
results = _datasets_for_publisher(publisher, count) results = _datasets_for_publisher(publisher, count)
   
ctx = { ctx = {
'dataset_count': len(results), 'dataset_count': len(results),
'datasets': results, 'datasets': results,
   
'publisher': publisher 'publisher': publisher,
  'preview_image': preview_image
} }
   
return base.render_snippet('ga_report/publisher/popular.html', **ctx) return base.render_snippet('ga_report/publisher/popular.html', **ctx)
   
def _datasets_for_publisher(publisher, count): def _datasets_for_publisher(publisher, count):
datasets = {} datasets = {}
entries = model.Session.query(GA_Url).\ entries = model.Session.query(GA_Url).\
filter(GA_Url.department_id==publisher.name).\ filter(GA_Url.department_id==publisher.name).\
filter(GA_Url.url.like('/dataset/%')).\ filter(GA_Url.url.like('/dataset/%')).\
order_by('ga_url.pageviews::int desc').all() order_by('ga_url.pageviews::int desc').all()
for entry in entries: for entry in entries:
if len(datasets) < count: if len(datasets) < count:
p = model.Package.get(entry.url[len('/dataset/'):]) p = model.Package.get(entry.url[len('/dataset/'):])
if not p: if not p:
_log.warning("Could not find Package for {url}".format(url=entry.url)) _log.warning("Could not find Package for {url}".format(url=entry.url))
continue continue
   
if not p in datasets: if not p in datasets:
datasets[p] = {'views':0, 'visits': 0} datasets[p] = {'views':0, 'visits': 0}
datasets[p]['views'] = datasets[p]['views'] + int(entry.pageviews) datasets[p]['views'] = datasets[p]['views'] + int(entry.pageviews)
datasets[p]['visits'] = datasets[p]['visits'] + int(entry.visits) datasets[p]['visits'] = datasets[p]['visits'] + int(entry.visits)
   
results = [] results = []
for k, v in datasets.iteritems(): for k, v in datasets.iteritems():
results.append((k,v['views'],v['visits'])) results.append((k,v['views'],v['visits']))
   
return sorted(results, key=operator.itemgetter(1), reverse=True) return sorted(results, key=operator.itemgetter(1), reverse=True)
   
.table-condensed td.sparkline-cell { .table-condensed td.sparkline-cell {
padding: 1px 0 0 0; padding: 1px 0 0 0;
width: 108px; width: 108px;
text-align: center; 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 { .rickshaw_chart_container {
position: relative; position: relative;
height: 350px; height: 350px;
margin: 0 auto 20px auto; margin: 0 auto 20px auto;
} }
.rickshaw_chart { .rickshaw_chart {
position: absolute; position: absolute;
left: 40px; left: 40px;
width: 500px; width: 500px;
top: 0; top: 0;
bottom: 0; bottom: 0;
} }
.rickshaw_legend { .rickshaw_legend {
background: transparent; background: transparent;
width: 100%; width: 100%;
padding-top: 4px; padding-top: 4px;
} }
.rickshaw_y_axis { .rickshaw_y_axis {
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
width: 40px; width: 40px;
} }
.rickshaw_legend .label { .rickshaw_legend .label {
background: transparent !important; background: transparent !important;
color: #000000 !important; color: #000000 !important;
font-weight: normal !important; font-weight: normal !important;
} }
.rickshaw_legend .instructions { .rickshaw_legend .instructions {
color: #000; color: #000;
margin-bottom: 6px; margin-bottom: 6px;
} }
   
.rickshaw_legend .line .action { .rickshaw_legend .line .action {
display: none; display: none;
} }
.rickshaw_legend .line .swatch { .rickshaw_legend .line .swatch {
display: block; display: block;
float: left; float: left;
} }
.rickshaw_legend .line .label { .rickshaw_legend .line .label {
display: block; display: block;
white-space: normal; white-space: normal;
float: left; float: left;
width: 200px; width: 200px;
} }
.rickshaw_legend .line .label:hover { .rickshaw_legend .line .label:hover {
text-decoration: underline; text-decoration: underline;
} }
   
.ga-reports-table .td-numeric { .ga-reports-table .td-numeric {
text-align: center; text-align: center;
} }
   
var CKAN = CKAN || {}; var CKAN = CKAN || {};
CKAN.GA_Reports = {}; CKAN.GA_Reports = {};
   
CKAN.GA_Reports.render_rickshaw = function( css_name, data, mode, colorscheme ) { CKAN.GA_Reports.render_rickshaw = function( css_name, data, mode, colorscheme ) {
var graphLegends = $('#graph-legend-container'); var graphLegends = $('#graph-legend-container');
   
if (!Modernizr.svg) { if (!Modernizr.svg) {
$("#chart_"+css_name) $("#chart_"+css_name)
.html( '<div class="alert">Your browser does not support vector graphics. No graphs can be rendered.</div>') .html( '<div class="alert">Your browser does not support vector graphics. No graphs can be rendered.</div>')
.closest('.rickshaw_chart_container').css('height',50); .closest('.rickshaw_chart_container').css('height',50);
var myLegend = $('<div id="legend_'+css_name+'"/>') var myLegend = $('<div id="legend_'+css_name+'"/>')
.html('(Graph cannot be rendered)') .html('(Graph cannot be rendered)')
.appendTo(graphLegends); .appendTo(graphLegends);
return; return;
} }
var myLegend = $('<div id="legend_'+css_name+'"/>').appendTo(graphLegends); var myLegend = $('<div id="legend_'+css_name+'"/>').appendTo(graphLegends);
   
var palette = new Rickshaw.Color.Palette( { scheme: colorscheme } ); var palette = new Rickshaw.Color.Palette( { scheme: colorscheme } );
$.each(data, function(i, object) { $.each(data, function(i, object) {
object['color'] = palette.color(); object['color'] = palette.color();
}); });
// Rickshaw renders the legend in reverse order... // Rickshaw renders the legend in reverse order...
data.reverse(); data.reverse();
   
var graphElement = document.querySelector("#chart_"+css_name); var graphElement = document.querySelector("#chart_"+css_name);
   
var graph = new Rickshaw.Graph( { var graph = new Rickshaw.Graph( {
element: document.querySelector("#chart_"+css_name), element: document.querySelector("#chart_"+css_name),
renderer: mode, renderer: mode,
series: data , series: data ,
height: 328 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( { var y_axis = new Rickshaw.Graph.Axis.Y( {
graph: graph, graph: graph,
orientation: 'left', orientation: 'left',
tickFormat: Rickshaw.Fixtures.Number.formatKMBT, tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
element: document.getElementById('y_axis_'+css_name) element: document.getElementById('y_axis_'+css_name)
} ); } );
var legend = new Rickshaw.Graph.Legend( { var legend = new Rickshaw.Graph.Legend( {
element: document.querySelector('#legend_'+css_name), element: document.querySelector('#legend_'+css_name),
graph: graph graph: graph
} ); } );
var shelving = new Rickshaw.Graph.Behavior.Series.Toggle( { var shelving = new Rickshaw.Graph.Behavior.Series.Toggle( {
graph: graph, graph: graph,
legend: legend legend: legend
} ); } );
myLegend.prepend('<div class="instructions">Click on a series below to isolate its graph:</div>'); myLegend.prepend('<div class="instructions">Click on a series below to isolate its graph:</div>');
graph.render(); graph.render();
}; };
   
CKAN.GA_Reports.bind_sparklines = function() { CKAN.GA_Reports.bind_sparklines = function() {
/* /*
* Bind to the 'totals' tab being on screen, when the * Bind to the 'totals' tab being on screen, when the
* Sparkline graphs should be drawn. * Sparkline graphs should be drawn.
* Note that they cannot be drawn sooner. * Note that they cannot be drawn sooner.
*/ */
var created = false; var created = false;
$('a[href="#totals"]').on( $('a[href="#totals"]').on(
'shown', 'shown',
function() { function() {
if (!created) { if (!created) {
var sparkOptions = { var sparkOptions = {
enableTagOptions: true, enableTagOptions: true,
type: 'line', type: 'line',
width: 100, width: 100,
height: 26, height: 26,
chartRangeMin: 0, chartRangeMin: 0,
spotColor: '', spotColor: '',
maxSpotColor: '', maxSpotColor: '',
minSpotColor: '', minSpotColor: '',
highlightSpotColor: '#000000', highlightSpotColor: '#000000',
lineColor: '#3F8E6D', lineColor: '#3F8E6D',
fillColor: '#B7E66B' fillColor: '#B7E66B'
}; };
$('.sparkline').sparkline('html',sparkOptions); $('.sparkline').sparkline('html',sparkOptions);
created = true; created = true;
} }
$.sparkline_display_visible(); $.sparkline_display_visible();
} }
); );
}; };
   
CKAN.GA_Reports.bind_sidebar = function() { CKAN.GA_Reports.bind_sidebar = function() {
/* /*
* Bind to changes in the tab behaviour: * Bind to changes in the tab behaviour:
* Show the correct rickshaw graph in the sidebar. * Show the correct rickshaw graph in the sidebar.
* Not to be called before all graphs load. * Not to be called before all graphs load.
*/ */
$('a[data-toggle="hashchange"]').on( $('a[data-toggle="hashtab"]').on(
'shown', 'shown',
function(e) { function(e) {
var href = $(e.target).attr('href'); var href = $(e.target).attr('href');
var pane = $(href); var pane = $(href);
if (!pane.length) { console.err('bad href',href); return; } if (!pane.length) { console.err('bad href',href); return; }
var legend_name = "none"; var legend_name = "none";
var graph = pane.find('.rickshaw_chart'); var graph = pane.find('.rickshaw_chart');
if (graph.length) { if (graph.length) {
legend_name = graph.attr('id').replace('chart_',''); legend_name = graph.attr('id').replace('chart_','');
} }
legend_name = '#legend_'+legend_name; legend_name = '#legend_'+legend_name;
$('#graph-legend-container > *').hide(); $('#graph-legend-container > *').hide();
$('#graph-legend-container .instructions').show(); $('#graph-legend-container .instructions').show();
$(legend_name).show(); $(legend_name).show();
} }
); );
  /* The first tab might already have been shown */
  $('li.active > a[data-toggle="hashtab"]').trigger('shown');
}; };
   
CKAN.GA_Reports.bind_month_selector = function() { CKAN.GA_Reports.bind_month_selector = function() {
var handler = function(e) { var handler = function(e) {
var target = $(e.delegateTarget); var target = $(e.delegateTarget);
var form = target.closest('form'); var form = target.closest('form');
var url = form.attr('action')+'?month='+target.val()+window.location.hash; var url = form.attr('action')+'?month='+target.val()+window.location.hash;
window.location = url; window.location = url;
}; };
var selectors = $('select[name="month"]'); var selectors = $('select[name="month"]');
assert(selectors.length>0); assert(selectors.length>0);
selectors.bind('change', handler); 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'); }  
});  
});  
   
<html <html
xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml"
xmlns:i18n="http://genshi.edgewall.org/i18n" xmlns:i18n="http://genshi.edgewall.org/i18n"
xmlns:py="http://genshi.edgewall.org/" xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xi="http://www.w3.org/2001/XInclude"
py:strip="" py:strip=""
> >
   
<select name="month" py:def="month_selector(current_month, months, day)"> <select name="month" py:def="month_selector(current_month, months, day)">
<option value='' py:attrs="{'selected': 'selected' if not current_month else