mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-10-24 03:58:33 -04:00
Merge pull request #390 from aledeg/statistics
Ajout de statistiques de l'application
This commit is contained in:
commit
0233d1d222
9 files changed
+378
-3
No files matched your search
@@ -6,12 +6,14 @@ class FreshRSS_index_Controller extends Minz_ActionController {
|
||||
private $entryDAO;
|
||||
private $feedDAO;
|
||||
private $catDAO;
|
||||
private $statsDAO;
|
||||
|
||||
function __construct($router) {
|
||||
parent::__construct($router);
|
||||
$this->entryDAO = new FreshRSS_EntryDAO ();
|
||||
$this->feedDAO = new FreshRSS_FeedDAO ();
|
||||
$this->catDAO = new FreshRSS_CategoryDAO ();
|
||||
$this->statsDAO = new FreshRSS_StatsDAO ();
|
||||
}
|
||||
|
||||
public function indexAction () {
|
||||
@@ -198,6 +200,14 @@ class FreshRSS_index_Controller extends Minz_ActionController {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function statsAction () {
|
||||
Minz_View::appendScript (Minz_Url::display ('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
|
||||
$this->view->repartition = $this->statsDAO->calculateEntryRepartition();
|
||||
$this->view->count = ($this->statsDAO->calculateEntryCount());
|
||||
$this->view->feedByCategory = $this->statsDAO->calculateFeedByCategory();
|
||||
$this->view->entryByCategory = $this->statsDAO->calculateEntryByCategory();
|
||||
}
|
||||
|
||||
public function aboutAction () {
|
||||
Minz_View::prependTitle (Minz_Translate::t ('about') . ' · ');
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
class FreshRSS_StatsDAO extends Minz_ModelPdo {
|
||||
|
||||
/**
|
||||
* Calculates entry repartition for all feeds and for main stream.
|
||||
* The repartition includes:
|
||||
* - total entries
|
||||
* - read entries
|
||||
* - unread entries
|
||||
* - favorite entries
|
||||
*
|
||||
* @return type
|
||||
*/
|
||||
public function calculateEntryRepartition() {
|
||||
$repartition = array();
|
||||
|
||||
// Generates the repartition for the main stream of entry
|
||||
$sql = <<<SQL
|
||||
SELECT COUNT(1) AS `total`,
|
||||
COUNT(1) - SUM(e.is_read) AS `unread`,
|
||||
SUM(e.is_read) AS `read`,
|
||||
SUM(e.is_favorite) AS `favorite`
|
||||
FROM {$this->prefix}entry AS e
|
||||
, {$this->prefix}feed AS f
|
||||
WHERE e.id_feed = f.id
|
||||
AND f.priority = 10
|
||||
SQL;
|
||||
$stm = $this->bd->prepare($sql);
|
||||
$stm->execute();
|
||||
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
|
||||
$repartition['main_stream'] = $res[0];
|
||||
|
||||
// Generates the repartition for all entries
|
||||
$sql = <<<SQL
|
||||
SELECT COUNT(1) AS `total`,
|
||||
COUNT(1) - SUM(e.is_read) AS `unread`,
|
||||
SUM(e.is_read) AS `read`,
|
||||
SUM(e.is_favorite) AS `favorite`
|
||||
FROM {$this->prefix}entry AS e
|
||||
SQL;
|
||||
$stm = $this->bd->prepare($sql);
|
||||
$stm->execute();
|
||||
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
|
||||
$repartition['all_feeds'] = $res[0];
|
||||
|
||||
return $repartition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates entry count per day on a 30 days period.
|
||||
* Returns the result as a JSON string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function calculateEntryCount() {
|
||||
$count = array();
|
||||
|
||||
// Generates a list of 30 last day to be sure we always have 30 days.
|
||||
// If we do not do that kind of thing, we'll end up with holes in the
|
||||
// days if the user do not have a lot of feeds.
|
||||
$sql = <<<SQL
|
||||
SELECT - (tens.val + units.val + 1) AS day
|
||||
FROM (
|
||||
SELECT 0 AS val
|
||||
UNION ALL SELECT 1
|
||||
UNION ALL SELECT 2
|
||||
UNION ALL SELECT 3
|
||||
UNION ALL SELECT 4
|
||||
UNION ALL SELECT 5
|
||||
UNION ALL SELECT 6
|
||||
UNION ALL SELECT 7
|
||||
UNION ALL SELECT 8
|
||||
UNION ALL SELECT 9
|
||||
) AS units
|
||||
CROSS JOIN (
|
||||
SELECT 0 AS val
|
||||
UNION ALL SELECT 10
|
||||
UNION ALL SELECT 20
|
||||
) AS tens
|
||||
ORDER BY day ASC
|
||||
SQL;
|
||||
$stm = $this->bd->prepare($sql);
|
||||
$stm->execute();
|
||||
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($res as $value) {
|
||||
$count[$value['day']] = 0;
|
||||
}
|
||||
|
||||
// Get stats per day for the last 30 days and applies the result on
|
||||
// the array created with the last query.
|
||||
$sql = <<<SQL
|
||||
SELECT DATEDIFF(FROM_UNIXTIME(e.date), NOW()) AS day,
|
||||
COUNT(1) AS count
|
||||
FROM {$this->prefix}entry AS e
|
||||
WHERE FROM_UNIXTIME(e.date, '%Y%m%d') BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -30 DAY), '%Y%m%d') AND DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -1 DAY), '%Y%m%d')
|
||||
GROUP BY day
|
||||
ORDER BY day ASC
|
||||
SQL;
|
||||
$stm = $this->bd->prepare($sql);
|
||||
$stm->execute();
|
||||
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($res as $value) {
|
||||
$count[$value['day']] = (int) $value['count'];
|
||||
}
|
||||
|
||||
return $this->convertToSerie($count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates feed count per category.
|
||||
* Returns the result as a JSON string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function calculateFeedByCategory() {
|
||||
$sql = <<<SQL
|
||||
SELECT c.name AS label
|
||||
, COUNT(f.id) AS data
|
||||
FROM {$this->prefix}category AS c,
|
||||
{$this->prefix}feed AS f
|
||||
WHERE c.id = f.category
|
||||
GROUP BY label
|
||||
SQL;
|
||||
$stm = $this->bd->prepare($sql);
|
||||
$stm->execute();
|
||||
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return $this->convertToPieSerie($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates entry count per category.
|
||||
* Returns the result as a JSON string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function calculateEntryByCategory() {
|
||||
$sql = <<<SQL
|
||||
SELECT c.name AS label
|
||||
, COUNT(e.id) AS data
|
||||
FROM {$this->prefix}category AS c,
|
||||
{$this->prefix}feed AS f,
|
||||
{$this->prefix}entry AS e
|
||||
WHERE c.id = f.category
|
||||
AND f.id = e.id_feed
|
||||
GROUP BY label
|
||||
SQL;
|
||||
$stm = $this->bd->prepare($sql);
|
||||
$stm->execute();
|
||||
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return $this->convertToPieSerie($res);
|
||||
}
|
||||
|
||||
private function convertToSerie($data) {
|
||||
$serie = array();
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$serie[] = array($key, $value);
|
||||
}
|
||||
|
||||
return json_encode($serie);
|
||||
}
|
||||
|
||||
private function convertToPieSerie($data) {
|
||||
$serie = array();
|
||||
|
||||
foreach ($data as $value) {
|
||||
$value['data'] = array(array(0, (int)$value['data']));
|
||||
$serie[] = $value;
|
||||
}
|
||||
|
||||
return json_encode($serie);
|
||||
}
|
||||
|
||||
}
|
||||
+13
-1
@@ -13,6 +13,7 @@ return array (
|
||||
'category' => 'Category',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'about' => 'About',
|
||||
'stats' => 'Statistics',
|
||||
|
||||
'your_rss_feeds' => 'Your RSS feeds',
|
||||
'add_rss_feed' => 'Add a RSS feed',
|
||||
@@ -20,7 +21,8 @@ return array (
|
||||
'import_export_opml' => 'Import / export (OPML)',
|
||||
|
||||
'subscription_management' => 'Subscriptions management',
|
||||
'all_feeds' => 'Main stream',
|
||||
'main_stream' => 'Main stream',
|
||||
'all_feeds' => 'All feeds',
|
||||
'favorite_feeds' => 'Favourites (%d)',
|
||||
'not_read' => '%d unread',
|
||||
'not_reads' => '%d unread',
|
||||
@@ -297,4 +299,14 @@ return array (
|
||||
// format for date() function, %s allows to indicate month in letter
|
||||
'format_date' => '%s j\<\s\u\p\>S\<\/\s\u\p\> Y',
|
||||
'format_date_hour' => '%s j\<\s\u\p\>S\<\/\s\u\p\> Y \a\t H\:i',
|
||||
|
||||
'status_favorites' => 'favourites',
|
||||
'status_read' => 'read',
|
||||
'status_unread' => 'unread',
|
||||
'status_total' => 'total',
|
||||
|
||||
'stats_entry_repartition' => 'Entry repartition',
|
||||
'stats_entry_per_day' => 'Entry per day (last 30 days)',
|
||||
'stats_feed_per_category' => 'Feed per category',
|
||||
'stats_entry_per_category' => 'Entry per category',
|
||||
);
|
||||
+13
-1
@@ -13,6 +13,7 @@ return array (
|
||||
'category' => 'Catégorie',
|
||||
'shortcuts' => 'Raccourcis',
|
||||
'about' => 'À propos',
|
||||
'stats' => 'Statistiques',
|
||||
|
||||
'your_rss_feeds' => 'Vos flux RSS',
|
||||
'add_rss_feed' => 'Ajouter un flux RSS',
|
||||
@@ -20,7 +21,8 @@ return array (
|
||||
'import_export_opml' => 'Importer / exporter (OPML)',
|
||||
|
||||
'subscription_management' => 'Gestion des abonnements',
|
||||
'all_feeds' => 'Flux principal',
|
||||
'main_stream' => 'Flux principal',
|
||||
'all_feeds' => 'Tous les flux',
|
||||
'favorite_feeds' => 'Favoris (%d)',
|
||||
'not_read' => '%d non lu',
|
||||
'not_reads' => '%d non lus',
|
||||
@@ -297,4 +299,14 @@ return array (
|
||||
// format pour la fonction date(), %s permet d'indiquer le mois en toutes lettres
|
||||
'format_date' => 'j %s Y',
|
||||
'format_date_hour' => 'j %s Y \à H\:i',
|
||||
|
||||
'status_favorites' => 'favoris',
|
||||
'status_read' => 'lus',
|
||||
'status_unread' => 'non lus',
|
||||
'status_total' => 'total',
|
||||
|
||||
'stats_entry_repartition' => 'Répartition des articles',
|
||||
'stats_entry_per_day' => 'Nombre d’articles par jour (30 derniers jours)',
|
||||
'stats_feed_per_category' => 'Flux par categorie',
|
||||
'stats_entry_per_category' => 'Article par categorie',
|
||||
);
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="category all">
|
||||
<a data-unread="<?php echo $this->nb_not_read; ?>" class="btn<?php echo $this->get_c == 'a' ? ' active' : ''; ?>" href="<?php echo Minz_Url::display($arUrl); ?>">
|
||||
<?php echo FreshRSS_Themes::icon('all'); ?>
|
||||
<?php echo Minz_Translate::t ('all_feeds'); ?>
|
||||
<?php echo Minz_Translate::t ('main_stream'); ?>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -74,6 +74,7 @@ if (Minz_Configuration::canLogIn()) {
|
||||
<li class="separator"></li>
|
||||
<li class="item"><a href="<?php echo _url ('configure', 'users'); ?>"><?php echo Minz_Translate::t ('users'); ?></a></li>
|
||||
<li class="separator"></li>
|
||||
<li class="item"><a href="<?php echo _url ('index', 'stats'); ?>"><?php echo Minz_Translate::t ('stats'); ?></a></li>
|
||||
<li class="item"><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about'); ?></a></li>
|
||||
<li class="item"><a href="<?php echo _url ('index', 'logs'); ?>"><?php echo Minz_Translate::t ('logs'); ?></a></li>
|
||||
<?php
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<div class="post content">
|
||||
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||
|
||||
<h1><?php echo Minz_Translate::t ('stats'); ?></h1>
|
||||
|
||||
<div class="stat">
|
||||
<h2><?php echo Minz_Translate::t ('stats_entry_repartition')?></h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th><?php echo Minz_Translate::t ('main_stream')?></th>
|
||||
<th><?php echo Minz_Translate::t ('all_feeds')?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><?php echo Minz_Translate::t ('status_total')?></th>
|
||||
<td><?php echo $this->repartition['main_stream']['total']?></td>
|
||||
<td><?php echo $this->repartition['all_feeds']['total']?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php echo Minz_Translate::t ('status_read')?></th>
|
||||
<td><?php echo $this->repartition['main_stream']['read']?></td>
|
||||
<td><?php echo $this->repartition['all_feeds']['read']?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php echo Minz_Translate::t ('status_unread')?></th>
|
||||
<td><?php echo $this->repartition['main_stream']['unread']?></td>
|
||||
<td><?php echo $this->repartition['all_feeds']['unread']?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php echo Minz_Translate::t ('status_favorites')?></th>
|
||||
<td><?php echo $this->repartition['main_stream']['favorite']?></td>
|
||||
<td><?php echo $this->repartition['all_feeds']['favorite']?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<h2><?php echo Minz_Translate::t ('stats_entry_per_day')?></h2>
|
||||
<div id="statsEntryPerDay" style="height: 300px"></div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<h2><?php echo Minz_Translate::t ('stats_feed_per_category')?></h2>
|
||||
<div id="statsFeedPerCategory" style="height: 300px"></div>
|
||||
<div id="statsFeedPerCategoryLegend"></div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<h2><?php echo Minz_Translate::t ('stats_entry_per_category')?></h2>
|
||||
<div id="statsEntryPerCategory" style="height: 300px"></div>
|
||||
<div id="statsEntryPerCategoryLegend"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Entry per day
|
||||
Flotr.draw(document.getElementById('statsEntryPerDay'),
|
||||
[<?php echo $this->count ?>],
|
||||
{
|
||||
bars: {horizontal: false, show: true},
|
||||
xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0},
|
||||
yaxis: {showLabels: false},
|
||||
mouse: {relative: true, track: true, trackDecimals: 0,trackFormatter: function(obj) {return obj.y;}}
|
||||
});
|
||||
// Feed per category
|
||||
Flotr.draw(document.getElementById('statsFeedPerCategory'),
|
||||
<?php echo $this->feedByCategory ?>,
|
||||
{
|
||||
grid: {verticalLines: false, horizontalLines: false},
|
||||
pie: {explode: 2, show: true},
|
||||
xaxis: {showLabels: false},
|
||||
yaxis: {showLabels: false},
|
||||
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.y;}},
|
||||
legend: {container: document.getElementById('statsFeedPerCategoryLegend')}
|
||||
});
|
||||
// Entry per category
|
||||
Flotr.draw(document.getElementById('statsEntryPerCategory'),
|
||||
<?php echo $this->entryByCategory ?>,
|
||||
{
|
||||
grid: {verticalLines: false, horizontalLines: false},
|
||||
pie: {explode: 2, show: true},
|
||||
xaxis: {showLabels: false},
|
||||
yaxis: {showLabels: false},
|
||||
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.y;}},
|
||||
legend: {container: document.getElementById('statsEntryPerCategoryLegend')}
|
||||
});
|
||||
</script>
|
||||
Vendored
+27
File diff suppressed because one or more lines are too long.
@@ -898,3 +898,46 @@ input.extend {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.stat{
|
||||
border:1px solid #aaa;
|
||||
border-radius:10px;
|
||||
box-shadow:2px 2px 5px #aaa;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.stat h2{
|
||||
border-bottom:1px solid #aaa;
|
||||
margin:0;
|
||||
padding-left:5px;
|
||||
}
|
||||
.stat h2 + *{
|
||||
margin:5px;
|
||||
}
|
||||
.stat h2 + div + div{
|
||||
margin:0 5px 5px 5px;
|
||||
}
|
||||
.stat h2 + table{
|
||||
border-collapse:collapse;
|
||||
width:calc(100% - 10px);
|
||||
}
|
||||
.stat h2 + table th{
|
||||
text-transform:capitalize;
|
||||
}
|
||||
.stat h2 + table td{
|
||||
text-align:center;
|
||||
}
|
||||
.stat h2 + table thead th{
|
||||
border-bottom:2px solid #aaa;
|
||||
}
|
||||
.stat h2 + table tbody tr *{
|
||||
border-bottom:1px solid #aaa;
|
||||
}
|
||||
.stat h2 + table tbody tr:last-child *{
|
||||
border-bottom:0;
|
||||
}
|
||||
.stat h2 + table tr *{
|
||||
border-left:2px solid #aaa;
|
||||
}
|
||||
.stat h2 + table tr *:first-child{
|
||||
border-left:0;
|
||||
}
|
||||
Reference in new issue
Block a user