diff --git a/app/Config/Routes.php b/app/Config/Routes.php index f435cc76..d578e387 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -158,6 +158,14 @@ $routes->group( '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( 'players', 'Podcast::viewAnalyticsPlayers/$1', diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php index a6e9e202..1e51120e 100644 --- a/app/Controllers/Admin/Podcast.php +++ b/app/Controllers/Admin/Podcast.php @@ -98,6 +98,14 @@ class Podcast extends BaseController 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() { $data = ['podcast' => $this->podcast]; diff --git a/app/Language/en/Breadcrumb.php b/app/Language/en/Breadcrumb.php index be49e105..a7b26ff7 100644 --- a/app/Language/en/Breadcrumb.php +++ b/app/Language/en/Breadcrumb.php @@ -28,4 +28,5 @@ return [ 'unique-listeners' => 'Unique listeners', 'players' => 'Players', 'listening-time' => 'Listening time', + 'time-periods' => 'Time periods', ]; diff --git a/app/Language/en/Charts.php b/app/Language/en/Charts.php index 206df556..0b6078d1 100644 --- a/app/Language/en/Charts.php +++ b/app/Language/en/Charts.php @@ -30,4 +30,7 @@ return [ 'podcast_bots' => 'Bots (crawlers)', 'daily_listening_time' => 'Daily 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)', ]; diff --git a/app/Language/en/PodcastNavigation.php b/app/Language/en/PodcastNavigation.php index 26288408..712fc8bd 100644 --- a/app/Language/en/PodcastNavigation.php +++ b/app/Language/en/PodcastNavigation.php @@ -26,4 +26,5 @@ return [ 'podcast-analytics-unique-listeners' => 'Unique listeners', 'podcast-analytics-players' => 'Players', 'podcast-analytics-listening-time' => 'Listening time', + 'podcast-analytics-time-periods' => 'Time periods', ]; diff --git a/app/Language/fr/Breadcrumb.php b/app/Language/fr/Breadcrumb.php index 50405e53..5c55f168 100644 --- a/app/Language/fr/Breadcrumb.php +++ b/app/Language/fr/Breadcrumb.php @@ -28,4 +28,5 @@ return [ 'unique-listeners' => 'Auditeurs uniques', 'players' => 'Lecteurs', 'listening-time' => 'Durée d’écoute', + 'time-periods' => 'Périodes', ]; diff --git a/app/Language/fr/Charts.php b/app/Language/fr/Charts.php index 14607d26..1b72c51b 100644 --- a/app/Language/fr/Charts.php +++ b/app/Language/fr/Charts.php @@ -43,4 +43,7 @@ return [ 'podcast_bots' => 'Robots (bots)', 'daily_listening_time' => 'Durée quotidienne 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)', ]; diff --git a/app/Language/fr/PodcastNavigation.php b/app/Language/fr/PodcastNavigation.php index 9a2e09e9..39852186 100644 --- a/app/Language/fr/PodcastNavigation.php +++ b/app/Language/fr/PodcastNavigation.php @@ -26,4 +26,5 @@ return [ 'podcast-analytics-unique-listeners' => 'Auditeurs uniques', 'podcast-analytics-players' => 'Lecteurs', 'podcast-analytics-listening-time' => 'Durée d’écoute', + 'podcast-analytics-time-periods' => 'Périodes', ]; diff --git a/app/Models/AnalyticsPodcastByCountryModel.php b/app/Models/AnalyticsPodcastByCountryModel.php index 162170a5..ad9bb395 100644 --- a/app/Models/AnalyticsPodcastByCountryModel.php +++ b/app/Models/AnalyticsPodcastByCountryModel.php @@ -37,15 +37,33 @@ class AnalyticsPodcastByCountryModel extends Model "{$podcastId}_analytics_podcast_by_country_weekly" )) ) { + $oneWeekAgo=date('Y-m-d', strtotime('-1 week')); $found = $this->select('`country_code` as `labels`') ->selectSum('`hits`', '`values`') ->where([ '`podcast_id`' => $podcastId, - '`date` >' => date('Y-m-d', strtotime('-1 week')), + '`date` >' => $oneWeekAgo, ]) ->groupBy('`labels`') ->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( "{$podcastId}_analytics_podcast_by_country_weekly", @@ -70,16 +88,34 @@ class AnalyticsPodcastByCountryModel extends Model "{$podcastId}_analytics_podcast_by_country_yearly" )) ) { + $oneYearAgo = date('Y-m-d', strtotime('-1 year')); $found = $this->select('`country_code` as `labels`') ->selectSum('`hits`', '`values`') ->where([ '`podcast_id`' => $podcastId, - '`date` >' => date('Y-m-d', strtotime('-1 year')), + '`date` >' => $oneYearAgo, ]) ->groupBy('`labels`') ->orderBy('`values`', 'DESC') ->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( "{$podcastId}_analytics_podcast_by_country_yearly", $found, diff --git a/app/Models/AnalyticsPodcastByHourModel.php b/app/Models/AnalyticsPodcastByHourModel.php index 9dd82a4d..1960e783 100644 --- a/app/Models/AnalyticsPodcastByHourModel.php +++ b/app/Models/AnalyticsPodcastByHourModel.php @@ -22,4 +22,38 @@ class AnalyticsPodcastByHourModel extends Model protected $useSoftDeletes = 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; + } + + + + + } diff --git a/app/Models/AnalyticsPodcastModel.php b/app/Models/AnalyticsPodcastModel.php index e2c60ff5..1d44a551 100644 --- a/app/Models/AnalyticsPodcastModel.php +++ b/app/Models/AnalyticsPodcastModel.php @@ -46,6 +46,66 @@ class AnalyticsPodcastModel extends Model 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 * diff --git a/app/Views/_assets/modules/Charts.ts b/app/Views/_assets/modules/Charts.ts index 41149edd..9e2f754e 100644 --- a/app/Views/_assets/modules/Charts.ts +++ b/app/Views/_assets/modules/Charts.ts @@ -52,7 +52,7 @@ const drawXYChart = (chartDivId: string, dataUrl: string | null): void => { const series = chart.series.push(new am4charts.LineSeries()); series.dataFields.valueY = "values"; series.dataFields.dateX = "labels"; - series.tooltipText = "{valueY} hits"; + series.tooltipText = "{valueY}"; series.strokeWidth = 2; // Make bullets grow on hover 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(); }; +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 = ( chartDivId: string, dataUrl: string | null @@ -205,6 +234,9 @@ const DrawCharts = (): void => { case "xy-chart": drawXYChart(chartDiv.id, chartDiv.getAttribute("data-chart-url")); break; + case "bar-chart": + drawBarChart(chartDiv.id, chartDiv.getAttribute("data-chart-url")); + break; case "xy-duration-chart": drawXYDurationChart( chartDiv.id, diff --git a/app/Views/admin/podcast/_sidebar.php b/app/Views/admin/podcast/_sidebar.php index cf637465..704a7444 100644 --- a/app/Views/admin/podcast/_sidebar.php +++ b/app/Views/admin/podcast/_sidebar.php @@ -16,6 +16,7 @@ $podcastNavigation = [ 'podcast-analytics-listening-time', 'podcast-analytics-players', 'podcast-analytics-locations', + 'podcast-analytics-time-periods', 'podcast-analytics-webpages', ], ], diff --git a/app/Views/admin/podcast/analytics/index.php b/app/Views/admin/podcast/analytics/index.php index f604d175..54b95443 100644 --- a/app/Views/admin/podcast/analytics/index.php +++ b/app/Views/admin/podcast/analytics/index.php @@ -29,6 +29,16 @@ ) ?>"> +
+

+
+
+

+
+

+
+
+ +
+

+
+
+ +
+ + +endSection() ?>