Ajout de statistiques de l'application

Conflicts:
	app/i18n/en.php
	app/i18n/fr.php
pull/390/head
Alexis Degrugillier 11 years ago
parent 3ddffcfe5d
commit e98ac32716
  1. 10
      app/Controllers/indexController.php
  2. 178
      app/Models/StatsDAO.php
  3. 14
      app/i18n/en.php
  4. 14
      app/i18n/fr.php
  5. 2
      app/layout/aside_flux.phtml
  6. 1
      app/layout/header.phtml
  7. 92
      app/views/index/stats.phtml
  8. 27
      p/scripts/flotr2.min.js
  9. 43
      p/themes/Origine/freshrss.css

@ -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,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,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>&nbsp;</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>

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;
}
Loading…
Cancel
Save