mirror of
https://code.castopod.org/adaures/castopod
synced 2025-06-05 17:02:01 +00:00
feat(analytics): add weekday and hour bar charts
This commit is contained in:
parent
b6fc7d91eb
commit
8ab313296b
@ -158,6 +158,14 @@ $routes->group(
|
|||||||
'filter' => 'permission:podcasts-view,podcast-view',
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
$routes->get(
|
||||||
|
'time-periods',
|
||||||
|
'Podcast::viewAnalyticsTimePeriods/$1',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-analytics-time-periods',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
]
|
||||||
|
);
|
||||||
$routes->get(
|
$routes->get(
|
||||||
'players',
|
'players',
|
||||||
'Podcast::viewAnalyticsPlayers/$1',
|
'Podcast::viewAnalyticsPlayers/$1',
|
||||||
|
@ -98,6 +98,14 @@ class Podcast extends BaseController
|
|||||||
return view('admin/podcast/analytics/listening_time', $data);
|
return view('admin/podcast/analytics/listening_time', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function viewAnalyticsTimePeriods()
|
||||||
|
{
|
||||||
|
$data = ['podcast' => $this->podcast];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([0 => $this->podcast->title]);
|
||||||
|
return view('admin/podcast/analytics/time_periods', $data);
|
||||||
|
}
|
||||||
|
|
||||||
public function viewAnalyticsPlayers()
|
public function viewAnalyticsPlayers()
|
||||||
{
|
{
|
||||||
$data = ['podcast' => $this->podcast];
|
$data = ['podcast' => $this->podcast];
|
||||||
|
@ -28,4 +28,5 @@ return [
|
|||||||
'unique-listeners' => 'Unique listeners',
|
'unique-listeners' => 'Unique listeners',
|
||||||
'players' => 'Players',
|
'players' => 'Players',
|
||||||
'listening-time' => 'Listening time',
|
'listening-time' => 'Listening time',
|
||||||
|
'time-periods' => 'Time periods',
|
||||||
];
|
];
|
||||||
|
@ -30,4 +30,7 @@ return [
|
|||||||
'podcast_bots' => 'Bots (crawlers)',
|
'podcast_bots' => 'Bots (crawlers)',
|
||||||
'daily_listening_time' => 'Daily cumulative listening time',
|
'daily_listening_time' => 'Daily cumulative listening time',
|
||||||
'monthly_listening_time' => 'Monthly cumulative listening time',
|
'monthly_listening_time' => 'Monthly cumulative listening time',
|
||||||
|
'by_weekday' => 'By week day (for the past 60 days)',
|
||||||
|
'by_hour' => 'By time of day (for the past 60 days)',
|
||||||
|
'podcast_by_bandwidth' => 'Daily used bandwidth (in MB)',
|
||||||
];
|
];
|
||||||
|
@ -26,4 +26,5 @@ return [
|
|||||||
'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',
|
'podcast-analytics-listening-time' => 'Listening time',
|
||||||
|
'podcast-analytics-time-periods' => 'Time periods',
|
||||||
];
|
];
|
||||||
|
@ -28,4 +28,5 @@ return [
|
|||||||
'unique-listeners' => 'Auditeurs uniques',
|
'unique-listeners' => 'Auditeurs uniques',
|
||||||
'players' => 'Lecteurs',
|
'players' => 'Lecteurs',
|
||||||
'listening-time' => 'Durée d’écoute',
|
'listening-time' => 'Durée d’écoute',
|
||||||
|
'time-periods' => 'Périodes',
|
||||||
];
|
];
|
||||||
|
@ -43,4 +43,7 @@ return [
|
|||||||
'podcast_bots' => 'Robots (bots)',
|
'podcast_bots' => 'Robots (bots)',
|
||||||
'daily_listening_time' => 'Durée quotidienne d’écoute cumulée',
|
'daily_listening_time' => 'Durée quotidienne d’écoute cumulée',
|
||||||
'monthly_listening_time' => 'Durée mensuelle d’écoute cumulée',
|
'monthly_listening_time' => 'Durée mensuelle d’écoute cumulée',
|
||||||
|
'by_weekday' => 'Par jour de la semaine (sur les 60 derniers jours)',
|
||||||
|
'by_hour' => 'Par heure de la journée (sur les 60 derniers jours)',
|
||||||
|
'podcast_by_bandwidth' => 'Bande passante quotidienne consommée (en Mo)',
|
||||||
];
|
];
|
||||||
|
@ -26,4 +26,5 @@ return [
|
|||||||
'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',
|
'podcast-analytics-listening-time' => 'Durée d’écoute',
|
||||||
|
'podcast-analytics-time-periods' => 'Périodes',
|
||||||
];
|
];
|
||||||
|
@ -37,15 +37,33 @@ class AnalyticsPodcastByCountryModel extends Model
|
|||||||
"{$podcastId}_analytics_podcast_by_country_weekly"
|
"{$podcastId}_analytics_podcast_by_country_weekly"
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
|
$oneWeekAgo=date('Y-m-d', strtotime('-1 week'));
|
||||||
$found = $this->select('`country_code` as `labels`')
|
$found = $this->select('`country_code` as `labels`')
|
||||||
->selectSum('`hits`', '`values`')
|
->selectSum('`hits`', '`values`')
|
||||||
->where([
|
->where([
|
||||||
'`podcast_id`' => $podcastId,
|
'`podcast_id`' => $podcastId,
|
||||||
'`date` >' => date('Y-m-d', strtotime('-1 week')),
|
'`date` >' => $oneWeekAgo,
|
||||||
])
|
])
|
||||||
->groupBy('`labels`')
|
->groupBy('`labels`')
|
||||||
->orderBy('`values`', 'DESC')
|
->orderBy('`values`', 'DESC')
|
||||||
->findAll(10);
|
->findAll(9);
|
||||||
|
|
||||||
|
$found[] = $this->db
|
||||||
|
->query(
|
||||||
|
"SELECT
|
||||||
|
\"Other\" AS `labels`,
|
||||||
|
SUM(`others`.`values`) AS `values`
|
||||||
|
FROM
|
||||||
|
(SELECT SUM(`hits`) AS `values`
|
||||||
|
FROM {$this->db->prefixTable($this->table)}
|
||||||
|
WHERE `date` > $oneWeekAgo
|
||||||
|
GROUP BY `country_code`
|
||||||
|
ORDER BY `values` DESC
|
||||||
|
LIMIT 18446744073709551610 OFFSET 9
|
||||||
|
) AS `others`
|
||||||
|
GROUP BY `labels`"
|
||||||
|
)
|
||||||
|
->getRow(0);
|
||||||
|
|
||||||
cache()->save(
|
cache()->save(
|
||||||
"{$podcastId}_analytics_podcast_by_country_weekly",
|
"{$podcastId}_analytics_podcast_by_country_weekly",
|
||||||
@ -70,16 +88,34 @@ class AnalyticsPodcastByCountryModel extends Model
|
|||||||
"{$podcastId}_analytics_podcast_by_country_yearly"
|
"{$podcastId}_analytics_podcast_by_country_yearly"
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
|
$oneYearAgo = date('Y-m-d', strtotime('-1 year'));
|
||||||
$found = $this->select('`country_code` as `labels`')
|
$found = $this->select('`country_code` as `labels`')
|
||||||
->selectSum('`hits`', '`values`')
|
->selectSum('`hits`', '`values`')
|
||||||
->where([
|
->where([
|
||||||
'`podcast_id`' => $podcastId,
|
'`podcast_id`' => $podcastId,
|
||||||
'`date` >' => date('Y-m-d', strtotime('-1 year')),
|
'`date` >' => $oneYearAgo,
|
||||||
])
|
])
|
||||||
->groupBy('`labels`')
|
->groupBy('`labels`')
|
||||||
->orderBy('`values`', 'DESC')
|
->orderBy('`values`', 'DESC')
|
||||||
->findAll(10);
|
->findAll(10);
|
||||||
|
|
||||||
|
$found[] = $this->db
|
||||||
|
->query(
|
||||||
|
"SELECT
|
||||||
|
\"Other\" AS `labels`,
|
||||||
|
SUM(`others`.`values`) AS `values`
|
||||||
|
FROM
|
||||||
|
(SELECT SUM(`hits`) AS `values`
|
||||||
|
FROM {$this->db->prefixTable($this->table)}
|
||||||
|
WHERE `date` > $oneyearago
|
||||||
|
GROUP BY `country_code`
|
||||||
|
ORDER BY `values` DESC
|
||||||
|
LIMIT 18446744073709551610 OFFSET 9
|
||||||
|
) AS `others`
|
||||||
|
GROUP BY `labels`"
|
||||||
|
)
|
||||||
|
->getRow(0);
|
||||||
|
|
||||||
cache()->save(
|
cache()->save(
|
||||||
"{$podcastId}_analytics_podcast_by_country_yearly",
|
"{$podcastId}_analytics_podcast_by_country_yearly",
|
||||||
$found,
|
$found,
|
||||||
|
@ -22,4 +22,38 @@ class AnalyticsPodcastByHourModel extends Model
|
|||||||
protected $useSoftDeletes = false;
|
protected $useSoftDeletes = false;
|
||||||
|
|
||||||
protected $useTimestamps = false;
|
protected $useTimestamps = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets hits data for a podcast
|
||||||
|
*
|
||||||
|
* @param int $podcastId
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getData(int $podcastId): array
|
||||||
|
{
|
||||||
|
if (!($found = cache("{$podcastId}_analytics_podcasts_by_hour"))) {
|
||||||
|
$found = $this->select('`hour` as `labels`')
|
||||||
|
->selectSum('`hits`', '`values`')
|
||||||
|
->where([
|
||||||
|
'`podcast_id`' => $podcastId,
|
||||||
|
'`date` >' => date('Y-m-d', strtotime('-60 days')),
|
||||||
|
])
|
||||||
|
->groupBy('`labels`')
|
||||||
|
->orderBy('`labels`', 'ASC')
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
cache()->save(
|
||||||
|
"{$podcastId}_analytics_podcasts_by_hour",
|
||||||
|
$found,
|
||||||
|
600
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,66 @@ class AnalyticsPodcastModel extends Model
|
|||||||
return $found;
|
return $found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets hits data for a podcast
|
||||||
|
*
|
||||||
|
* @param int $podcastId
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getDataByWeekday(int $podcastId): array
|
||||||
|
{
|
||||||
|
if (!($found = cache("{$podcastId}_analytics_podcasts_by_weekday"))) {
|
||||||
|
$found = $this->select(
|
||||||
|
'LEFT(DAYNAME(`date`),3) as `labels`, WEEKDAY(`date`) as `sort_labels`'
|
||||||
|
)
|
||||||
|
->selectSum('`hits`', '`values`')
|
||||||
|
->where([
|
||||||
|
'`podcast_id`' => $podcastId,
|
||||||
|
'`date` >' => date('Y-m-d', strtotime('-60 days')),
|
||||||
|
])
|
||||||
|
->groupBy('`labels`, `sort_labels`')
|
||||||
|
->orderBy('`sort_labels`', 'ASC')
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
cache()->save(
|
||||||
|
"{$podcastId}_analytics_podcasts_by_weekday",
|
||||||
|
$found,
|
||||||
|
600
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets bandwidth data for a podcast
|
||||||
|
*
|
||||||
|
* @param int $podcastId
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getDataBandwidthByDay(int $podcastId): array
|
||||||
|
{
|
||||||
|
if (!($found = cache("{$podcastId}_analytics_podcast_by_bandwidth"))) {
|
||||||
|
$found = $this->select(
|
||||||
|
'`date` as `labels`, round(`bandwidth` / 1048576, 1) as `values`'
|
||||||
|
)
|
||||||
|
->where([
|
||||||
|
'`podcast_id`' => $podcastId,
|
||||||
|
'`date` >' => date('Y-m-d', strtotime('-60 days')),
|
||||||
|
])
|
||||||
|
->orderBy('`labels`', 'ASC')
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
cache()->save(
|
||||||
|
"{$podcastId}_analytics_podcast_by_bandwidth",
|
||||||
|
$found,
|
||||||
|
600
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets hits data for a podcast
|
* Gets hits data for a podcast
|
||||||
*
|
*
|
||||||
|
@ -52,7 +52,7 @@ const drawXYChart = (chartDivId: string, dataUrl: string | null): void => {
|
|||||||
const series = chart.series.push(new am4charts.LineSeries());
|
const series = chart.series.push(new am4charts.LineSeries());
|
||||||
series.dataFields.valueY = "values";
|
series.dataFields.valueY = "values";
|
||||||
series.dataFields.dateX = "labels";
|
series.dataFields.dateX = "labels";
|
||||||
series.tooltipText = "{valueY} hits";
|
series.tooltipText = "{valueY}";
|
||||||
series.strokeWidth = 2;
|
series.strokeWidth = 2;
|
||||||
// Make bullets grow on hover
|
// Make bullets grow on hover
|
||||||
const bullet = series.bullets.push(new am4charts.CircleBullet());
|
const bullet = series.bullets.push(new am4charts.CircleBullet());
|
||||||
@ -68,6 +68,35 @@ const drawXYChart = (chartDivId: string, dataUrl: string | null): void => {
|
|||||||
chart.scrollbarX = new am4core.Scrollbar();
|
chart.scrollbarX = new am4core.Scrollbar();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const drawBarChart = (chartDivId: string, dataUrl: string | null): void => {
|
||||||
|
// Create chart instance
|
||||||
|
const chart = am4core.create(chartDivId, am4charts.XYChart);
|
||||||
|
am4core.percent(100);
|
||||||
|
chart.exporting.menu = new am4core.ExportMenu();
|
||||||
|
chart.exporting.menu.align = "right";
|
||||||
|
chart.exporting.menu.verticalAlign = "bottom";
|
||||||
|
// Set theme
|
||||||
|
am4core.useTheme(am4themes_material);
|
||||||
|
chart.dataSource.url = dataUrl || "";
|
||||||
|
chart.dataSource.parser.options.emptyAs = 0;
|
||||||
|
const categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
|
||||||
|
categoryAxis.dataFields.category = "labels";
|
||||||
|
categoryAxis.renderer.grid.template.location = 0;
|
||||||
|
categoryAxis.renderer.minGridDistance = 30;
|
||||||
|
chart.yAxes.push(new am4charts.ValueAxis());
|
||||||
|
// Create series
|
||||||
|
const series = chart.series.push(new am4charts.ColumnSeries());
|
||||||
|
series.dataFields.valueY = "values";
|
||||||
|
series.dataFields.categoryX = "labels";
|
||||||
|
series.name = "Hits";
|
||||||
|
series.columns.template.tooltipText = "{valueY} hits";
|
||||||
|
series.columns.template.fillOpacity = .8;
|
||||||
|
const columnTemplate = series.columns.template;
|
||||||
|
columnTemplate.strokeWidth = 2;
|
||||||
|
columnTemplate.strokeOpacity = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const drawXYDurationChart = (
|
const drawXYDurationChart = (
|
||||||
chartDivId: string,
|
chartDivId: string,
|
||||||
dataUrl: string | null
|
dataUrl: string | null
|
||||||
@ -205,6 +234,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 "bar-chart":
|
||||||
|
drawBarChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
|
||||||
|
break;
|
||||||
case "xy-duration-chart":
|
case "xy-duration-chart":
|
||||||
drawXYDurationChart(
|
drawXYDurationChart(
|
||||||
chartDiv.id,
|
chartDiv.id,
|
||||||
|
@ -16,6 +16,7 @@ $podcastNavigation = [
|
|||||||
'podcast-analytics-listening-time',
|
'podcast-analytics-listening-time',
|
||||||
'podcast-analytics-players',
|
'podcast-analytics-players',
|
||||||
'podcast-analytics-locations',
|
'podcast-analytics-locations',
|
||||||
|
'podcast-analytics-time-periods',
|
||||||
'podcast-analytics-webpages',
|
'podcast-analytics-webpages',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -29,6 +29,16 @@
|
|||||||
) ?>"></div>
|
) ?>"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-12 text-center">
|
||||||
|
<h2><?= lang('Charts.podcast_by_bandwidth') ?></h2>
|
||||||
|
<div class="chart-xy" id="by-bandwidth-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
|
||||||
|
'analytics-data',
|
||||||
|
$podcast->id,
|
||||||
|
'Podcast',
|
||||||
|
'BandwidthByDay'
|
||||||
|
) ?>"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-12 text-center">
|
<div class="mb-12 text-center">
|
||||||
<h2><?= lang('Charts.episodes_by_day') ?></h2>
|
<h2><?= lang('Charts.episodes_by_day') ?></h2>
|
||||||
<div class="chart-xy" id="by-age-graph" data-chart-type="xy-series-chart" data-chart-url="<?= route_to(
|
<div class="chart-xy" id="by-age-graph" data-chart-type="xy-series-chart" data-chart-url="<?= route_to(
|
||||||
|
36
app/Views/admin/podcast/analytics/time_periods.php
Normal file
36
app/Views/admin/podcast/analytics/time_periods.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?= $this->extend('admin/_layout') ?>
|
||||||
|
|
||||||
|
<?= $this->section('title') ?>
|
||||||
|
<?= $podcast->title ?>
|
||||||
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
|
<?= $this->section('pageTitle') ?>
|
||||||
|
<?= $podcast->title ?>
|
||||||
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
|
<?= $this->section('content') ?>
|
||||||
|
|
||||||
|
<div class="lg:divide-x lg:grid lg:grid-cols-2">
|
||||||
|
<div class="mb-12 mr-6 text-center">
|
||||||
|
<h2><?= lang('Charts.by_weekday') ?></h2>
|
||||||
|
<div class="chart-xy" id="by-weekday-barchart" data-chart-type="bar-chart" data-chart-url="<?= route_to(
|
||||||
|
'analytics-data',
|
||||||
|
$podcast->id,
|
||||||
|
'Podcast',
|
||||||
|
'ByWeekday'
|
||||||
|
) ?>"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-12 mr-6 text-center">
|
||||||
|
<h2><?= lang('Charts.by_hour') ?></h2>
|
||||||
|
<div class="chart-xy" id="by-hour-barchart" data-chart-type="bar-chart" data-chart-url="<?= route_to(
|
||||||
|
'analytics-full-data',
|
||||||
|
$podcast->id,
|
||||||
|
'PodcastByHour'
|
||||||
|
) ?>"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/assets/charts.js" type="module"></script>
|
||||||
|
<?= $this->endSection() ?>
|
Loading…
x
Reference in New Issue
Block a user