Ver código fonte

Merge pull request #548 from aledeg/more-stats

Add article repartition in stats
Marien Fressinaud 11 anos atrás
pai
commit
4a0e5ac037

+ 14 - 1
app/Controllers/statsController.php

@@ -55,7 +55,20 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
 
 		$this->view->idleFeeds = $idleFeeds;
 	}
-	
+
+	public function repartitionAction() {
+		$statsDAO = FreshRSS_Factory::createStatsDAO();
+		$feedDAO = FreshRSS_Factory::createFeedDao();
+		Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
+		$id = Minz_Request::param ('id', null);
+		$this->view->feed = $feedDAO->searchById($id);
+		$this->view->days = $statsDAO->getDays();
+		$this->view->months = $statsDAO->getMonths();
+		$this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id);
+		$this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id);
+		$this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id);
+	}
+
 	public function firstAction() {
 		if (!$this->view->loginOk) {
 			Minz_Error::error(

+ 128 - 1
app/Models/StatsDAO.php

@@ -85,9 +85,83 @@ SQL;
 	 * @return array
 	 */
 	protected function initEntryCountArray() {
+		return $this->initStatsArray(-self::ENTRY_COUNT_PERIOD, -1);
+	}
+
+	/**
+	 * Calculates the number of article per hour of the day per feed
+	 *
+	 * @param integer $feed id
+	 * @return string
+	 */
+	public function calculateEntryRepartitionPerFeedPerHour($feed = null) {
+		return $this->calculateEntryRepartitionPerFeedPerPeriod('%H', $feed);
+	}
+
+	/**
+	 * Calculates the number of article per day of week per feed
+	 *
+	 * @param integer $feed id
+	 * @return string
+	 */
+	public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) {
+		return $this->calculateEntryRepartitionPerFeedPerPeriod('%w', $feed);
+	}
+
+	/**
+	 * Calculates the number of article per month per feed
+	 *
+	 * @param integer $feed
+	 * @return string
+	 */
+	public function calculateEntryRepartitionPerFeedPerMonth($feed = null) {
+		return $this->calculateEntryRepartitionPerFeedPerPeriod('%m', $feed);
+	}
+
+	/**
+	 * Calculates the number of article per period per feed
+	 *
+	 * @param string $period format string to use for grouping
+	 * @param integer $feed id
+	 * @return string
+	 */
+	protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
+		if ($feed) {
+			$restrict = "WHERE e.id_feed = {$feed}";
+		} else {
+			$restrict = '';
+		}
+		$sql = <<<SQL
+SELECT DATE_FORMAT(FROM_UNIXTIME(e.date), '{$period}') AS period
+, COUNT(1) AS count
+FROM {$this->prefix}entry AS e
+{$restrict}
+GROUP BY period
+ORDER BY period ASC
+SQL;
+
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_NAMED);
+
+		foreach ($res as $value) {
+			$repartition[(int) $value['period']] = (int) $value['count'];
+		}
+
+		return $this->convertToSerie($repartition);
+	}
+
+	/**
+	 * Initialize an array for statistics depending on a range
+	 *
+	 * @param integer $min
+	 * @param integer $max
+	 * @return array
+	 */
+	protected function initStatsArray($min, $max) {
 		return array_map(function () {
 			return 0;
-		}, array_flip(range(-self::ENTRY_COUNT_PERIOD, -1)));
+		}, array_flip(range($min, $max)));
 	}
 
 	/**
@@ -205,4 +279,57 @@ SQL;
 		return json_encode($serie);
 	}
 
+	/**
+	 * Gets days ready for graphs
+	 *
+	 * @return string
+	 */
+	public function getDays() {
+		return $this->convertToTranslatedJson(array(
+			'sun',
+			'mon',
+			'tue',
+			'wed',
+			'thu',
+			'fri',
+			'sat',
+		));
+	}
+
+	/**
+	 * Gets months ready for graphs
+	 *
+	 * @return string
+	 */
+	public function getMonths() {
+		return $this->convertToTranslatedJson(array(
+			'jan',
+			'feb',
+			'mar',
+			'apr',
+			'may',
+			'jun',
+			'jul',
+			'aug',
+			'sep',
+			'oct',
+			'nov',
+			'dec',
+		));
+	}
+
+	/**
+	 * Translates array content and encode it as JSON
+	 *
+	 * @param array $data
+	 * @return string
+	 */
+	private function convertToTranslatedJson($data = array()) {
+		$translated = array_map(function ($a) {
+			return Minz_Translate::t($a);
+		}, $data);
+
+		return json_encode($translated);
+	}
+
 }

+ 27 - 1
app/Models/StatsDAOSQLite.php

@@ -28,10 +28,36 @@ SQL;
 		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
 
 		foreach ($res as $value) {
-			$count[(int)$value['day']] = (int) $value['count'];
+			$count[(int) $value['day']] = (int) $value['count'];
 		}
 
 		return $this->convertToSerie($count);
 	}
 
+	protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
+		if ($feed) {
+			$restrict = "WHERE e.id_feed = {$feed}";
+		} else {
+			$restrict = '';
+		}
+		$sql = <<<SQL
+SELECT strftime('{$period}', e.date, 'unixepoch') AS period
+, COUNT(1) AS count
+FROM {$this->prefix}entry AS e
+{$restrict}
+GROUP BY period
+ORDER BY period ASC
+SQL;
+
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_NAMED);
+
+		foreach ($res as $value) {
+			$repartition[(int) $value['period']] = (int) $value['count'];
+		}
+
+		return $this->convertToSerie($repartition);
+	}
+
 }

+ 35 - 12
app/i18n/en.php

@@ -48,6 +48,10 @@ return array (
 	'stats'				=> 'Statistics',
 	'stats_idle'			=> 'Idle feeds',
 	'stats_main'			=> 'Main statistics',
+	'stats_repartition'		=> 'Articles repartition',
+	'stats_entry_per_hour'		=> 'Per hour',
+	'stats_entry_per_day_of_week'	=> 'Per day of week',
+	'stats_entry_per_month'		=> 'Per month',
     
 	'last_week'			=> 'Last week',
 	'last_month'			=> 'Last month',
@@ -341,18 +345,37 @@ return array (
 	'confirm_action'		=> 'Are you sure you want to perform this action? It cannot be cancelled!',
 
 	// DATE
-	'january'			=> 'january',
-	'february'			=> 'february',
-	'march'				=> 'march',
-	'april'				=> 'april',
-	'may'				=> 'may',
-	'june'				=> 'june',
-	'july'				=> 'july',
-	'august'			=> 'august',
-	'september'			=> 'september',
-	'october'			=> 'october',
-	'november'			=> 'november',
-	'december'			=> 'december',
+	'january'			=> 'January',
+	'february'			=> 'February',
+	'march'				=> 'March',
+	'april'				=> 'April',
+	'may'				=> 'May',
+	'june'				=> 'June',
+	'july'				=> 'July',
+	'august'			=> 'August',
+	'september'			=> 'September',
+	'october'			=> 'October',
+	'november'			=> 'November',
+	'december'			=> 'December',
+	'january'			=> 'Jan',
+	'february'			=> 'Feb',
+	'march'				=> 'Mar',
+	'april'				=> 'Apr',
+	'may'				=> 'May',
+	'june'				=> 'Jun',
+	'july'				=> 'Jul',
+	'august'			=> 'Aug',
+	'september'			=> 'Sep',
+	'october'			=> 'Oct',
+	'november'			=> 'Nov',
+	'december'			=> 'Dec',
+	'sun'				=> 'Sun',
+	'mon'				=> 'Mon',
+	'tue'				=> 'Tue',
+	'wed'				=> 'Wed',
+	'thu'				=> 'Thu',
+	'fri'				=> 'Fri',
+	'sat'				=> 'Sat',
 	// special format for date() function
 	'Jan'				=> '\J\a\n\u\a\r\y',
 	'Feb'				=> '\F\e\b\r\u\a\r\y',

+ 23 - 0
app/i18n/fr.php

@@ -48,6 +48,10 @@ return array (
 	'stats'				=> 'Statistiques',
 	'stats_idle'			=> 'Flux inactifs',
 	'stats_main'			=> 'Statistiques principales',
+	'stats_repartition'		=> 'Répartition des articles',
+	'stats_entry_per_hour'		=> 'Par heure',
+	'stats_entry_per_day_of_week'	=> 'Par jour de la semaine',
+	'stats_entry_per_month'		=> 'Par mois',
 
 	'last_week'			=> 'La dernière semaine',
 	'last_month'			=> 'Le dernier mois',
@@ -353,6 +357,25 @@ return array (
 	'october'			=> 'octobre',
 	'november'			=> 'novembre',
 	'december'			=> 'décembre',
+	'jan'				=> 'jan.',
+	'feb'				=> 'fév.',
+	'mar'				=> 'mar.',
+	'apr'				=> 'avr.',
+	'may'				=> 'mai.',
+	'jun'				=> 'juin',
+	'jul'				=> 'jui.',
+	'aug'				=> 'août',
+	'sep'				=> 'sep.',
+	'oct'				=> 'oct.',
+	'nov'				=> 'nov.',
+	'dec'				=> 'déc.',
+	'sun'				=> 'dim.',
+	'mon'				=> 'lun.',
+	'tue'				=> 'mar.',
+	'wed'				=> 'mer.',
+	'thu'				=> 'jeu.',
+	'fri'				=> 'ven.',
+	'sat'				=> 'sam.',
 	// format spécial pour la fonction date()
 	'Jan'				=> '\j\a\n\v\i\e\r',
 	'Feb'				=> '\f\é\v\r\i\e\r',

+ 1 - 0
app/layout/aside_flux.phtml

@@ -77,6 +77,7 @@
 	<ul class="dropdown-menu">
 		<li class="dropdown-close"><a href="#close">❌</a></li>
 		<li class="item"><a href="<?php echo _url ('index', 'index', 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('filter'); ?></a></li>
+		<li class="item"><a href="<?php echo _url ('stats', 'repartition', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('stats'); ?></a></li>
 		<li class="item"><a target="_blank" href="http://example.net/"><?php echo Minz_Translate::t ('see_website'); ?></a></li>
 		<?php if ($this->loginOk) { ?>
 		<li class="separator"></li>

+ 3 - 0
app/layout/aside_stats.phtml

@@ -6,4 +6,7 @@
 	<li class="item<?php echo Minz_Request::actionName () == 'idle' ? ' active' : ''; ?>">
 		<a href="<?php echo _url ('stats', 'idle'); ?>"><?php echo Minz_Translate::t ('stats_idle'); ?></a>
 	</li>
+	<li class="item<?php echo Minz_Request::actionName () == 'repartition' ? ' active' : ''; ?>">
+		<a href="<?php echo _url ('stats', 'repartition'); ?>"><?php echo Minz_Translate::t ('stats_repartition'); ?></a>
+	</li>
 </ul>

+ 0 - 127
app/views/stats/main.phtml

@@ -1,127 +0,0 @@
-<?php $this->partial('aside_stats'); ?>
-
-<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_main'); ?></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 class="numeric"><?php echo formatNumber($this->repartition['main_stream']['total']); ?></td>
-					<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['total']); ?></td>
-				</tr>
-				<tr>
-					<th><?php echo Minz_Translate::t ('status_read'); ?></th>
-					<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['read']); ?></td>
-					<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['read']); ?></td>
-				</tr>
-				<tr>
-					<th><?php echo Minz_Translate::t ('status_unread'); ?></th>
-					<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['unread']); ?></td>
-					<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['unread']); ?></td>
-				</tr>
-				<tr>
-					<th><?php echo Minz_Translate::t ('status_favorites'); ?></th>
-					<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['favorite']); ?></td>
-					<td class="numeric"><?php echo formatNumber($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 class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_top_feed'); ?></h2>
-		<table>
-			<thead>
-				<tr>
-					<th><?php echo Minz_Translate::t ('feed'); ?></th>
-					<th><?php echo Minz_Translate::t ('category'); ?></th>
-					<th><?php echo Minz_Translate::t ('stats_entry_count'); ?></th>
-				</tr>
-			</thead>
-			<tbody>
-				<?php foreach ($this->topFeed as $feed): ?>
-					<tr>
-						<td><?php echo $feed['name']; ?></td>
-						<td><?php echo $feed['category']; ?></td>
-						<td class="numeric"><?php echo formatNumber($feed['count']); ?></td>
-					</tr>
-				<?php endforeach;?>
-			</tbody>
-		</table>
-	</div>
-</div>
-
-<script>
-"use strict";
-function initStats() {
-	if (!window.Flotr) {
-		if (window.console) {
-			console.log('FreshRSS waiting for Flotr…');
-		}
-		window.setTimeout(initStats, 50);
-		return;
-	}
-	// Entry per day
-	Flotr.draw(document.getElementById('statsEntryPerDay'),
-		[<?php echo $this->count ?>],
-		{
-			grid: {verticalLines: false},
-			bars: {horizontal: false, show: true},
-			xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0},
-			yaxis: {min: 0},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
-		});
-	// Feed per category
-	Flotr.draw(document.getElementById('statsFeedPerCategory'),
-		<?php echo $this->feedByCategory ?>,
-		{
-			grid: {verticalLines: false, horizontalLines: false},
-			pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
-			xaxis: {showLabels: false},
-			yaxis: {showLabels: false},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
-			legend: {container: document.getElementById('statsFeedPerCategoryLegend'), noColumns: 3}
-		});
-	// Entry per category
-	Flotr.draw(document.getElementById('statsEntryPerCategory'),
-		<?php echo $this->entryByCategory ?>,
-		{
-			grid: {verticalLines: false, horizontalLines: false},
-			pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
-			xaxis: {showLabels: false},
-			yaxis: {showLabels: false},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
-			legend: {container: document.getElementById('statsEntryPerCategoryLegend'), noColumns: 3}
-		});
-}
-initStats();
-</script>

+ 99 - 0
app/views/stats/repartition.phtml

@@ -0,0 +1,99 @@
+<?php $this->partial('aside_stats'); ?>
+
+<div class="post content">
+	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>
+	
+	<?php if ($this->feed) {?>
+		<h1>
+			<?php echo _t('stats_repartition'), " - "; ?>
+			<a href="<?php echo _url('configure', 'feed', 'id', $this->feed->id()); ?>">
+				<?php echo $this->feed->name(); ?>
+			</a>
+		</h1>
+	<?php } else {?>
+		<h1><?php echo _t('stats_repartition'); ?></h1>
+	<?php }?>
+	
+	<div class="stat">
+		<h2><?php echo _t('stats_entry_per_hour'); ?></h2>
+		<div id="statsEntryPerHour" style="height: 300px"></div>
+	</div>
+	
+	<div class="stat">
+		<h2><?php echo _t('stats_entry_per_day_of_week'); ?></h2>
+		<div id="statsEntryPerDayOfWeek" style="height: 300px"></div>
+	</div>
+
+	<div class="stat">
+		<h2><?php echo _t('stats_entry_per_month'); ?></h2>
+		<div id="statsEntryPerMonth" style="height: 300px"></div>
+	</div>
+</div>
+
+<script>
+"use strict";
+function initStats() {
+	if (!window.Flotr) {
+		if (window.console) {
+			console.log('FreshRSS waiting for Flotr…');
+		}
+		window.setTimeout(initStats, 50);
+		return;
+	}
+	// Entry per hour
+	Flotr.draw(document.getElementById('statsEntryPerHour'),
+		[<?php echo $this->repartitionHour ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 23,
+				tickFormatter: function(x) {
+					var x = parseInt(x);
+					return x + 1;
+				},
+				min: -0.9,
+				max: 23.9,
+				tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+		});
+	// Entry per day of week
+	Flotr.draw(document.getElementById('statsEntryPerDayOfWeek'),
+		[<?php echo $this->repartitionDayOfWeek ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 6,
+				tickFormatter: function(x) {
+					var x = parseInt(x),
+					    days = <?php echo $this->days?>;
+					return days[x];
+				},
+				min: -0.9,
+				max: 6.9,
+				tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+		});
+	// Entry per month
+	Flotr.draw(document.getElementById('statsEntryPerMonth'),
+		[<?php echo $this->repartitionMonth ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 12,
+				tickFormatter: function(x) {
+					var x = parseInt(x),
+					    months = <?php echo $this->months?>;
+					return months[(x - 1)];
+				},
+				min: 0.1,
+				max: 12.9,
+				tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+		});
+	
+}
+initStats();
+</script>