Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • adaures/castopod
  • mkljczk/castopod-host
  • spaetz/castopod-host
  • PatrykMis/castopod
  • jonas/castopod
  • ajeremias/castopod
  • misuzu/castopod
  • KrzysztofDomanczyk/castopod
  • Behel/castopod
  • nebulon/castopod
  • ewen/castopod
  • NeoluxConsulting/castopod
  • nateritter/castopod-og
  • prcutler/castopod
14 results
Show changes
Commits on Source (2)
Showing
with 178 additions and 9 deletions
# [1.0.0-alpha.2](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2020-10-19)
### Features
* add cumulative listening time charts ([588b4d2](https://code.podlibre.org/podlibre/castopod/commit/588b4d28da00bc12d02126e23181690f54d81716))
# 1.0.0-alpha.1 (2020-10-16)
......
......@@ -153,6 +153,14 @@ $routes->group(
'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(
'players',
'Podcast::viewAnalyticsPlayers/$1',
......
......@@ -90,6 +90,14 @@ class Podcast extends BaseController
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()
{
$data = ['podcast' => $this->podcast];
......
......@@ -27,4 +27,5 @@ return [
'webpages' => 'Web pages',
'unique-listeners' => 'Unique listeners',
'players' => 'Players',
'listening-time' => 'Listening time',
];
......@@ -27,4 +27,6 @@ return [
'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)',
'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-unique-listeners' => 'Unique listeners',
'podcast-analytics-players' => 'Players',
'podcast-analytics-listening-time' => 'Listening time',
];
......@@ -27,4 +27,5 @@ return [
'webpages' => 'Pages web',
'unique-listeners' => 'Auditeurs uniques',
'players' => 'Lecteurs',
'listening-time' => 'Durée d’écoute',
];
......@@ -39,4 +39,6 @@ return [
'by_entry_page' =>
'Fréquentation des pages web par page d’entrée (sur la dernière semaine)',
'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-unique-listeners' => 'Auditeurs uniques',
'podcast-analytics-players' => 'Lecteurs',
'podcast-analytics-listening-time' => 'Durée d’écoute',
];
......@@ -142,4 +142,71 @@ class AnalyticsPodcastByEpisodeModel extends Model
}
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}"
))
) {
$found = $this->select(
'`country_code`, `region_code`, `latitude`, `longitude`'
)
$found = $this->select('`country_code`, `region_code`')
->selectSum('`hits`', '`value`')
->groupBy(
'`country_code`, `region_code`, `latitude`, `longitude`'
)
->selectAvg('`latitude`')
->selectAvg('`longitude`')
->groupBy('`country_code`, `region_code`')
->where([
'`podcast_id`' => $podcastId,
'`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();
};
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 = (
chartDivId: string,
dataUrl: string | null
......@@ -152,6 +187,9 @@ const DrawCharts = (): void => {
case "xy-chart":
drawXYChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
break;
case "xy-duration-chart":
drawXYDurationChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
break;
case "xy-series-chart":
drawXYSeriesChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
break;
......
......@@ -13,6 +13,7 @@ $podcastNavigation = [
'items' => [
'podcast-analytics',
'podcast-analytics-unique-listeners',
'podcast-analytics-listening-time',
'podcast-analytics-players',
'podcast-analytics-locations',
'podcast-analytics-webpages',
......
<?= $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() ?>
{
"name": "podlibre/castopod",
"version": "1.0.0-alpha1",
"version": "1.0.0-alpha2",
"type": "project",
"description": "Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.",
"homepage": "https://castopod.org",
......
{
"name": "castopod",
"version": "1.0.0-alpha.1",
"version": "1.0.0-alpha.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
{
"name": "castopod",
"version": "1.0.0-alpha.1",
"version": "1.0.0-alpha.2",
"description": "Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.",
"private": true,
"license": "AGPL-3.0-or-later",
......