feat: add cumulative listening time charts
This commit is contained in:
parent
14733105e6
commit
588b4d28da
|
@ -153,6 +153,14 @@ $routes->group(
|
||||||
'filter' => 'permission:podcasts-view,podcast-view',
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
$routes->get(
|
||||||
|
'listening-time',
|
||||||
|
'Podcast::viewAnalyticsListeningTime/$1',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-analytics-listening-time',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
]
|
||||||
|
);
|
||||||
$routes->get(
|
$routes->get(
|
||||||
'players',
|
'players',
|
||||||
'Podcast::viewAnalyticsPlayers/$1',
|
'Podcast::viewAnalyticsPlayers/$1',
|
||||||
|
|
|
@ -90,6 +90,14 @@ class Podcast extends BaseController
|
||||||
return view('admin/podcast/analytics/unique_listeners', $data);
|
return view('admin/podcast/analytics/unique_listeners', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function viewAnalyticsListeningTime()
|
||||||
|
{
|
||||||
|
$data = ['podcast' => $this->podcast];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([0 => $this->podcast->title]);
|
||||||
|
return view('admin/podcast/analytics/listening-time', $data);
|
||||||
|
}
|
||||||
|
|
||||||
public function viewAnalyticsPlayers()
|
public function viewAnalyticsPlayers()
|
||||||
{
|
{
|
||||||
$data = ['podcast' => $this->podcast];
|
$data = ['podcast' => $this->podcast];
|
||||||
|
|
|
@ -27,4 +27,5 @@ return [
|
||||||
'webpages' => 'Web pages',
|
'webpages' => 'Web pages',
|
||||||
'unique-listeners' => 'Unique listeners',
|
'unique-listeners' => 'Unique listeners',
|
||||||
'players' => 'Players',
|
'players' => 'Players',
|
||||||
|
'listening-time' => 'Listening time',
|
||||||
];
|
];
|
||||||
|
|
|
@ -27,4 +27,6 @@ return [
|
||||||
'by_domain_yearly' => 'Web pages visits by source (for the past year)',
|
'by_domain_yearly' => 'Web pages visits by source (for the past year)',
|
||||||
'by_entry_page' => 'Web pages visits by landing page (for the past week)',
|
'by_entry_page' => 'Web pages visits by landing page (for the past week)',
|
||||||
'podcast_bots' => 'Bots (crawlers)',
|
'podcast_bots' => 'Bots (crawlers)',
|
||||||
|
'daily_listening_time' => 'Daily cumulative listening time',
|
||||||
|
'monthly_listening_time' => 'Monthly cumulative listening time',
|
||||||
];
|
];
|
||||||
|
|
|
@ -25,4 +25,5 @@ return [
|
||||||
'podcast-analytics-locations' => 'Locations',
|
'podcast-analytics-locations' => 'Locations',
|
||||||
'podcast-analytics-unique-listeners' => 'Unique listeners',
|
'podcast-analytics-unique-listeners' => 'Unique listeners',
|
||||||
'podcast-analytics-players' => 'Players',
|
'podcast-analytics-players' => 'Players',
|
||||||
|
'podcast-analytics-listening-time' => 'Listening time',
|
||||||
];
|
];
|
||||||
|
|
|
@ -27,4 +27,5 @@ return [
|
||||||
'webpages' => 'Pages web',
|
'webpages' => 'Pages web',
|
||||||
'unique-listeners' => 'Auditeurs uniques',
|
'unique-listeners' => 'Auditeurs uniques',
|
||||||
'players' => 'Lecteurs',
|
'players' => 'Lecteurs',
|
||||||
|
'listening-time' => 'Durée d’écoute',
|
||||||
];
|
];
|
||||||
|
|
|
@ -39,4 +39,6 @@ return [
|
||||||
'by_entry_page' =>
|
'by_entry_page' =>
|
||||||
'Fréquentation des pages web par page d’entrée (sur la dernière semaine)',
|
'Fréquentation des pages web par page d’entrée (sur la dernière semaine)',
|
||||||
'podcast_bots' => 'Robots (bots)',
|
'podcast_bots' => 'Robots (bots)',
|
||||||
|
'daily_listening_time' => 'Durée quotidienne d’écoute cumulée',
|
||||||
|
'monthly_listening_time' => 'Durée mensuelle d’écoute cumulée',
|
||||||
];
|
];
|
||||||
|
|
|
@ -25,4 +25,5 @@ return [
|
||||||
'podcast-analytics-locations' => 'Localisations',
|
'podcast-analytics-locations' => 'Localisations',
|
||||||
'podcast-analytics-unique-listeners' => 'Auditeurs uniques',
|
'podcast-analytics-unique-listeners' => 'Auditeurs uniques',
|
||||||
'podcast-analytics-players' => 'Lecteurs',
|
'podcast-analytics-players' => 'Lecteurs',
|
||||||
|
'podcast-analytics-listening-time' => 'Durée d’écoute',
|
||||||
];
|
];
|
||||||
|
|
|
@ -142,4 +142,71 @@ class AnalyticsPodcastByEpisodeModel extends Model
|
||||||
}
|
}
|
||||||
return $found;
|
return $found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets listening-time data for a podcast
|
||||||
|
*
|
||||||
|
* @param int $podcastId
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getDataTotalListeningTimeByDay(int $podcastId): array
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
!($found = cache(
|
||||||
|
"{$podcastId}_analytics_podcast_listening_time_by_day"
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
$found = $this->select('date as labels')
|
||||||
|
->selectSum('(enclosure_duration * hits)', 'values')
|
||||||
|
->join('episodes', 'id = episode_id', 'inner')
|
||||||
|
->where([
|
||||||
|
$this->table . '.podcast_id' => $podcastId,
|
||||||
|
'date >' => date('Y-m-d', strtotime('-60 days')),
|
||||||
|
])
|
||||||
|
->groupBy('labels')
|
||||||
|
->orderBy('labels', 'ASC')
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
cache()->save(
|
||||||
|
"{$podcastId}_analytics_podcast_listening_time_by_day",
|
||||||
|
$found,
|
||||||
|
600
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets listening-time data for a podcast
|
||||||
|
*
|
||||||
|
* @param int $podcastId
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getDataTotalListeningTimeByMonth(int $podcastId): array
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
!($found = cache(
|
||||||
|
"{$podcastId}_analytics_podcast_listening_time_by_month"
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
$found = $this->select('DATE_FORMAT(`date`,"%Y-%m-01") as `labels`')
|
||||||
|
->selectSum('(enclosure_duration * hits)', 'values')
|
||||||
|
->join('episodes', 'id = episode_id', 'inner')
|
||||||
|
->where([
|
||||||
|
$this->table . '.podcast_id' => $podcastId,
|
||||||
|
])
|
||||||
|
->groupBy('`labels`')
|
||||||
|
->orderBy('`labels`', 'ASC')
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
cache()->save(
|
||||||
|
"{$podcastId}_analytics_podcast_listening_time_by_month",
|
||||||
|
$found,
|
||||||
|
600
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,13 +38,11 @@ class AnalyticsPodcastByRegionModel extends Model
|
||||||
"{$podcastId}_analytics_podcast_by_region_{$locale}"
|
"{$podcastId}_analytics_podcast_by_region_{$locale}"
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
$found = $this->select(
|
$found = $this->select('`country_code`, `region_code`')
|
||||||
'`country_code`, `region_code`, `latitude`, `longitude`'
|
|
||||||
)
|
|
||||||
->selectSum('`hits`', '`value`')
|
->selectSum('`hits`', '`value`')
|
||||||
->groupBy(
|
->selectAvg('`latitude`')
|
||||||
'`country_code`, `region_code`, `latitude`, `longitude`'
|
->selectAvg('`longitude`')
|
||||||
)
|
->groupBy('`country_code`, `region_code`')
|
||||||
->where([
|
->where([
|
||||||
'`podcast_id`' => $podcastId,
|
'`podcast_id`' => $podcastId,
|
||||||
'`date` >' => date('Y-m-d', strtotime('-1 week')),
|
'`date` >' => date('Y-m-d', strtotime('-1 week')),
|
||||||
|
|
|
@ -62,6 +62,41 @@ const drawXYChart = (chartDivId: string, dataUrl: string | null): void => {
|
||||||
chart.scrollbarX = new am4core.Scrollbar();
|
chart.scrollbarX = new am4core.Scrollbar();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const drawXYDurationChart = (chartDivId: string, dataUrl: string | null): void => {
|
||||||
|
// Create chart instance
|
||||||
|
const chart = am4core.create(chartDivId, am4charts.XYChart);
|
||||||
|
am4core.percent(100);
|
||||||
|
// Set theme
|
||||||
|
am4core.useTheme(am4themes_material);
|
||||||
|
// Create axes
|
||||||
|
const dateAxis = chart.xAxes.push(new am4charts.DateAxis());
|
||||||
|
dateAxis.renderer.minGridDistance = 60;
|
||||||
|
const yAxis = chart.yAxes.push(new am4charts.DurationAxis());
|
||||||
|
yAxis.baseUnit = "second";
|
||||||
|
chart.durationFormatter.durationFormat = "hh'h,' mm'mn'";
|
||||||
|
// Add data
|
||||||
|
chart.dataSource.url = dataUrl || "";
|
||||||
|
chart.dataSource.parser.options.emptyAs = 0;
|
||||||
|
// Create series
|
||||||
|
const series = chart.series.push(new am4charts.LineSeries());
|
||||||
|
series.dataFields.valueY = "values";
|
||||||
|
series.dataFields.dateX = "labels";
|
||||||
|
series.tooltipText = "{valueY.formatDuration()}";
|
||||||
|
series.strokeWidth = 2;
|
||||||
|
// Make bullets grow on hover
|
||||||
|
const bullet = series.bullets.push(new am4charts.CircleBullet());
|
||||||
|
bullet.circle.strokeWidth = 2;
|
||||||
|
bullet.circle.radius = 4;
|
||||||
|
bullet.circle.fill = am4core.color("#fff");
|
||||||
|
const bullethover = bullet.states.create("hover");
|
||||||
|
bullethover.properties.scale = 1.3;
|
||||||
|
series.tooltip.pointerOrientation = "vertical";
|
||||||
|
chart.cursor = new am4charts.XYCursor();
|
||||||
|
chart.cursor.snapToSeries = series;
|
||||||
|
chart.cursor.xAxis = dateAxis;
|
||||||
|
chart.scrollbarX = new am4core.Scrollbar();
|
||||||
|
};
|
||||||
|
|
||||||
const drawXYSeriesChart = (
|
const drawXYSeriesChart = (
|
||||||
chartDivId: string,
|
chartDivId: string,
|
||||||
dataUrl: string | null
|
dataUrl: string | null
|
||||||
|
@ -152,6 +187,9 @@ const DrawCharts = (): void => {
|
||||||
case "xy-chart":
|
case "xy-chart":
|
||||||
drawXYChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
|
drawXYChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
|
||||||
break;
|
break;
|
||||||
|
case "xy-duration-chart":
|
||||||
|
drawXYDurationChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
|
||||||
|
break;
|
||||||
case "xy-series-chart":
|
case "xy-series-chart":
|
||||||
drawXYSeriesChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
|
drawXYSeriesChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -13,6 +13,7 @@ $podcastNavigation = [
|
||||||
'items' => [
|
'items' => [
|
||||||
'podcast-analytics',
|
'podcast-analytics',
|
||||||
'podcast-analytics-unique-listeners',
|
'podcast-analytics-unique-listeners',
|
||||||
|
'podcast-analytics-listening-time',
|
||||||
'podcast-analytics-players',
|
'podcast-analytics-players',
|
||||||
'podcast-analytics-locations',
|
'podcast-analytics-locations',
|
||||||
'podcast-analytics-webpages',
|
'podcast-analytics-webpages',
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?= $this->extend('admin/_layout') ?>
|
||||||
|
|
||||||
|
<?= $this->section('title') ?>
|
||||||
|
<?= $podcast->title ?>
|
||||||
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
|
<?= $this->section('pageTitle') ?>
|
||||||
|
<?= $podcast->title ?>
|
||||||
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
|
<?= $this->section('content') ?>
|
||||||
|
|
||||||
|
<div class="mb-12 text-center">
|
||||||
|
<h2><?= lang('Charts.daily_listening_time') ?></h2>
|
||||||
|
<div class="chart-xy" id="by-day-listening-time-graph" data-chart-type="xy-duration-chart" data-chart-url="<?= route_to(
|
||||||
|
'analytics-data',
|
||||||
|
$podcast->id,
|
||||||
|
'PodcastByEpisode',
|
||||||
|
'TotalListeningTimeByDay'
|
||||||
|
) ?>"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-12 text-center">
|
||||||
|
<h2><?= lang('Charts.monthly_listening_time') ?></h2>
|
||||||
|
<div class="chart-xy" id="by-month-listening-time-graph" data-chart-type="xy-duration-chart" data-chart-url="<?= route_to(
|
||||||
|
'analytics-data',
|
||||||
|
$podcast->id,
|
||||||
|
'PodcastByEpisode',
|
||||||
|
'TotalListeningTimeByMonth'
|
||||||
|
) ?>"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/assets/charts.js" type="module"></script>
|
||||||
|
<?= $this->endSection() ?>
|
Loading…
Reference in New Issue