Analytics: add the first graph (task repartition)
This commit is contained in:
parent
3df63e051f
commit
e89ba5e9e6
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
/**
|
||||
* Project Anaytic controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Analytic extends Base
|
||||
{
|
||||
/**
|
||||
* Common layout for analytic views
|
||||
*
|
||||
* @access private
|
||||
* @param string $template Template name
|
||||
* @param array $params Template parameters
|
||||
* @return string
|
||||
*/
|
||||
private function layout($template, array $params)
|
||||
{
|
||||
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
|
||||
$params['analytic_content_for_layout'] = $this->template->load($template, $params);
|
||||
|
||||
return $this->template->layout('analytic/layout', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show task distribution graph
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function repartition()
|
||||
{
|
||||
$project = $this->getProject();
|
||||
$metrics = $this->projectAnalytic->getTaskRepartition($project['id']);
|
||||
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->json(array(
|
||||
'metrics' => $metrics,
|
||||
'labels' => array(
|
||||
'column_title' => t('Column'),
|
||||
'nb_tasks' => t('Number of tasks'),
|
||||
)
|
||||
));
|
||||
}
|
||||
else {
|
||||
$this->response->html($this->layout('analytic/repartition', array(
|
||||
'project' => $project,
|
||||
'metrics' => $metrics,
|
||||
'title' => t('Task repartition for "%s"', $project['name']),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ use Model\LastLogin;
|
|||
* @property \Model\Notification $notification
|
||||
* @property \Model\Project $project
|
||||
* @property \Model\ProjectPermission $projectPermission
|
||||
* @property \Model\ProjectAnalytic $projectAnalytic
|
||||
* @property \Model\SubTask $subTask
|
||||
* @property \Model\Task $task
|
||||
* @property \Model\TaskHistory $taskHistory
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
'Columns' => 'Colonnes',
|
||||
'Task' => 'Tâche',
|
||||
'Your are not member of any project.' => 'Vous n\'êtes membre d\'aucun projet.',
|
||||
'Percentage' => 'Pourcentage',
|
||||
'Number of tasks' => 'Nombre de tâches',
|
||||
'Task distribution' => 'Répartition des tâches',
|
||||
'Reportings' => 'Rapports',
|
||||
'Task repartition for "%s"' => 'Répartition des tâches pour « %s »',
|
||||
'Analytics' => 'Analytique',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -565,4 +565,10 @@ return array(
|
|||
// 'Columns' => '',
|
||||
// 'Task' => '',
|
||||
// 'Your are not member of any project.' => '',
|
||||
// 'Percentage' => '',
|
||||
// 'Number of tasks' => '',
|
||||
// 'Task distribution' => '',
|
||||
// 'Reportings' => '',
|
||||
// 'Task repartition for "%s"' => '',
|
||||
// 'Analytics' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class Acl extends Base
|
|||
'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'open', 'duplicate', 'remove', 'description', 'move', 'copy', 'time'),
|
||||
'category' => array('index', 'save', 'edit', 'update', 'confirm', 'remove'),
|
||||
'action' => array('index', 'event', 'params', 'create', 'confirm', 'remove'),
|
||||
'analytic' => array('repartition'),
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
/**
|
||||
* Project analytic model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class ProjectAnalytic extends Base
|
||||
{
|
||||
/**
|
||||
* Get task repartition
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @return array
|
||||
*/
|
||||
public function getTaskRepartition($project_id)
|
||||
{
|
||||
$metrics = array();
|
||||
$total = 0;
|
||||
$columns = $this->board->getColumns($project_id);
|
||||
|
||||
foreach ($columns as $column) {
|
||||
|
||||
$nb_tasks = $this->taskFinder->countByColumnId($project_id, $column['id']);
|
||||
$total += $nb_tasks;
|
||||
|
||||
$metrics[] = array(
|
||||
'column_title' => $column['title'],
|
||||
'nb_tasks' => $nb_tasks,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($metrics as &$metric) {
|
||||
$metric['percentage'] = round(($metric['nb_tasks'] * 100) / $total, 2);
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?= Helper\js('assets/js/d3.v3.4.8.min.js') ?>
|
||||
<?= Helper\js('assets/js/dimple.v2.1.0.min.js') ?>
|
||||
|
||||
<section id="main">
|
||||
<div class="page-header">
|
||||
<ul>
|
||||
<li><i class="fa fa-table fa-fw"></i><?= Helper\a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section class="sidebar-container" id="analytic-section">
|
||||
|
||||
<?= Helper\template('analytic/sidebar', array('project' => $project)) ?>
|
||||
|
||||
<div class="sidebar-content">
|
||||
<?= $analytic_content_for_layout ?>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Task distribution') ?></h2>
|
||||
</div>
|
||||
<section id="analytic-repartition">
|
||||
|
||||
<div id="chart" data-url="<?= Helper\u('analytic', 'repartition', array('project_id' => $project['id'])) ?>"></div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th><?= t('Column') ?></th>
|
||||
<th><?= t('Number of tasks') ?></th>
|
||||
<th><?= t('Percentage') ?></th>
|
||||
</tr>
|
||||
<?php foreach ($metrics as $metric): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= Helper\escape($metric['column_title']) ?>
|
||||
</td>
|
||||
<td>
|
||||
<?= $metric['nb_tasks'] ?>
|
||||
</td>
|
||||
<td>
|
||||
<?= n($metric['percentage']) ?>%
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
|
||||
</section>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<div class="sidebar">
|
||||
<h2><?= t('Reportings') ?></h2>
|
||||
<ul>
|
||||
<li>
|
||||
<?= Helper\a(t('Task distribution'), 'analytic', 'repartition', array('project_id' => $project['id'])) ?>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -1,8 +1,4 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Page not found') ?></h2>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-error">
|
||||
<?= t('Sorry, I didn\'t found this information in my database!') ?>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@
|
|||
<i class="fa fa-dashboard fa-fw"></i>
|
||||
<?= Helper\a(t('Activity'), 'project', 'activity', array('project_id' => $current_project_id)) ?>
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa fa-line-chart fa-fw"></i>
|
||||
<?= Helper\a(t('Analytics'), 'analytic', 'repartition', array('project_id' => $current_project_id)) ?>
|
||||
</li>
|
||||
<?php if (Helper\is_admin()): ?>
|
||||
<li><i class="fa fa-cog fa-fw"></i>
|
||||
<?= Helper\a(t('Configure'), 'project', 'show', array('project_id' => $current_project_id)) ?>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
Kanboard.Analytic = (function() {
|
||||
|
||||
return {
|
||||
Init: function() {
|
||||
|
||||
if (Kanboard.Exists("analytic-repartition")) {
|
||||
Kanboard.Analytic.Repartition.Init();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
Kanboard.Analytic.Repartition = (function() {
|
||||
|
||||
function fetchData()
|
||||
{
|
||||
jQuery.getJSON($("#chart").attr("data-url"), function(data) {
|
||||
drawGraph(data.metrics, data.labels);
|
||||
});
|
||||
}
|
||||
|
||||
function drawGraph(metrics, labels)
|
||||
{
|
||||
var series = prepareSeries(metrics, labels);
|
||||
|
||||
var svg = dimple.newSvg("#chart", 700, 350);
|
||||
|
||||
var chart = new dimple.chart(svg, series);
|
||||
chart.addMeasureAxis("p", labels["nb_tasks"]);
|
||||
var ring = chart.addSeries(labels["column_title"], dimple.plot.pie);
|
||||
ring.innerRadius = "50%";
|
||||
chart.addLegend(0, 0, 100, 100, "left");
|
||||
chart.draw();
|
||||
}
|
||||
|
||||
function prepareSeries(metrics, labels)
|
||||
{
|
||||
var series = [];
|
||||
|
||||
for (var i = 0; i < metrics.length; i++) {
|
||||
|
||||
var serie = {};
|
||||
serie[labels["nb_tasks"]] = metrics[i]["nb_tasks"];
|
||||
serie[labels["column_title"]] = metrics[i]["column_title"];
|
||||
|
||||
series.push(serie);
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
return {
|
||||
Init: fetchData
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -18,6 +18,15 @@ var Kanboard = (function() {
|
|||
|
||||
return {
|
||||
|
||||
// Return true if the element#id exists
|
||||
Exists: function(id) {
|
||||
if (document.getElementById(id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Display a popup
|
||||
Popover: function(e, callback) {
|
||||
e.preventDefault();
|
||||
|
|
@ -264,15 +273,76 @@ Kanboard.Task = (function() {
|
|||
}
|
||||
};
|
||||
|
||||
})();// Initialization
|
||||
})();
|
||||
Kanboard.Analytic = (function() {
|
||||
|
||||
return {
|
||||
Init: function() {
|
||||
|
||||
if (Kanboard.Exists("analytic-repartition")) {
|
||||
Kanboard.Analytic.Repartition.Init();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
Kanboard.Analytic.Repartition = (function() {
|
||||
|
||||
function fetchData()
|
||||
{
|
||||
jQuery.getJSON($("#chart").attr("data-url"), function(data) {
|
||||
drawGraph(data.metrics, data.labels);
|
||||
});
|
||||
}
|
||||
|
||||
function drawGraph(metrics, labels)
|
||||
{
|
||||
var series = prepareSeries(metrics, labels);
|
||||
|
||||
var svg = dimple.newSvg("#chart", 700, 350);
|
||||
|
||||
var chart = new dimple.chart(svg, series);
|
||||
chart.addMeasureAxis("p", labels["nb_tasks"]);
|
||||
var ring = chart.addSeries(labels["column_title"], dimple.plot.pie);
|
||||
ring.innerRadius = "50%";
|
||||
chart.addLegend(0, 0, 100, 100, "left");
|
||||
chart.draw();
|
||||
}
|
||||
|
||||
function prepareSeries(metrics, labels)
|
||||
{
|
||||
var series = [];
|
||||
|
||||
for (var i = 0; i < metrics.length; i++) {
|
||||
|
||||
var serie = {};
|
||||
serie[labels["nb_tasks"]] = metrics[i]["nb_tasks"];
|
||||
serie[labels["column_title"]] = metrics[i]["column_title"];
|
||||
|
||||
series.push(serie);
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
return {
|
||||
Init: fetchData
|
||||
};
|
||||
|
||||
})();
|
||||
// Initialization
|
||||
$(function() {
|
||||
|
||||
Kanboard.Init();
|
||||
|
||||
if ($("#board").length) {
|
||||
if (Kanboard.Exists("board")) {
|
||||
Kanboard.Board.Init();
|
||||
}
|
||||
else if ($("#task-section").length) {
|
||||
else if (Kanboard.Exists("task-section")) {
|
||||
Kanboard.Task.Init();
|
||||
}
|
||||
else if (Kanboard.Exists("analytic-section")) {
|
||||
Kanboard.Analytic.Init();
|
||||
}
|
||||
});
|
||||
|
|
@ -3,6 +3,15 @@ var Kanboard = (function() {
|
|||
|
||||
return {
|
||||
|
||||
// Return true if the element#id exists
|
||||
Exists: function(id) {
|
||||
if (document.getElementById(id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Display a popup
|
||||
Popover: function(e, callback) {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -3,10 +3,13 @@ $(function() {
|
|||
|
||||
Kanboard.Init();
|
||||
|
||||
if ($("#board").length) {
|
||||
if (Kanboard.Exists("board")) {
|
||||
Kanboard.Board.Init();
|
||||
}
|
||||
else if ($("#task-section").length) {
|
||||
else if (Kanboard.Exists("task-section")) {
|
||||
Kanboard.Task.Init();
|
||||
}
|
||||
else if (Kanboard.Exists("analytic-section")) {
|
||||
Kanboard.Analytic.Init();
|
||||
}
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
css="base links title table form button alert header board project task comment subtask markdown listing activity dashboard pagination popover confirm sidebar responsive font-awesome.min jquery-ui-1.10.4.custom chosen.min"
|
||||
js="jquery-1.11.1.min jquery-ui-1.10.4.custom.min jquery.ui.touch-punch.min chosen.jquery.min base board task init"
|
||||
js="jquery-1.11.1.min jquery-ui-1.10.4.custom.min jquery.ui.touch-punch.min chosen.jquery.min base board task analytic init"
|
||||
|
||||
rm -f assets/css/app.css
|
||||
echo "/* DO NOT EDIT: auto-generated file */" > assets/css/app.css
|
||||
|
|
|
|||
Loading…
Reference in New Issue