Merge pull request #816 from Robbt/add_show_analytics
Add show-based listener analytics
This commit is contained in:
commit
9ac28a1aa5
|
@ -26,6 +26,7 @@ $ccAcl->add(new Zend_Acl_Resource('library'))
|
|||
->add(new Zend_Acl_Resource('playouthistory'))
|
||||
->add(new Zend_Acl_Resource('playouthistorytemplate'))
|
||||
->add(new Zend_Acl_Resource('listenerstat'))
|
||||
->add(new Zend_Acl_Resource('showlistenerstat'))
|
||||
->add(new Zend_Acl_Resource('usersettings'))
|
||||
->add(new Zend_Acl_Resource('audiopreview'))
|
||||
->add(new Zend_Acl_Resource('webstream'))
|
||||
|
@ -84,6 +85,7 @@ $ccAcl->allow('G', 'index')
|
|||
->allow('H', 'playlist')
|
||||
->allow('H', 'playouthistory')
|
||||
->allow('H', 'listenerstat')
|
||||
->allow('H', 'showlistenerstat')
|
||||
->allow('A', 'playouthistorytemplate')
|
||||
->allow('A', 'user')
|
||||
->allow('A', 'systemstatus')
|
||||
|
|
|
@ -138,6 +138,14 @@ $pages[] = array(
|
|||
'action' => 'index',
|
||||
'resource' => 'listenerstat'
|
||||
),
|
||||
array(
|
||||
'label' => _('Show Listener Stats'),
|
||||
'module' => 'default',
|
||||
'controller' => 'listenerstat',
|
||||
'action' => 'show',
|
||||
'resource' => 'showlistenerstat'
|
||||
),
|
||||
|
||||
)
|
||||
);
|
||||
if (LIBRETIME_ENABLE_BILLING === true) {
|
||||
|
@ -221,4 +229,4 @@ $container = new Zend_Navigation($pages);
|
|||
$container->id = "nav";
|
||||
|
||||
//store it in the registry:
|
||||
Zend_Registry::set('Zend_Navigation', $container);
|
||||
Zend_Registry::set('Zend_Navigation', $container);
|
||||
|
|
|
@ -26,7 +26,6 @@ class ListenerstatController extends Zend_Controller_Action
|
|||
$this->view->headScript()->appendFile($baseUrl.'js/timepicker/jquery.ui.timepicker.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'js/airtime/buttons/buttons.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'js/airtime/utilities/utilities.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
|
||||
$this->view->headLink()->appendStylesheet($baseUrl.'css/jquery.ui.timepicker.css?'.$CC_CONFIG['airtime_version']);
|
||||
|
||||
list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($request);
|
||||
|
@ -56,12 +55,76 @@ class ListenerstatController extends Zend_Controller_Action
|
|||
$this->view->errorStatus = $out;
|
||||
$this->view->date_form = $form;
|
||||
}
|
||||
public function showAction() {
|
||||
$CC_CONFIG = Config::getConfig();
|
||||
|
||||
$request = $this->getRequest();
|
||||
$baseUrl = Application_Common_OsPath::getBaseDir();
|
||||
$headScript = $this->view->headScript();
|
||||
AirtimeTableView::injectTableJavaScriptDependencies($headScript, $baseUrl, $CC_CONFIG['airtime_version']);
|
||||
Zend_Layout::getMvcInstance()->assign('parent_page', 'Analytics');
|
||||
$this->view->headScript()->appendFile($baseUrl.'js/timepicker/jquery.ui.timepicker.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'js/airtime/buttons/buttons.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'js/airtime/utilities/utilities.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'js/airtime/listenerstat/showlistenerstat.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
|
||||
|
||||
|
||||
$this->view->headLink()->appendStylesheet($baseUrl.'css/datatables/css/ColVis.css?'.$CC_CONFIG['airtime_version']);
|
||||
$this->view->headLink()->appendStylesheet($baseUrl.'css/datatables/css/dataTables.colReorder.min.css?'.$CC_CONFIG['airtime_version']);
|
||||
$this->view->headLink()->appendStylesheet($baseUrl.'js/datatables/plugin/TableTools-2.1.5/css/TableTools.css?'.$CC_CONFIG['airtime_version']);
|
||||
$this->view->headLink()->appendStylesheet($baseUrl.'css/jquery.ui.timepicker.css?'.$CC_CONFIG['airtime_version']);
|
||||
$this->view->headLink()->appendStylesheet($baseUrl.'css/show_analytics.css'.$CC_CONFIG['airtime_version']);
|
||||
|
||||
$user = Application_Model_User::getCurrentUser();
|
||||
if ($user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
|
||||
$this->view->showAllShows = true;
|
||||
}
|
||||
$data = [];
|
||||
$this->view->showData = $data;
|
||||
|
||||
$form = new Application_Form_ShowListenerStat();
|
||||
|
||||
list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($request);
|
||||
$userTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone());
|
||||
$startsDT->setTimezone($userTimezone);
|
||||
$endsDT->setTimezone($userTimezone);
|
||||
$form->populate(array(
|
||||
'his_date_start' => $startsDT->format("Y-m-d"),
|
||||
'his_time_start' => $startsDT->format("H:i"),
|
||||
'his_date_end' => $endsDT->format("Y-m-d"),
|
||||
'his_time_end' => $endsDT->format("H:i")
|
||||
));
|
||||
|
||||
$this->view->date_form = $form;
|
||||
}
|
||||
|
||||
public function getDataAction(){
|
||||
list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($this->getRequest());
|
||||
|
||||
$data = Application_Model_ListenerStat::getDataPointsWithinRange($startsDT->format(DEFAULT_TIMESTAMP_FORMAT),
|
||||
$endsDT->format(DEFAULT_TIMESTAMP_FORMAT));
|
||||
$this->_helper->json->sendJson($data);
|
||||
}
|
||||
|
||||
public function getShowDataAction(){
|
||||
list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($this->getRequest());
|
||||
$show_id = $this->getRequest()->getParam("show_id", null);
|
||||
$data = Application_Model_ListenerStat::getShowDataPointsWithinRange($startsDT->format(DEFAULT_TIMESTAMP_FORMAT),
|
||||
$endsDT->format(DEFAULT_TIMESTAMP_FORMAT),$show_id);
|
||||
$this->_helper->json->sendJson($data);
|
||||
}
|
||||
public function getAllShowData(){
|
||||
list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($this->getRequest());
|
||||
$data = Application_Model_ListenerStat::getAllShowDataPointsWithinRange($startsDT->format(DEFAULT_TIMESTAMP_FORMAT),
|
||||
$endsDT->format(DEFAULT_TIMESTAMP_FORMAT));
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getAllShowDataAction(){
|
||||
list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($this->getRequest());
|
||||
$show_id = $this->getRequest()->getParam("show_id", null);
|
||||
$data = Application_Model_ListenerStat::getAllShowDataPointsWithinRange($startsDT->format(DEFAULT_TIMESTAMP_FORMAT),
|
||||
$endsDT->format(DEFAULT_TIMESTAMP_FORMAT));
|
||||
$this->_helper->json->sendJson($data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
class Application_Form_ShowListenerStat extends Zend_Form_SubForm
|
||||
{
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->setDecorators(array(
|
||||
array('ViewScript', array('viewScript' => 'form/daterange.phtml'))
|
||||
));
|
||||
|
||||
// Add start date element
|
||||
$startDate = new Zend_Form_Element_Text('his_date_start');
|
||||
$startDate->class = 'input_text';
|
||||
$startDate->setRequired(true)
|
||||
->setLabel(_('Date Start:'))
|
||||
->setValue(date("Y-m-d"))
|
||||
->setFilters(array('StringTrim'))
|
||||
->setValidators(array(
|
||||
'NotEmpty',
|
||||
array('date', false, array('YYYY-MM-DD'))))
|
||||
->setDecorators(array('ViewHelper'));
|
||||
$startDate->setAttrib('alt', 'date');
|
||||
$this->addElement($startDate);
|
||||
|
||||
// Add start time element
|
||||
$startTime = new Zend_Form_Element_Text('his_time_start');
|
||||
$startTime->class = 'input_text';
|
||||
$startTime->setRequired(true)
|
||||
->setValue('00:00')
|
||||
->setFilters(array('StringTrim'))
|
||||
->setValidators(array(
|
||||
'NotEmpty',
|
||||
array('date', false, array('HH:mm')),
|
||||
array('regex', false, array('/^[0-2]?[0-9]:[0-5][0-9]$/', 'messages' => _('Invalid character entered')))))
|
||||
->setDecorators(array('ViewHelper'));
|
||||
$startTime->setAttrib('alt', 'time');
|
||||
$this->addElement($startTime);
|
||||
|
||||
// Add end date element
|
||||
$endDate = new Zend_Form_Element_Text('his_date_end');
|
||||
$endDate->class = 'input_text';
|
||||
$endDate->setRequired(true)
|
||||
->setLabel(_('Date End:'))
|
||||
->setValue(date("Y-m-d"))
|
||||
->setFilters(array('StringTrim'))
|
||||
->setValidators(array(
|
||||
'NotEmpty',
|
||||
array('date', false, array('YYYY-MM-DD'))))
|
||||
->setDecorators(array('ViewHelper'));
|
||||
$endDate->setAttrib('alt', 'date');
|
||||
$this->addElement($endDate);
|
||||
|
||||
// Add end time element
|
||||
$endTime = new Zend_Form_Element_Text('his_time_end');
|
||||
$endTime->class = 'input_text';
|
||||
$endTime->setRequired(true)
|
||||
->setValue('01:00')
|
||||
->setFilters(array('StringTrim'))
|
||||
->setValidators(array(
|
||||
'NotEmpty',
|
||||
array('date', false, array('HH:mm')),
|
||||
array('regex', false, array('/^[0-2]?[0-9]:[0-5][0-9]$/', 'messages' => _('Invalid character entered')))))
|
||||
->setDecorators(array('ViewHelper'));
|
||||
$endTime->setAttrib('alt', 'time');
|
||||
$this->addElement($endTime);
|
||||
}
|
||||
}
|
|
@ -16,8 +16,8 @@ group by mount_name
|
|||
SQL;
|
||||
$data = Application_Common_Database::prepareAndExecute($sql,
|
||||
array('p1'=>$p_start, 'p2'=>$p_end));
|
||||
|
||||
$out = array();
|
||||
|
||||
foreach ($data as $d) {
|
||||
$jump = intval($d['count']/1000);
|
||||
$jump = max(1, $jump);
|
||||
|
@ -36,7 +36,7 @@ WHERE (temp.rownum%:p4) = :p5;
|
|||
SQL;
|
||||
$result = Application_Common_Database::prepareAndExecute($sql,
|
||||
array('p1'=>$p_start, 'p2'=>$p_end, 'p3'=>$d['mount_name'], 'p4'=>$jump, 'p5'=>$remainder));
|
||||
|
||||
|
||||
$utcTimezone = new DateTimeZone("UTC");
|
||||
$displayTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone());
|
||||
|
||||
|
@ -51,6 +51,7 @@ SQL;
|
|||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
$enabledStreamIds = Application_Model_StreamSetting::getEnabledStreamIds();
|
||||
$enabledOut = array();
|
||||
|
||||
|
@ -77,6 +78,68 @@ SQL;
|
|||
|
||||
return $enabledOut;
|
||||
}
|
||||
// this will currently log the average number of listeners to a specific show during a certain range
|
||||
public static function getShowDataPointsWithinRange($p_start, $p_end, $show_id) {
|
||||
$showData = [];
|
||||
$ccShow = CcShowQuery::create()->findPk($show_id);
|
||||
$showName = $ccShow->getDbName();
|
||||
|
||||
|
||||
// this query selects all show instances that aired in this date range that match the show_id
|
||||
$sql = <<<SQL
|
||||
SELECT id, starts, ends FROM cc_show_instances WHERE show_id =:p1
|
||||
AND starts >=:p2 AND ends <=:p3
|
||||
SQL;
|
||||
$data = Application_Common_Database::prepareAndExecute($sql,
|
||||
array('p1'=>$show_id,'p2'=>$p_start, 'p3'=>$p_end));
|
||||
foreach ($data as $d) {
|
||||
$sql = <<<SQL
|
||||
SELECT timestamp, SUM(listener_count) AS listeners
|
||||
FROM cc_listener_count AS lc
|
||||
INNER JOIN cc_timestamp AS ts ON (lc.timestamp_id = ts.ID)
|
||||
INNER JOIN cc_mount_name AS mn ON (lc.mount_name_id = mn.ID)
|
||||
WHERE (ts.timestamp >=:p1 AND ts.timestamp <=:p2)
|
||||
GROUP BY timestamp
|
||||
SQL;
|
||||
$data = Application_Common_Database::prepareAndExecute($sql,
|
||||
array('p1'=>$d['starts'], 'p2'=>$d['ends']));
|
||||
$utcTimezone = new DateTimeZone("UTC");
|
||||
$displayTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone());
|
||||
if (sizeof($data) > 0) {
|
||||
$t = new DateTime($data[0]['timestamp'], $utcTimezone);
|
||||
$t->setTimezone($displayTimezone);
|
||||
// tricking javascript so it thinks the server timezone is in UTC
|
||||
$average_listeners = array_sum(array_column($data, 'listeners')) / sizeof($data);
|
||||
$max_num_listeners = max(array_column($data, 'listeners'));
|
||||
$entry = array("show" => $showName, "time" => $t->format( 'Y-m-d H:i:s')
|
||||
, "average_number_of_listeners" => $average_listeners,
|
||||
"maximum_number_of_listeners" => $max_num_listeners);
|
||||
array_push($showData, $entry);
|
||||
}
|
||||
}
|
||||
return($showData);
|
||||
}
|
||||
public static function getAllShowDataPointsWithinRange($p_start, $p_end) {
|
||||
// this query selects the id of all show instances that aired in this date range
|
||||
$all_show_data = [];
|
||||
$sql = <<<SQL
|
||||
SELECT show_id FROM cc_show_instances
|
||||
WHERE starts >=:p1 AND ends <=:p2
|
||||
GROUP BY show_id
|
||||
SQL;
|
||||
$data = Application_Common_Database::prepareAndExecute($sql,
|
||||
array('p1'=>$p_start, 'p2'=>$p_end));
|
||||
|
||||
foreach($data as $show_id) {
|
||||
$all_show_data = array_merge(self::getShowDataPointsWithinRange($p_start,$p_end,$show_id['show_id']), $all_show_data);
|
||||
}
|
||||
/* option to sort by number of listeners currently commented out
|
||||
usort($all_show_data, function($a, $b) {
|
||||
return $a['average_number_of_listeners'] - $b['average_number_of_listeners'];
|
||||
});
|
||||
*/
|
||||
return $all_show_data;
|
||||
}
|
||||
|
||||
public static function insertDataPoints($p_dataPoints) {
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<div id="showlistenerstat_content" class="alpha-block padded">
|
||||
<H2><?php echo _("Listeners")?></H2>
|
||||
<div class="error_window"></div>
|
||||
<div id="date_form" style="float:left; margin:0px 0px">
|
||||
<h3 style="padding-left: 0px">Date Range</h3>
|
||||
<?php echo $this->date_form; ?>
|
||||
</div>
|
||||
|
||||
<table id="show_builder_table" cellpadding="0" cellspacing="0" class="datatable"></table>
|
||||
<table cellspacing="0" cellpadding="0" style="" id="show_stats_datatable" class="datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php echo _("Show Name") ?></th>
|
||||
<th><?php echo _("Air Date") ?></th>
|
||||
<th><?php echo _("Average Listeners")?></th>
|
||||
<th><?php echo _("Maximum Number of Listeners")?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
div.date_form input.error {
|
||||
background-color: rgba(255,0,0,0.2);
|
||||
}
|
||||
div.error_window.error {
|
||||
background-color: rgba(255,0,0,0.2);
|
||||
color:white;
|
||||
}
|
||||
div.error_window {
|
||||
background-color: white;
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
$(document).ready(function() {
|
||||
showlistenerstat_content = $("#showlistenerstat_content")
|
||||
dateStartId = "#his_date_start",
|
||||
timeStartId = "#his_time_start",
|
||||
dateEndId = "#his_date_end",
|
||||
timeEndId = "#his_time_end",
|
||||
show_id = "#his_show_filter";
|
||||
|
||||
// set width dynamically
|
||||
var width = $("#showlistenerstat_content").width();
|
||||
width = width * .91;
|
||||
addDatePicker();
|
||||
|
||||
showlistenerstat_content.find("#his_submit").click(function(){
|
||||
// var show_id = $("#sb_show_filter").val();
|
||||
var oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
|
||||
var start = oRange.start;
|
||||
var end = oRange.end;
|
||||
showListenerDataTable();
|
||||
});
|
||||
});
|
||||
|
||||
function getShowData(startTimestamp, endTimestamp, show_id) {
|
||||
// get data
|
||||
$.get(baseUrl+'Listenerstat/get-all-show-data', {start: startTimestamp, end: endTimestamp }, function(data) {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
function addDatePicker() {
|
||||
|
||||
oBaseDatePickerSettings = {
|
||||
dateFormat: 'yy-mm-dd',
|
||||
//i18n_months, i18n_days_short are in common.js
|
||||
monthNames: i18n_months,
|
||||
dayNamesMin: i18n_days_short,
|
||||
onSelect: function(sDate, oDatePicker) {
|
||||
$(this).datepicker( "setDate", sDate );
|
||||
},
|
||||
onClose: validateTimeRange
|
||||
};
|
||||
oBaseTimePickerSettings = {
|
||||
showPeriodLabels: false,
|
||||
showCloseButton: true,
|
||||
closeButtonText: $.i18n._("Done"),
|
||||
showLeadingZero: false,
|
||||
defaultTime: '0:00',
|
||||
hourText: $.i18n._("Hour"),
|
||||
minuteText: $.i18n._("Minute"),
|
||||
onClose: validateTimeRange
|
||||
};
|
||||
|
||||
showlistenerstat_content.find(dateStartId).datepicker(oBaseDatePickerSettings).blur(validateTimeRange());
|
||||
showlistenerstat_content.find(timeStartId).timepicker(oBaseTimePickerSettings).blur(validateTimeRange());
|
||||
showlistenerstat_content.find(dateEndId).datepicker(oBaseDatePickerSettings).blur(validateTimeRange());
|
||||
showlistenerstat_content.find(timeEndId).timepicker(oBaseTimePickerSettings).blur(validateTimeRange());
|
||||
}
|
||||
|
||||
function getStartEnd() {
|
||||
|
||||
return AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
|
||||
}
|
||||
|
||||
function validateTimeRange() {
|
||||
var oRange,
|
||||
inputs = $('.date_form > input'),
|
||||
error_window = $('.error_window'),
|
||||
start, end;
|
||||
|
||||
oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
|
||||
|
||||
start = oRange.start;
|
||||
end = oRange.end;
|
||||
|
||||
if (end >= start) {
|
||||
error_window.removeClass('error');
|
||||
$('.error_window').html('');
|
||||
}
|
||||
else {
|
||||
error_window.addClass('error');
|
||||
console.log('bad')
|
||||
$('.error_window').html('Your start date time is after your end date time');
|
||||
}
|
||||
|
||||
return {
|
||||
start: start,
|
||||
end: end,
|
||||
isValid: end >= start
|
||||
};
|
||||
}
|
||||
|
||||
function showListenerDataTable() {
|
||||
var oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId);
|
||||
var start = oRange.start;
|
||||
var lengthMenu = [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, $.i18n._("All")]];
|
||||
var end = oRange.end;
|
||||
var sDom = 'l<"dt-process-rel"r><"H"T><"dataTables_scrolling"t><"F"ip>';
|
||||
var show_id = $("#sb_show_filter").val();
|
||||
var dt = $('#show_stats_datatable');
|
||||
info = getStartEnd();
|
||||
dt.dataTable({
|
||||
"aoColumns": [
|
||||
/* first name */ {"sName": "show", "mDataProp": "show"},
|
||||
/* air date */ {"sName": "time", "mDataProp": "time"},
|
||||
/* last name */ {"sName": "average_number_of_listeners", "mDataProp": "average_number_of_listeners"},
|
||||
/* last name */ {"sName": "maximum_number_of_listeners", "mDataProp": "maximum_number_of_listeners"}],
|
||||
"sAjaxSource": baseUrl+'Listenerstat/get-all-show-data',
|
||||
"sAjaxDataProp": "",
|
||||
"bDestroy": true,
|
||||
"aLengthMenu": lengthMenu,
|
||||
"iDisplayLength": 25,
|
||||
"sPaginationType": "full_numbers",
|
||||
"bJQueryUI": true,
|
||||
"bAutoWidth": true,
|
||||
"sDom": sDom,
|
||||
"fnServerData": function ( sSource, aoData, fnCallback ) {
|
||||
aoData.push({"start": start, "end": end});
|
||||
$.ajax( {
|
||||
"dataType": 'json',
|
||||
"type": "POST",
|
||||
"url": sSource,
|
||||
"data": {"start": start, "end": end},
|
||||
"success": fnCallback
|
||||
} );
|
||||
},
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue