mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-02-14 15:31:13 -05:00
Before, there was an error when retrieving stats for a user without feeds. Now, there are default values to display empty stats instead of an exception. See #7884 Closes #7884 Changes proposed in this pull request: - add default values when retrieving stat data How to test the feature manually: 1. create a new user 2. connect as the new user 3. display stats 4. validate that there is no errors
180 lines
6.6 KiB
PHTML
180 lines
6.6 KiB
PHTML
<?php
|
||
declare(strict_types=1);
|
||
/** @var FreshRSS_ViewStats $this */
|
||
$this->partial('aside_subscription');
|
||
?>
|
||
<main class="post">
|
||
<h1><?= _t('admin.stats.main') ?></h1>
|
||
|
||
<div class="box">
|
||
<div class="box-title"><h2><?= _t('admin.stats.entry_repartition') ?></h2></div>
|
||
<div class="box-content scrollbar-thin">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th> </th>
|
||
<th><?= _t('admin.stats.main_stream') ?></th>
|
||
<th><?= _t('admin.stats.all_feeds') ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<th><?= _t('admin.stats.status_total') ?></th>
|
||
<td class="numeric"><?= format_number($this->repartitions['main_stream']['total'] ?? -1) ?></td>
|
||
<td class="numeric"><?= format_number($this->repartitions['all_feeds']['total'] ?? -1) ?></td>
|
||
</tr>
|
||
<tr>
|
||
<th><?= _t('admin.stats.status_read') ?></th>
|
||
<td class="numeric"><?= format_number($this->repartitions['main_stream']['count_reads'] ?? -1) ?></td>
|
||
<td class="numeric"><?= format_number($this->repartitions['all_feeds']['count_reads'] ?? -1) ?></td>
|
||
</tr>
|
||
<tr>
|
||
<th><?= _t('admin.stats.status_unread') ?></th>
|
||
<td class="numeric"><?= format_number($this->repartitions['main_stream']['count_unreads'] ?? -1) ?></td>
|
||
<td class="numeric"><?= format_number($this->repartitions['all_feeds']['count_unreads'] ?? -1) ?></td>
|
||
</tr>
|
||
<tr>
|
||
<th><?= _t('admin.stats.status_favorites') ?></th>
|
||
<td class="numeric"><?= format_number($this->repartitions['main_stream']['count_favorites'] ?? -1) ?></td>
|
||
<td class="numeric"><?= format_number($this->repartitions['all_feeds']['count_favorites'] ?? -1) ?></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="box double-height">
|
||
<div class="box-title"><h2><?= _t('admin.stats.top_feed') ?></h2></div>
|
||
<div class="box-content scrollbar-thin">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th><?= _t('admin.stats.feed') ?></th>
|
||
<th><?= _t('admin.stats.category') ?></th>
|
||
<th><?= _t('admin.stats.entry_count') ?></th>
|
||
<th><?= _t('admin.stats.percent_of_total') ?></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($this->topFeed as $feed): ?>
|
||
<tr>
|
||
<td><a href="<?= _url('stats', 'repartition', 'id', $feed['id']) ?>"><?= $feed['name'] ?></a></td>
|
||
<td><?= $feed['category'] ?></td>
|
||
<td class="numeric"><?= format_number($feed['count']) ?></td>
|
||
<td class="numeric"><?php
|
||
if (!empty($this->repartitions['all_feeds']['total'])) {
|
||
echo format_number($feed['count'] / $this->repartitions['all_feeds']['total'] * 100, 1);
|
||
}
|
||
?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<br />
|
||
<div class="box double-width double-height">
|
||
<div class="box-title"><h2><?= _t('admin.stats.entry_per_day') ?></h2></div>
|
||
<div class="box-content scrollbar-thin">
|
||
<canvas id="statsEntriesPerDay"></canvas>
|
||
<script class="jsonData-stats" type="application/json">
|
||
<?= json_encode([
|
||
'canvasID' => 'statsEntriesPerDay',
|
||
'charttype' => 'barWithAverage',
|
||
'labelBarChart' => _t('admin.stats.entry_count'),
|
||
'dataBarChart' => $this->entryCount,
|
||
'labelAverage' => 'Average (' . $this->average . ')',
|
||
'dataAverage' => $this->average,
|
||
'xAxisLabels' => $this->last30DaysLabels,
|
||
], JSON_UNESCAPED_UNICODE)
|
||
?></script>
|
||
</div>
|
||
</div>
|
||
|
||
<?php
|
||
// Function to generate a color palette
|
||
/**
|
||
* Generate a color palette.
|
||
*
|
||
* @param int $count The number of colors to generate.
|
||
* @return array<int,string> An array of HSL color strings.
|
||
*/
|
||
function generateColorPalette(int $count): array {
|
||
$colors = [];
|
||
for ($i = 0; $i < $count; $i++) {
|
||
$hue = ($i / $count) * 360; // Distribute colors evenly around the color wheel
|
||
$saturation = 70; // Fixed saturation
|
||
$lightness = 50; // Fixed lightness
|
||
$colors[] = "hsl($hue, {$saturation}%, {$lightness}%)";
|
||
}
|
||
return $colors;
|
||
}
|
||
|
||
// 1. Get all unique category labels and sort them
|
||
$allLabels = array_unique(array_merge($this->feedByCategory['label'] ?? [], $this->entryByCategory['label'] ?? []));
|
||
sort($allLabels); // Ensure consistent order
|
||
|
||
// 2. Generate a color palette based on the number of unique categories
|
||
$colorPalette = generateColorPalette(count($allLabels));
|
||
|
||
// 3. Map categories to colors
|
||
$colorMap = array_combine($allLabels, $colorPalette);
|
||
|
||
// 4. Align data and labels for both charts
|
||
$feedData = array_fill_keys($allLabels, 0); // Initialize data with all categories
|
||
foreach ($this->feedByCategory['label'] ?? [] as $index => $label) {
|
||
$feedData[$label] = $this->feedByCategory['data'][$index];
|
||
}
|
||
$entryData = array_fill_keys($allLabels, 0); // Initialize data with all categories
|
||
foreach ($this->entryByCategory['label'] ?? [] as $index => $label) {
|
||
$entryData[$label] = $this->entryByCategory['data'][$index];
|
||
}
|
||
|
||
// Final data and labels
|
||
$feedLabels = array_keys($feedData);
|
||
$feedColors = array_map(fn($label) => $colorMap[$label], $feedLabels);
|
||
$feedValues = array_values($feedData);
|
||
|
||
$entryLabels = array_keys($entryData);
|
||
$entryColors = array_map(fn($label) => $colorMap[$label], $entryLabels);
|
||
$entryValues = array_values($entryData);
|
||
?>
|
||
<br id="stats_per_category" />
|
||
<div class="box double-height" id="feed_per_category">
|
||
<div class="box-title"><h2><?= _t('admin.stats.feed_per_category') ?></h2><a href="#feed_per_category" class="btn target-hidden">+</a><a href="#stats_per_category" class="btn target-visible">-</a></div>
|
||
<div class="box-content scrollbar-thin">
|
||
<canvas id="statsFeedsPerCategory"></canvas>
|
||
<script class="jsonData-stats" type="application/json">
|
||
<?= json_encode([
|
||
'canvasID' => 'statsFeedsPerCategory',
|
||
'charttype' => 'doughnut',
|
||
'data' => $feedValues,
|
||
'labels' => $feedLabels,
|
||
'backgroundColor' => $feedColors,
|
||
], JSON_UNESCAPED_UNICODE); ?>
|
||
</script>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="box double-height" id="entry_per_category">
|
||
<div class="box-title"><h2><?= _t('admin.stats.entry_per_category') ?></h2><a href="#entry_per_category" class="btn target-hidden">+</a><a href="#stats_per_category" class="btn target-visible">-</a></div>
|
||
<div class="box-content scrollbar-thin">
|
||
<canvas id="statsEntriesPerCategory"></canvas>
|
||
<script class="jsonData-stats" type="application/json">
|
||
<?= json_encode([
|
||
'canvasID' => 'statsEntriesPerCategory',
|
||
'charttype' => 'doughnut',
|
||
'data' => $entryValues,
|
||
'labels' => $entryLabels,
|
||
'backgroundColor' => $entryColors,
|
||
], JSON_UNESCAPED_UNICODE); ?>
|
||
</script>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</main>
|
||
|
||
<script src="../scripts/statsWithChartjs.js?<?= @filemtime(PUBLIC_PATH . '/scripts/statsWithChartjs.js') ?>"></script>
|