[Fusionforge-commits] FusionForge branch master updated. 1188e98 JS code is polished

Franck Villaume nerville at libremir.placard.fr.eu.org
Sun Jun 7 20:37:35 CEST 2015


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "FusionForge".

The branch, master has been updated
       via  1188e98adadc839690bcf9d5876ff055160c7529 (commit)
       via  8c661763217d24cea406aa21de4ee1e7b8ad2b84 (commit)
       via  1830188100c4593800b271b87b0a9a1c01705286 (commit)
       via  fd53dbbe12f14cbbc3b46304923ed34ab242824b (commit)
       via  b16cafc79d7c2ca2fd0de39b3ab8dc9ccc54a6d2 (commit)
       via  283838b1ba70566e7ed4ce36215c7dbb9f061c95 (commit)
       via  a0a3bf0af69a78dc172bef6186ba9baa29119f2d (commit)
      from  9cb2fb1b401deec39d3db5679017b2d862fadb3a (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 1188e98adadc839690bcf9d5876ff055160c7529
Author: Vitaliy Pylypiv <pylypiv at integramedia.eu>
Date:   Sun May 31 13:12:40 2015 +0300

    JS code is polished

diff --git a/src/plugins/taskboard/common/TaskBoardColumnSource.class.php b/src/plugins/taskboard/common/TaskBoardColumnSource.class.php
index 30ee859..4686189 100644
--- a/src/plugins/taskboard/common/TaskBoardColumnSource.class.php
+++ b/src/plugins/taskboard/common/TaskBoardColumnSource.class.php
@@ -256,9 +256,6 @@ class TaskBoardColumnSource extends Error {
 			$target_column = taskboard_column_get_object( $this->getTargetColumnID() );
 			
 			$columns = $this->getTaskboard()->getColumns();
-			
-			error_log( count($columns) . ' ' .  $target_column->getOrder() );
-			
 			if( $target_column->getOrder() == count($columns) ) {
 				// final column, so set remaining cost to 0 
 				$remaining_cost = 0;
diff --git a/src/plugins/taskboard/common/views/releases/burndown.php b/src/plugins/taskboard/common/views/releases/burndown.php
index 170bf8c..3e241c6 100644
--- a/src/plugins/taskboard/common/views/releases/burndown.php
+++ b/src/plugins/taskboard/common/views/releases/burndown.php
@@ -108,54 +108,54 @@ foreach( $release_snapshots as $snapshot ) {
 			{
 				axesDefaults: {
 					pad : 1
-				},	
+				},
 				seriesColors: [ '#000', '#DDDDDD', '#00FA9A', '#B22222' ],
 				legend: {
-			        show: true,
-			        location: 'ne', 
-			        xoffset: 12,
-			        yoffset: 12
-			    },
+					show: true,
+					location: 'ne', 
+					xoffset: 12,
+					yoffset: 12
+				},
 				axes : { 
-					xaxis : {	
+					xaxis : {
 						renderer : jQuery.jqplot.DateAxisRenderer, 
 						tickRenderer: jQuery.jqplot.CanvasAxisTickRenderer,
 						tickOptions:{ 
-				            angle: -90,
-				            fontSize : '1.3em',
-				            formatString : '%Y-%m-%d'
-				        },
-				        numberTicks: <?php echo count($xaxisData) - 2; ?>,	
-				        min: <?php echo $release->getStartDate() * 1000; ?>,
-				        max: <?php echo $release->getEndDate() * 1000; ?>	       
+							angle: -90,
+							fontSize : '1.3em',
+							formatString : '%Y-%m-%d'
+						},
+						numberTicks: <?php echo count($xaxisData) - 2; ?>,
+						min: <?php echo $release->getStartDate() * 1000; ?>,
+						max: <?php echo $release->getEndDate() * 1000; ?>
 					},
 					yaxis : {
 						autoscale:true,
 						min : 0,
-					    label: "<?php echo _('Completed tasks') ?>" ,
-				        labelRenderer: jQuery.jqplot.CanvasAxisLabelRenderer,
-				        labelOptions:{
-				       		fontSize : '12px'
-				        }
+						label: "<?php echo _('Completed tasks') ?>" ,
+						labelRenderer: jQuery.jqplot.CanvasAxisLabelRenderer,
+							labelOptions:{
+							fontSize : '12px'
+						}
 					},
-				    y2axis: {
-		                autoscale:true,	
-		                min : 0,  
-		                tickOptions:{ 
-		                	isMinorTick: true,
-		                	formatString: "%.1f <?php echo _('m/d') ?>"
-				        } 
-		            }
+					y2axis: {
+						autoscale:true,
+						min : 0,
+						tickOptions:{ 
+						isMinorTick: true,
+						formatString: "%.1f <?php echo _('m/d') ?>"
+						}
+					}
 				},
-				series:[ 
-					{ show : false }, // to indicates all dates only	
+				series:[
+					{ show : false }, // to indicate all dates
 					{ label : "<?php echo _('Ideal burndown') ;?>", lineWidth:1, markerOptions : { style : 'circle', size : 5 } },
 					{ label : "<?php echo _('Remaining tasks') ;?>", lineWidth:1, markerOptions : { style : 'circle', size : 5 },  yaxis: 'yaxis' },
 					{ label : "<?php echo _('Remaining efforts') ;?>", lineWidth:1, markerOptions : { style : 'circle', size : 5 } , yaxis:'y2axis' }
 				],
 				highlighter: {
-		        	show: true,
-		        	sizeAdjust: 8
+					show: true,
+					sizeAdjust: 8
 				},
 				cursor: {
 					show: false
diff --git a/src/plugins/taskboard/www/js/agile-board.js b/src/plugins/taskboard/www/js/agile-board.js
index dcaf08c..d55b7fc 100644
--- a/src/plugins/taskboard/www/js/agile-board.js
+++ b/src/plugins/taskboard/www/js/agile-board.js
@@ -185,8 +185,8 @@ function drawBoardProgress() {
 		// show progress by cost
 		html += '<table>';
 		html += '<tr><td width="' + parseInt( 100 / aPhases.length )  + '%" style="padding: 0;">' + gMessages.progressByCost + ':</td><td style="padding: 0;">';
+		html += '<div class="agile-board-progress-bar-done" style="width: ' + wt + '%;" title="' + gMessages['completedCost'] + '">' + totalCostCompleted + '</div>';
 		html += '<div class="agile-board-progress-bar-remains" style="width: ' + ( 100 - wt ) + '%;" title="' + gMessages['remainingCost'] + '">' + totalCostRemaining + '</div>';
-		html += '<div class="agile-board-progress-bar-done" style="width: ' + wt + '%;" title="' + gMessages['completedCost'] + '">' + totalCostCompleted + '</div>';		
 		html += '</td></tr><table>';
 	}
 	
@@ -482,7 +482,7 @@ function initEditable() {
 				}
 			});
 		}
-	});	
+	});
 	
 }
 

commit 8c661763217d24cea406aa21de4ee1e7b8ad2b84
Author: Vitaliy Pylypiv <pylypiv at integramedia.eu>
Date:   Sun May 31 10:00:36 2015 +0300

    Remaining cost is set to 0 when card is dropped on the last column

diff --git a/src/plugins/taskboard/TODO.txt b/src/plugins/taskboard/TODO.txt
index 9a6048e..7a26292 100644
--- a/src/plugins/taskboard/TODO.txt
+++ b/src/plugins/taskboard/TODO.txt
@@ -10,14 +10,14 @@
 - DONE View - filter by assigned persons
 - DONE Releases are based either on US or on tasks
 - DONE View - indicators - progress by tasks number
-- API - configured fields support in mapped tasks ('estimated_dev_effort', 'remaining_dev_effort', 'user_story' )
-- View - indicators - progress by tasks cost (if efforts fields are configured)
+- DONE API - configured fields support in mapped tasks ('estimated_dev_effort', 'remaining_dev_effort', 'user_story' )
+- DONE View - indicators - progress by tasks cost (if efforts fields are configured)
 - DONE Admin - releases management
 - View - auto ref rate support
 - Admin - auto ref rate support
 - Admin - adapters - coloring support
 - View - adapters - coloring support (for GT)
 - View - colors by extrafield (for GT)
-- View - indicators - burndown chart
+- DONE View - indicators - burndown chart
 - View - indicators - velocity calculation
 - Improve taskboard initialization - columns by default? 
diff --git a/src/plugins/taskboard/common/TaskBoardColumnSource.class.php b/src/plugins/taskboard/common/TaskBoardColumnSource.class.php
index feae4ba..30ee859 100644
--- a/src/plugins/taskboard/common/TaskBoardColumnSource.class.php
+++ b/src/plugins/taskboard/common/TaskBoardColumnSource.class.php
@@ -248,8 +248,24 @@ class TaskBoardColumnSource extends Error {
 		if( $this->getAutoassign() ) {
 			$assigned_to = user_getid();
 		}
+		
 
-		$msg = $this->getTaskboard()->TrackersAdapter->updateTask( $task,$assigned_to, $this->getTargetResolution() );
+		$remaining_cost = NULL;
+		if( $this->getTaskboard()->getRemainingCostField() ) {
+			// set 0 to remainin cost if target column is a last one
+			$target_column = taskboard_column_get_object( $this->getTargetColumnID() );
+			
+			$columns = $this->getTaskboard()->getColumns();
+			
+			error_log( count($columns) . ' ' .  $target_column->getOrder() );
+			
+			if( $target_column->getOrder() == count($columns) ) {
+				// final column, so set remaining cost to 0 
+				$remaining_cost = 0;
+			}
+		}		
+
+		$msg = $this->getTaskboard()->TrackersAdapter->updateTask( $task, $assigned_to, $this->getTargetResolution(), NULL, NULL, $remaining_cost );
 		if($msg) {
 			$msg = _('Tracker error')._(': ').$msg;
 		}
diff --git a/src/plugins/taskboard/common/adapters/TaskBoardBasicAdapter.class.php b/src/plugins/taskboard/common/adapters/TaskBoardBasicAdapter.class.php
index f6283c9..17a723f 100644
--- a/src/plugins/taskboard/common/adapters/TaskBoardBasicAdapter.class.php
+++ b/src/plugins/taskboard/common/adapters/TaskBoardBasicAdapter.class.php
@@ -322,7 +322,7 @@ class TaskBoardBasicAdapter {
 	 *
 	 * @return   string     error message in case of fail
 	 */
-	function updateTask(&$artifact, $assigned_to, $resolution, $title = NULL, $description = NULL) {
+	function updateTask(&$artifact, $assigned_to, $resolution, $title = NULL, $description = NULL, $remaining_cost=NULL ) {
 		if (!$assigned_to) {
 			$assigned_to = $artifact->getAssignedTo();
 		}
@@ -341,6 +341,17 @@ class TaskBoardBasicAdapter {
 			}
 		}
 
+		if($remaining_cost!==NULL) {
+			$remaining_cost_alias = $this->TaskBoard->getRemainingCostField();
+
+			if($remaining_cost_alias) {
+				if (array_key_exists($remaining_cost_alias, $fields_ids)){
+					$remaining_cost_field_id = $fields_ids[$remaining_cost_alias];
+					$extra_fields[ $remaining_cost_field_id ] = $remaining_cost;
+				}
+			}
+		} 
+
 		if (!$title) {
 			$title = htmlspecialchars_decode($artifact->getSummary());
 		}

commit 1830188100c4593800b271b87b0a9a1c01705286
Author: Vitaliy Pylypiv <pylypiv at integramedia.eu>
Date:   Sun May 31 08:59:42 2015 +0300

    Fix for snapshot saved completed_man_days calculation

diff --git a/src/plugins/taskboard/common/TaskBoardRelease.class.php b/src/plugins/taskboard/common/TaskBoardRelease.class.php
index cdeafa9..03d65de 100644
--- a/src/plugins/taskboard/common/TaskBoardRelease.class.php
+++ b/src/plugins/taskboard/common/TaskBoardRelease.class.php
@@ -275,15 +275,17 @@ class TaskBoardRelease extends Error {
 					if( $tsk['phase_id'] == $columns[$i]->getID() ) {
 						if( $i + 1 == $_columns_num ) {
 							// last column, so - completed task
-							$ret['completed_tasks']++;
-							if( $tsk['estimated_dev_effort'] ) {
-								$ret['completed_man_days'] += ( (float) $tsk['estimated_dev_effort'] - (float) $tsk['remaining_dev_effort'] );
-							}
+							$ret['completed_tasks']++;							
 						} else {
 							// incomplete task, so incomplete US
 							$completed_us = false;
 						}
 						$ret['tasks']++;
+						
+						if( $tsk['estimated_dev_effort'] ) {
+							$ret['completed_man_days'] += ( (float) $tsk['estimated_dev_effort'] - (float) $tsk['remaining_dev_effort'] );
+						}
+						
 						if( $tsk['estimated_dev_effort'] ) {
 							$ret['man_days'] += (float)  $tsk['estimated_dev_effort'];
 						}

commit fd53dbbe12f14cbbc3b46304923ed34ab242824b
Author: Vitaliy Pylypiv <pylypiv at integramedia.eu>
Date:   Sun May 31 08:49:28 2015 +0300

    taskboard: cherry-pick ac3f849a1611a3eb8ce10c5cc8a91a5a18ecb70c

diff --git a/src/plugins/taskboard/common/TaskBoardRelease.class.php b/src/plugins/taskboard/common/TaskBoardRelease.class.php
index a368859..cdeafa9 100644
--- a/src/plugins/taskboard/common/TaskBoardRelease.class.php
+++ b/src/plugins/taskboard/common/TaskBoardRelease.class.php
@@ -247,22 +247,26 @@ class TaskBoardRelease extends Error {
 	}
 
 	/**
-	 * Save current taskboard snapshot. So, we can have a history of release implementation,
-	 * that could be used for different indicators calculation.
+	 *  Get release volume - number of total
 	 *
-	 * @param	int	Snapshot unix date time
-	 * @return	boolean	success.
+	 *  @return array  hash with user_stories, tasks, story_points, man_days key
 	 */
-	function saveSnapshot($snapshot_datetime) {
-		$user_stories = $this->Taskboard->getUserStories($this->getTitle());		
+	function getVolume() {
+		$user_stories = $this->Taskboard->getUserStories($this->getTitle());
 		$columns = $this->Taskboard->getColumns($this->getTitle());
 
 		$_columns_num = count($columns);
-		$_completed_user_stories = 0;
- 		$_completed_tasks = 0;
-		$_completed_story_points = 0;
-		$_completed_man_days = 0;
-		
+		$ret = array(
+			'user_stories' => 0,
+			'completed_user_stories' => 0,
+			'tasks' => 0,
+			'completed_tasks' => 0,
+			'story_points' => 0,
+			'completed_story_points' => 0,
+			'man_days'=> 0,
+			'completed_man_days'=> 0,
+		);
+
 		foreach( $user_stories as $us ) {
 			$completed_us = true;
 
@@ -270,26 +274,45 @@ class TaskBoardRelease extends Error {
 				foreach( $us['tasks'] as $tsk ) {
 					if( $tsk['phase_id'] == $columns[$i]->getID() ) {
 						if( $i + 1 == $_columns_num ) {
-							// last column, so- completed task
-							$_completed_tasks++;
+							// last column, so - completed task
+							$ret['completed_tasks']++;
+							if( $tsk['estimated_dev_effort'] ) {
+								$ret['completed_man_days'] += ( (float) $tsk['estimated_dev_effort'] - (float) $tsk['remaining_dev_effort'] );
+							}
 						} else {
 							// incomplete task, so incomplete US
 							$completed_us = false;
 						}
+						$ret['tasks']++;
+						if( $tsk['estimated_dev_effort'] ) {
+							$ret['man_days'] += (float)  $tsk['estimated_dev_effort'];
+						}
 					}
 				}
-				
 			}
-			
+
 			if( $completed_us ) {
-				$_completed_user_stories++;
+				$ret['completed_user_stories']++;
 				// TODO $_completed_story_points += ...
 			}
+			$ret['user_stories']++;
 		}
-		
-		
+
+		return $ret;
+	}
+
+	/**
+	 * Save current taskboard snapshot. So, we can have a history of release implementation,
+	 * that could be used for different indicators calculation.
+	 *
+	 * @param	int	Snapshot unix date time
+	 * @return	boolean	success.
+	 */
+	function saveSnapshot($snapshot_datetime) {
+		$release_volume = $this->getVolume();
+
 		$res = db_query_params(
-				'SELECT taskboard_release_snapshot_id  FROM plugin_taskboard_releases_snapshots WHERE taskboard_release_id=$1 AND snapshot_date=$2', 
+				'SELECT taskboard_release_snapshot_id  FROM plugin_taskboard_releases_snapshots WHERE taskboard_release_id=$1 AND snapshot_date=$2',
 				array ($this->getID(), $snapshot_datetime )
 		);
 
@@ -297,20 +320,20 @@ class TaskBoardRelease extends Error {
 			$this->setError('TaskBoardRelease: Cannot get release snapshot');
 			return false;
 		}
-		
+
 		$row = db_fetch_array($res);
 		db_free_result($res);
-		
+
 		if( $row ) {
 			$res = db_query_params(
-					'UPDATE plugin_taskboard_releases_snapshots 
+					'UPDATE plugin_taskboard_releases_snapshots
 					SET completed_user_stories=$1, completed_tasks=$2, completed_story_points=$3, completed_man_days=$4
 					WHERE taskboard_release_snapshot_id=$5',
 					array(
-							$_completed_user_stories,
-							$_completed_tasks,
-							0, //TODO
-							0, //TODO
+							$release_volume['completed_user_stories'],
+							$release_volume['completed_tasks'],
+							$release_volume['completed_story_points'],
+							$release_volume['completed_man_days'],
 							intval($row['taskboard_release_snapshot_id'])
 					)
 			);
@@ -325,10 +348,10 @@ class TaskBoardRelease extends Error {
 					array(
 							$this->getID(),
 							$snapshot_datetime,
-							$_completed_user_stories,
-							$_completed_tasks,
-							0, //TODO
-							0 //TODO
+							$release_volume['completed_user_stories'],
+							$release_volume['completed_tasks'],
+							$release_volume['completed_story_points'],
+							$release_volume['completed_man_days'],
 					)
 			);
 			if (!$res) {
@@ -339,4 +362,31 @@ class TaskBoardRelease extends Error {
 
 		return true;
 	}
+
+	/**
+	 * Get current release snapshots
+	 *
+	 * @return	array
+	 */
+	function getSnapshots() {
+		$ret = array();
+
+		$res = db_query_params(
+				'SELECT  snapshot_date, completed_user_stories, completed_tasks, completed_story_points, completed_man_days
+				FROM plugin_taskboard_releases_snapshots WHERE taskboard_release_id=$1 ORDER BY snapshot_date',
+				array ($this->getID() )
+		);
+
+		if (!$res) {
+			$this->setError('TaskBoardRelease: Cannot get release snapshots');
+			return false;
+		}
+
+		while( $row = db_fetch_array($res) ) {
+			$ret[] = $row;
+		}
+		db_free_result($res);
+
+		return $ret;
+	}
 }
diff --git a/src/plugins/taskboard/common/views/admin/edit_column.php b/src/plugins/taskboard/common/views/admin/edit_column.php
index 480dfa0..2f89bb2 100644
--- a/src/plugins/taskboard/common/views/admin/edit_column.php
+++ b/src/plugins/taskboard/common/views/admin/edit_column.php
@@ -94,7 +94,6 @@ if ($column_id) {
 		echo $HTML->multiTableRow(array(), $cells);
 		$cells = array();
 		$cells[][] = html_e('strong', array(), _('Alert message'));
-		error_log($drop_rules_by_default->getAlertText());
 		$cells[][] = html_e('textarea', array('name' => 'alert', 'cols' => 79, 'rows' => 5), htmlspecialchars($drop_rules_by_default->getAlertText()), false);
 		echo $HTML->multiTableRow(array(), $cells);
 		echo $HTML->listTableBottom();
diff --git a/src/plugins/taskboard/common/views/releases/burndown.php b/src/plugins/taskboard/common/views/releases/burndown.php
new file mode 100644
index 0000000..170bf8c
--- /dev/null
+++ b/src/plugins/taskboard/common/views/releases/burndown.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Copyright (C) 2015 Vitaliy Pylypiv <vitaliy.pylypiv at gmail.com>
+ *
+ * This file is part of FusionForge.
+ *
+ * FusionForge is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * FusionForge is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+global $group_id, $group, $taskboard;
+
+$release_id = getIntFromRequest('release_id', NULL);
+
+$release = new TaskBoardRelease( $taskboard, $release_id );
+
+html_use_jqueryjqplotpluginCanvas();
+html_use_jqueryjqplotplugindateAxisRenderer();
+html_use_jqueryjqplotpluginhighlighter();
+
+$taskboard->header(
+		array(
+			'title' => _('Taskboard for ').$group->getPublicName()._(': '). _('Releases')._(': ')._('Burndown chart')._(': ').$release->getTitle() ,
+			'pagename' => _('Releases')._(': ')._('Burndown chart')._(': ').$release->getTitle(),
+			'sectionvals' => array($group->getPublicName()),
+			'group' => $group_id
+		)
+	);
+
+if ($taskboard->isError()) {
+	echo $HTML->error_msg($taskboard->getErrorMessage());
+} else {
+	echo html_e('div', array('id' => 'messages', 'style' => 'display: none;'), '', false);
+}
+
+// $xaxisData is used to have an every date on the X axis
+$xaxisData = array();
+$chartDate = $release->getStartDate();
+while( $chartDate <= $release->getEndDate() ) {
+	$xaxisData[] = array(  date( 'r', $chartDate) , 0);
+	$chartDate += 86400;
+}
+
+$release_volume = $release->getVolume();
+
+// ideal burndown
+$dataIdeal = array(
+	array( $release->getStartDate() * 1000,  $release_volume['tasks']),
+	array( $release->getEndDate() * 1000, 0)
+);
+
+$release_snapshots = $release->getSnapshots();
+$dataRemainingTasks = array();
+$dataRemainingEfforts = array();
+
+foreach( $release_snapshots as $snapshot ) {
+	if( count($dataRemainingTasks) == 0 && $snapshot['snapshot_date'] != $release->getStartDate() ) {
+		// initialize start point if snapshot is missing for the first day
+		$dataRemainingTasks[] = array( $release->getStartDate() * 1000, $release_volume['tasks'] );
+		$dataRemainingEfforts[] = array( $release->getStartDate() * 1000, $release_volume['man_days'] );
+	}
+	
+	$dataRemainingTasks[] = array( $snapshot['snapshot_date'] * 1000, ( $release_volume['tasks'] - $snapshot['completed_tasks'] ) );
+	$dataRemainingEfforts[] = array( $snapshot['snapshot_date'] * 1000, ( $release_volume['man_days'] -  $snapshot['completed_man_days'] ) );
+}
+
+
+
+?>
+<div id="taskboard-burndown-chart-nav">
+	<button id="taskboard-view-btn"><?php echo _('Taskboard'); ?></button>
+	<br/>
+</div>
+
+<figure>
+	<figcaption><?php echo  _("Burndown chart") . ' : ' . $release->getTitle() ?></figcaption>
+	<div id="taskboard-burndown-chart">
+	</div>
+</figure>
+
+<script>
+	var burndownChart;
+	var xaxisData = <?php echo json_encode( $xaxisData ); ?>;
+	var dataRemainingTasks = <?php echo json_encode( $dataRemainingTasks ); ?>;
+	var dataRemainingEfforts = <?php echo json_encode( $dataRemainingEfforts ); ?>;
+	var dataRemainingIdeal = <?php echo json_encode( $dataIdeal ); ?>;
+
+	jQuery( document ).ready(function( $ ) {
+		jQuery('#taskboard-view-btn').click( function ( e ) {
+			window.location = '<?php echo util_make_url ('/plugins/'.$pluginTaskboard->name.'?group_id='. $group_id . '&_release=' . $release_id ); ?>';
+			e.preventDefault();
+		});
+		
+		burndownChart = jQuery.jqplot(
+			'taskboard-burndown-chart',
+			[ xaxisData, dataRemainingIdeal, dataRemainingTasks, dataRemainingEfforts ],
+			{
+				axesDefaults: {
+					pad : 1
+				},	
+				seriesColors: [ '#000', '#DDDDDD', '#00FA9A', '#B22222' ],
+				legend: {
+			        show: true,
+			        location: 'ne', 
+			        xoffset: 12,
+			        yoffset: 12
+			    },
+				axes : { 
+					xaxis : {	
+						renderer : jQuery.jqplot.DateAxisRenderer, 
+						tickRenderer: jQuery.jqplot.CanvasAxisTickRenderer,
+						tickOptions:{ 
+				            angle: -90,
+				            fontSize : '1.3em',
+				            formatString : '%Y-%m-%d'
+				        },
+				        numberTicks: <?php echo count($xaxisData) - 2; ?>,	
+				        min: <?php echo $release->getStartDate() * 1000; ?>,
+				        max: <?php echo $release->getEndDate() * 1000; ?>	       
+					},
+					yaxis : {
+						autoscale:true,
+						min : 0,
+					    label: "<?php echo _('Completed tasks') ?>" ,
+				        labelRenderer: jQuery.jqplot.CanvasAxisLabelRenderer,
+				        labelOptions:{
+				       		fontSize : '12px'
+				        }
+					},
+				    y2axis: {
+		                autoscale:true,	
+		                min : 0,  
+		                tickOptions:{ 
+		                	isMinorTick: true,
+		                	formatString: "%.1f <?php echo _('m/d') ?>"
+				        } 
+		            }
+				},
+				series:[ 
+					{ show : false }, // to indicates all dates only	
+					{ label : "<?php echo _('Ideal burndown') ;?>", lineWidth:1, markerOptions : { style : 'circle', size : 5 } },
+					{ label : "<?php echo _('Remaining tasks') ;?>", lineWidth:1, markerOptions : { style : 'circle', size : 5 },  yaxis: 'yaxis' },
+					{ label : "<?php echo _('Remaining efforts') ;?>", lineWidth:1, markerOptions : { style : 'circle', size : 5 } , yaxis:'y2axis' }
+				],
+				highlighter: {
+		        	show: true,
+		        	sizeAdjust: 8
+				},
+				cursor: {
+					show: false
+				}
+			}
+		);
+	});
+</script>
\ No newline at end of file
diff --git a/src/plugins/taskboard/db/20150607-plugin-taskboard-integer-to-real.sql b/src/plugins/taskboard/db/20150607-plugin-taskboard-integer-to-real.sql
new file mode 100644
index 0000000..5b8e358
--- /dev/null
+++ b/src/plugins/taskboard/db/20150607-plugin-taskboard-integer-to-real.sql
@@ -0,0 +1 @@
+alter table plugin_taskboard_releases_snapshots alter column completed_man_days real;
diff --git a/src/plugins/taskboard/db/taskboard-init.sql b/src/plugins/taskboard/db/taskboard-init.sql
index 86d1e4f..6ce142f 100644
--- a/src/plugins/taskboard/db/taskboard-init.sql
+++ b/src/plugins/taskboard/db/taskboard-init.sql
@@ -23,7 +23,7 @@ CREATE TABLE plugin_taskboard_trackers (
 
 CREATE TABLE plugin_taskboard_columns (
     taskboard_column_id SERIAL primary key,
-    taskboard_id integer REFERENCES plugin_taskboard(taskboard_id) ON DELETE CASCADE, 
+    taskboard_id integer REFERENCES plugin_taskboard(taskboard_id) ON DELETE CASCADE,
     title text NOT NULL,
     title_background_color text,
     column_background_color text,
@@ -34,8 +34,8 @@ CREATE TABLE plugin_taskboard_columns (
 
 CREATE TABLE plugin_taskboard_columns_resolutions (
     taskboard_column_value_id SERIAL primary key,
-    taskboard_column_id integer REFERENCES plugin_taskboard_columns( taskboard_column_id ) ON DELETE CASCADE, 
-    taskboard_column_resolution text NOT NULL 
+    taskboard_column_id integer REFERENCES plugin_taskboard_columns( taskboard_column_id ) ON DELETE CASCADE,
+    taskboard_column_resolution text NOT NULL
 );
 -- ALTER TABLE plugin_taskboard_columns_resolutions OWNER TO gforge;
 
@@ -43,7 +43,7 @@ CREATE TABLE plugin_taskboard_columns_resolutions (
 -- records with empty source is a records by default
 -- possible set rules:
 -- resolution = 'Fixed' (alias = element_name, for extra fields, having elements)
--- remaining_estimated_cost = 0 (alias = value, for extra fields, using values ) 
+-- remaining_estimated_cost = 0 (alias = value, for extra fields, using values )
 --
 CREATE TABLE plugin_taskboard_columns_sources (
     taskboard_column_source_id SERIAL primary key,
@@ -75,6 +75,6 @@ CREATE TABLE plugin_taskboard_releases_snapshots (
     completed_story_points integer NOT NULL DEFAULT 0,
     completed_man_days integer NOT NULL DEFAULT 0
 );
-ALTER TABLE plugin_taskboard_releases_snapshots 
-    ADD CONSTRAINT plugin_taskboard_releases_snapshots_date 
+ALTER TABLE plugin_taskboard_releases_snapshots
+    ADD CONSTRAINT plugin_taskboard_releases_snapshots_date
         UNIQUE (taskboard_release_id, snapshot_date);
diff --git a/src/plugins/taskboard/www/index.php b/src/plugins/taskboard/www/index.php
index 5482c33..bdf2fec 100644
--- a/src/plugins/taskboard/www/index.php
+++ b/src/plugins/taskboard/www/index.php
@@ -227,7 +227,9 @@ var gAjaxUrl = '<?php echo util_make_url ('/plugins/'.$pluginTaskboard->name.'/a
 var gMessages = {
 	'notasks' : "<?php echo _('There are no tasks found.') ?>",
 	'progressByTasks' : "<?php echo _('Progress by tasks') ?>",
-	'progressByCost' : "<?php echo _('Progress by cost') ?>"
+	'progressByCost' : "<?php echo _('Progress by cost') ?>",
+	'remainingCost' : "<?php echo _('Remaining m/d') ?>",
+	'completedCost' : "<?php echo _('Completed m/d') ?>"
 };
 
 <?php
diff --git a/src/plugins/taskboard/www/js/agile-board.js b/src/plugins/taskboard/www/js/agile-board.js
index 9b16e81..dcaf08c 100644
--- a/src/plugins/taskboard/www/js/agile-board.js
+++ b/src/plugins/taskboard/www/js/agile-board.js
@@ -141,8 +141,11 @@ function drawBoardProgress() {
 					totalTasks ++;
 					
 					if( aUserStories[i].tasks[t].estimated_dev_effort ) {
-						totalCostEstimated += parseInt( aUserStories[i].tasks[t].estimated_dev_effort );
-						totalCostRemaining += parseInt( aUserStories[i].tasks[t].remaining_dev_effort );
+						totalCostEstimated += parseFloat( aUserStories[i].tasks[t].estimated_dev_effort );
+					}
+					
+					if( aUserStories[i].tasks[t].remaining_dev_effort ) {
+						totalCostRemaining += parseFloat( aUserStories[i].tasks[t].remaining_dev_effort );
 					}
 					
 					lastPhaseWithTasks = j;
@@ -176,14 +179,14 @@ function drawBoardProgress() {
 
 	html += '</td></tr><table>';
 
-	if( parseInt(totalCostEstimated) > 0 ) {
+	if( parseFloat(totalCostEstimated) > 0 ) {
 		var totalCostCompleted = totalCostEstimated - totalCostRemaining;
 		var wt = parseInt( totalCostCompleted/totalCostEstimated * 100);
 		// show progress by cost
 		html += '<table>';
 		html += '<tr><td width="' + parseInt( 100 / aPhases.length )  + '%" style="padding: 0;">' + gMessages.progressByCost + ':</td><td style="padding: 0;">';
-		html += '<div class="agile-board-progress-bar-done" style="width: ' + wt + '%;">' + totalCostCompleted + '</div>';
-		html += '<div class="agile-board-progress-bar-remains" style="width: ' + ( 100 - wt ) + '%;">' + totalCostRemaining + '</div>';
+		html += '<div class="agile-board-progress-bar-remains" style="width: ' + ( 100 - wt ) + '%;" title="' + gMessages['remainingCost'] + '">' + totalCostRemaining + '</div>';
+		html += '<div class="agile-board-progress-bar-done" style="width: ' + wt + '%;" title="' + gMessages['completedCost'] + '">' + totalCostCompleted + '</div>';		
 		html += '</td></tr><table>';
 	}
 	

commit b16cafc79d7c2ca2fd0de39b3ab8dc9ccc54a6d2
Author: Vitaliy Pylypiv <pylypiv at integramedia.eu>
Date:   Sun Mar 15 17:17:32 2015 +0200

    taskboard: cherry-pick 3054d9398e0ab6112e7150b100b02f7ab995a31b

diff --git a/src/plugins/taskboard/common/TaskBoardRelease.class.php b/src/plugins/taskboard/common/TaskBoardRelease.class.php
index fdd8e41..a368859 100644
--- a/src/plugins/taskboard/common/TaskBoardRelease.class.php
+++ b/src/plugins/taskboard/common/TaskBoardRelease.class.php
@@ -254,7 +254,88 @@ class TaskBoardRelease extends Error {
 	 * @return	boolean	success.
 	 */
 	function saveSnapshot($snapshot_datetime) {
-		// TODO
+		$user_stories = $this->Taskboard->getUserStories($this->getTitle());		
+		$columns = $this->Taskboard->getColumns($this->getTitle());
+
+		$_columns_num = count($columns);
+		$_completed_user_stories = 0;
+ 		$_completed_tasks = 0;
+		$_completed_story_points = 0;
+		$_completed_man_days = 0;
+		
+		foreach( $user_stories as $us ) {
+			$completed_us = true;
+
+			for($i=0; $i < $_columns_num ; $i++ ) {
+				foreach( $us['tasks'] as $tsk ) {
+					if( $tsk['phase_id'] == $columns[$i]->getID() ) {
+						if( $i + 1 == $_columns_num ) {
+							// last column, so- completed task
+							$_completed_tasks++;
+						} else {
+							// incomplete task, so incomplete US
+							$completed_us = false;
+						}
+					}
+				}
+				
+			}
+			
+			if( $completed_us ) {
+				$_completed_user_stories++;
+				// TODO $_completed_story_points += ...
+			}
+		}
+		
+		
+		$res = db_query_params(
+				'SELECT taskboard_release_snapshot_id  FROM plugin_taskboard_releases_snapshots WHERE taskboard_release_id=$1 AND snapshot_date=$2', 
+				array ($this->getID(), $snapshot_datetime )
+		);
+
+		if (!$res) {
+			$this->setError('TaskBoardRelease: Cannot get release snapshot');
+			return false;
+		}
+		
+		$row = db_fetch_array($res);
+		db_free_result($res);
+		
+		if( $row ) {
+			$res = db_query_params(
+					'UPDATE plugin_taskboard_releases_snapshots 
+					SET completed_user_stories=$1, completed_tasks=$2, completed_story_points=$3, completed_man_days=$4
+					WHERE taskboard_release_snapshot_id=$5',
+					array(
+							$_completed_user_stories,
+							$_completed_tasks,
+							0, //TODO
+							0, //TODO
+							intval($row['taskboard_release_snapshot_id'])
+					)
+			);
+			if (!$res) {
+				return false;
+			}
+			db_free_result($res);
+		} else {
+			$res = db_query_params(
+					'INSERT INTO plugin_taskboard_releases_snapshots(taskboard_release_id, snapshot_date, completed_user_stories, completed_tasks, completed_story_points, completed_man_days)
+					VALUES($1,$2,$3,$4,$5,$6)',
+					array(
+							$this->getID(),
+							$snapshot_datetime,
+							$_completed_user_stories,
+							$_completed_tasks,
+							0, //TODO
+							0 //TODO
+					)
+			);
+			if (!$res) {
+				return false;
+			}
+			db_free_result($res);
+		}
 
 		return true;
 	}
diff --git a/src/plugins/taskboard/common/actions/ajax_save_release_snapshot.php b/src/plugins/taskboard/common/actions/ajax_save_release_snapshot.php
index d6e5252..ee91a31 100644
--- a/src/plugins/taskboard/common/actions/ajax_save_release_snapshot.php
+++ b/src/plugins/taskboard/common/actions/ajax_save_release_snapshot.php
@@ -35,11 +35,12 @@ if( $release ) {
 	if( $snapshot_datetime ) {
 		if( $snapshot_datetime >= $release->getStartDate() && $snapshot_datetime <= $release->getEndDate() ) {
 			db_begin();
-			if( !$release->saveSnapshot( $snapshot_datetime ) ) {
+			if( !$release->saveSnapshot( $snapshot_datetime, $release_id ) ) {
 				$ret['alert'] = _('Cannot save release snapshot');
 				db_rollback();
 			} else {
 				db_commit();
+				$ret['alert'] = sprintf( _('Snapshot is succesfully saved for %s'), $snapshot_date);
 			}
 		} else {
 			$ret['alert'] = _('Snapshot date should be withing release period');
diff --git a/src/plugins/taskboard/www/css/agile-board.css b/src/plugins/taskboard/www/css/agile-board.css
index beb0d4b..d387c1b 100644
--- a/src/plugins/taskboard/www/css/agile-board.css
+++ b/src/plugins/taskboard/www/css/agile-board.css
@@ -122,8 +122,9 @@
 }
 
 #taskboard-release-description {
-	margin:0 auto;
 	float: left;
+	font-size: 14px;
+	margin: 4px;
 }
 
 #taskboard-release-snapshot {
diff --git a/src/plugins/taskboard/www/index.php b/src/plugins/taskboard/www/index.php
index 4b5edd8..5482c33 100644
--- a/src/plugins/taskboard/www/index.php
+++ b/src/plugins/taskboard/www/index.php
@@ -139,7 +139,7 @@ if ($taskboard->getReleaseField()) {
 					<?php echo _('Release')._(': ').$release_box; ?>
 				</td>
 				<?php if ( forge_check_perm('tracker_admin', $group_id ) ) { ?>
-				<td>
+				<td style="vertical-align: middle;">
 					<div id="taskboard-release-description"></div>
 					<div id="taskboard-release-snapshot">
 						<input type="hidden" name="taskboard_release_id" id="taskboard-release-id" value="" />

commit 283838b1ba70566e7ed4ce36215c7dbb9f061c95
Author: Vitaliy Pylypiv <pylypiv at integramedia.eu>
Date:   Mon Mar 9 18:48:06 2015 +0200

    taskboard: cherry-pick 9ff2b752ef363c990f454cb446b7e5e3541ade33

diff --git a/src/plugins/taskboard/etc/taskboard.ini b/src/plugins/taskboard/etc/taskboard.ini
index 3a72ad5..0ee3181 100644
--- a/src/plugins/taskboard/etc/taskboard.ini
+++ b/src/plugins/taskboard/etc/taskboard.ini
@@ -1,4 +1,3 @@
 [taskboard]
-
 trackers_adapter_class = TaskBoardBasicAdapter
 plugin_status = valid

commit a0a3bf0af69a78dc172bef6186ba9baa29119f2d
Author: Vitaliy Pylypiv <pylypiv at integramedia.eu>
Date:   Sun Mar 15 09:49:12 2015 +0200

    taskboard: cherry-pick d95cbf867bf90ee30bd2c07328d2478580bbda29: small optimization

diff --git a/src/plugins/taskboard/www/index.php b/src/plugins/taskboard/www/index.php
index 182f687..4b5edd8 100644
--- a/src/plugins/taskboard/www/index.php
+++ b/src/plugins/taskboard/www/index.php
@@ -191,8 +191,7 @@ if ($taskboard->getReleaseField()) {
 	<?php
 		$used_trackers = $taskboard->getUsedTrackersIds();
 		if(count($used_trackers) == 1) {
-			$tracker = $taskboard->TrackersAdapter->getTasksTracker($used_trackers[0]);
-			echo html_e('input', array('type' => 'hidden', 'name' => 'tracker_id', 'id' => 'tracker_id', 'value' => $tracker->getID()));
+			echo html_e('input', array('type' => 'hidden', 'name' => 'tracker_id', 'id' => 'tracker_id', 'value' => $used_trackers[0]));
 		} else {
 			// select target tracker if more then single trackers are configured
 			echo "<div>\n";

-----------------------------------------------------------------------

Summary of changes:
 src/plugins/taskboard/TODO.txt                     |   6 +-
 .../common/TaskBoardColumnSource.class.php         |  15 +-
 .../taskboard/common/TaskBoardRelease.class.php    | 135 ++++++++++++++++-
 .../common/actions/ajax_save_release_snapshot.php  |   3 +-
 .../adapters/TaskBoardBasicAdapter.class.php       |  13 +-
 .../taskboard/common/views/admin/edit_column.php   |   1 -
 .../taskboard/common/views/releases/burndown.php   | 166 +++++++++++++++++++++
 .../20150607-plugin-taskboard-integer-to-real.sql  |   1 +
 src/plugins/taskboard/db/taskboard-init.sql        |  12 +-
 src/plugins/taskboard/etc/taskboard.ini            |   1 -
 src/plugins/taskboard/www/css/agile-board.css      |   3 +-
 src/plugins/taskboard/www/index.php                |   9 +-
 src/plugins/taskboard/www/js/agile-board.js        |  15 +-
 13 files changed, 354 insertions(+), 26 deletions(-)
 create mode 100644 src/plugins/taskboard/common/views/releases/burndown.php
 create mode 100644 src/plugins/taskboard/db/20150607-plugin-taskboard-integer-to-real.sql

diff --git a/src/plugins/taskboard/TODO.txt b/src/plugins/taskboard/TODO.txt
index 9a6048e..7a26292 100644
--- a/src/plugins/taskboard/TODO.txt
+++ b/src/plugins/taskboard/TODO.txt
@@ -10,14 +10,14 @@
 - DONE View - filter by assigned persons
 - DONE Releases are based either on US or on tasks
 - DONE View - indicators - progress by tasks number
-- API - configured fields support in mapped tasks ('estimated_dev_effort', 'remaining_dev_effort', 'user_story' )
-- View - indicators - progress by tasks cost (if efforts fields are configured)
+- DONE API - configured fields support in mapped tasks ('estimated_dev_effort', 'remaining_dev_effort', 'user_story' )
+- DONE View - indicators - progress by tasks cost (if efforts fields are configured)
 - DONE Admin - releases management
 - View - auto ref rate support
 - Admin - auto ref rate support
 - Admin - adapters - coloring support
 - View - adapters - coloring support (for GT)
 - View - colors by extrafield (for GT)
-- View - indicators - burndown chart
+- DONE View - indicators - burndown chart
 - View - indicators - velocity calculation
 - Improve taskboard initialization - columns by default? 
diff --git a/src/plugins/taskboard/common/TaskBoardColumnSource.class.php b/src/plugins/taskboard/common/TaskBoardColumnSource.class.php
index feae4ba..4686189 100644
--- a/src/plugins/taskboard/common/TaskBoardColumnSource.class.php
+++ b/src/plugins/taskboard/common/TaskBoardColumnSource.class.php
@@ -248,8 +248,21 @@ class TaskBoardColumnSource extends Error {
 		if( $this->getAutoassign() ) {
 			$assigned_to = user_getid();
 		}
+		
 
-		$msg = $this->getTaskboard()->TrackersAdapter->updateTask( $task,$assigned_to, $this->getTargetResolution() );
+		$remaining_cost = NULL;
+		if( $this->getTaskboard()->getRemainingCostField() ) {
+			// set 0 to remainin cost if target column is a last one
+			$target_column = taskboard_column_get_object( $this->getTargetColumnID() );
+			
+			$columns = $this->getTaskboard()->getColumns();
+			if( $target_column->getOrder() == count($columns) ) {
+				// final column, so set remaining cost to 0 
+				$remaining_cost = 0;
+			}
+		}		
+
+		$msg = $this->getTaskboard()->TrackersAdapter->updateTask( $task, $assigned_to, $this->getTargetResolution(), NULL, NULL, $remaining_cost );
 		if($msg) {
 			$msg = _('Tracker error')._(': ').$msg;
 		}
diff --git a/src/plugins/taskboard/common/TaskBoardRelease.class.php b/src/plugins/taskboard/common/TaskBoardRelease.class.php
index fdd8e41..03d65de 100644
--- a/src/plugins/taskboard/common/TaskBoardRelease.class.php
+++ b/src/plugins/taskboard/common/TaskBoardRelease.class.php
@@ -247,6 +247,63 @@ class TaskBoardRelease extends Error {
 	}
 
 	/**
+	 *  Get release volume - number of total
+	 *
+	 *  @return array  hash with user_stories, tasks, story_points, man_days key
+	 */
+	function getVolume() {
+		$user_stories = $this->Taskboard->getUserStories($this->getTitle());
+		$columns = $this->Taskboard->getColumns($this->getTitle());
+
+		$_columns_num = count($columns);
+		$ret = array(
+			'user_stories' => 0,
+			'completed_user_stories' => 0,
+			'tasks' => 0,
+			'completed_tasks' => 0,
+			'story_points' => 0,
+			'completed_story_points' => 0,
+			'man_days'=> 0,
+			'completed_man_days'=> 0,
+		);
+
+		foreach( $user_stories as $us ) {
+			$completed_us = true;
+
+			for($i=0; $i < $_columns_num ; $i++ ) {
+				foreach( $us['tasks'] as $tsk ) {
+					if( $tsk['phase_id'] == $columns[$i]->getID() ) {
+						if( $i + 1 == $_columns_num ) {
+							// last column, so - completed task
+							$ret['completed_tasks']++;							
+						} else {
+							// incomplete task, so incomplete US
+							$completed_us = false;
+						}
+						$ret['tasks']++;
+						
+						if( $tsk['estimated_dev_effort'] ) {
+							$ret['completed_man_days'] += ( (float) $tsk['estimated_dev_effort'] - (float) $tsk['remaining_dev_effort'] );
+						}
+						
+						if( $tsk['estimated_dev_effort'] ) {
+							$ret['man_days'] += (float)  $tsk['estimated_dev_effort'];
+						}
+					}
+				}
+			}
+
+			if( $completed_us ) {
+				$ret['completed_user_stories']++;
+				// TODO $_completed_story_points += ...
+			}
+			$ret['user_stories']++;
+		}
+
+		return $ret;
+	}
+
+	/**
 	 * Save current taskboard snapshot. So, we can have a history of release implementation,
 	 * that could be used for different indicators calculation.
 	 *
@@ -254,8 +311,84 @@ class TaskBoardRelease extends Error {
 	 * @return	boolean	success.
 	 */
 	function saveSnapshot($snapshot_datetime) {
-		// TODO
+		$release_volume = $this->getVolume();
+
+		$res = db_query_params(
+				'SELECT taskboard_release_snapshot_id  FROM plugin_taskboard_releases_snapshots WHERE taskboard_release_id=$1 AND snapshot_date=$2',
+				array ($this->getID(), $snapshot_datetime )
+		);
+
+		if (!$res) {
+			$this->setError('TaskBoardRelease: Cannot get release snapshot');
+			return false;
+		}
+
+		$row = db_fetch_array($res);
+		db_free_result($res);
+
+		if( $row ) {
+			$res = db_query_params(
+					'UPDATE plugin_taskboard_releases_snapshots
+					SET completed_user_stories=$1, completed_tasks=$2, completed_story_points=$3, completed_man_days=$4
+					WHERE taskboard_release_snapshot_id=$5',
+					array(
+							$release_volume['completed_user_stories'],
+							$release_volume['completed_tasks'],
+							$release_volume['completed_story_points'],
+							$release_volume['completed_man_days'],
+							intval($row['taskboard_release_snapshot_id'])
+					)
+			);
+			if (!$res) {
+				return false;
+			}
+			db_free_result($res);
+		} else {
+			$res = db_query_params(
+					'INSERT INTO plugin_taskboard_releases_snapshots(taskboard_release_id, snapshot_date, completed_user_stories, completed_tasks, completed_story_points, completed_man_days)
+					VALUES($1,$2,$3,$4,$5,$6)',
+					array(
+							$this->getID(),
+							$snapshot_datetime,
+							$release_volume['completed_user_stories'],
+							$release_volume['completed_tasks'],
+							$release_volume['completed_story_points'],
+							$release_volume['completed_man_days'],
+					)
+			);
+			if (!$res) {
+				return false;
+			}
+			db_free_result($res);
+		}
 
 		return true;
 	}
+
+	/**
+	 * Get current release snapshots
+	 *
+	 * @return	array
+	 */
+	function getSnapshots() {
+		$ret = array();
+
+		$res = db_query_params(
+				'SELECT  snapshot_date, completed_user_stories, completed_tasks, completed_story_points, completed_man_days
+				FROM plugin_taskboard_releases_snapshots WHERE taskboard_release_id=$1 ORDER BY snapshot_date',
+				array ($this->getID() )
+		);
+
+		if (!$res) {
+			$this->setError('TaskBoardRelease: Cannot get release snapshots');
+			return false;
+		}
+
+		while( $row = db_fetch_array($res) ) {
+			$ret[] = $row;
+		}
+		db_free_result($res);
+
+		return $ret;
+	}
 }
diff --git a/src/plugins/taskboard/common/actions/ajax_save_release_snapshot.php b/src/plugins/taskboard/common/actions/ajax_save_release_snapshot.php
index d6e5252..ee91a31 100644
--- a/src/plugins/taskboard/common/actions/ajax_save_release_snapshot.php
+++ b/src/plugins/taskboard/common/actions/ajax_save_release_snapshot.php
@@ -35,11 +35,12 @@ if( $release ) {
 	if( $snapshot_datetime ) {
 		if( $snapshot_datetime >= $release->getStartDate() && $snapshot_datetime <= $release->getEndDate() ) {
 			db_begin();
-			if( !$release->saveSnapshot( $snapshot_datetime ) ) {
+			if( !$release->saveSnapshot( $snapshot_datetime, $release_id ) ) {
 				$ret['alert'] = _('Cannot save release snapshot');
 				db_rollback();
 			} else {
 				db_commit();
+				$ret['alert'] = sprintf( _('Snapshot is succesfully saved for %s'), $snapshot_date);
 			}
 		} else {
 			$ret['alert'] = _('Snapshot date should be withing release period');
diff --git a/src/plugins/taskboard/common/adapters/TaskBoardBasicAdapter.class.php b/src/plugins/taskboard/common/adapters/TaskBoardBasicAdapter.class.php
index f6283c9..17a723f 100644
--- a/src/plugins/taskboard/common/adapters/TaskBoardBasicAdapter.class.php
+++ b/src/plugins/taskboard/common/adapters/TaskBoardBasicAdapter.class.php
@@ -322,7 +322,7 @@ class TaskBoardBasicAdapter {
 	 *
 	 * @return   string     error message in case of fail
 	 */
-	function updateTask(&$artifact, $assigned_to, $resolution, $title = NULL, $description = NULL) {
+	function updateTask(&$artifact, $assigned_to, $resolution, $title = NULL, $description = NULL, $remaining_cost=NULL ) {
 		if (!$assigned_to) {
 			$assigned_to = $artifact->getAssignedTo();
 		}
@@ -341,6 +341,17 @@ class TaskBoardBasicAdapter {
 			}
 		}
 
+		if($remaining_cost!==NULL) {
+			$remaining_cost_alias = $this->TaskBoard->getRemainingCostField();
+
+			if($remaining_cost_alias) {
+				if (array_key_exists($remaining_cost_alias, $fields_ids)){
+					$remaining_cost_field_id = $fields_ids[$remaining_cost_alias];
+					$extra_fields[ $remaining_cost_field_id ] = $remaining_cost;
+				}
+			}
+		} 
+
 		if (!$title) {
 			$title = htmlspecialchars_decode($artifact->getSummary());
 		}
diff --git a/src/plugins/taskboard/common/views/admin/edit_column.php b/src/plugins/taskboard/common/views/admin/edit_column.php
index 480dfa0..2f89bb2 100644
--- a/src/plugins/taskboard/common/views/admin/edit_column.php
+++ b/src/plugins/taskboard/common/views/admin/edit_column.php
@@ -94,7 +94,6 @@ if ($column_id) {
 		echo $HTML->multiTableRow(array(), $cells);
 		$cells = array();
 		$cells[][] = html_e('strong', array(), _('Alert message'));
-		error_log($drop_rules_by_default->getAlertText());
 		$cells[][] = html_e('textarea', array('name' => 'alert', 'cols' => 79, 'rows' => 5), htmlspecialchars($drop_rules_by_default->getAlertText()), false);
 		echo $HTML->multiTableRow(array(), $cells);
 		echo $HTML->listTableBottom();
diff --git a/src/plugins/taskboard/common/views/releases/burndown.php b/src/plugins/taskboard/common/views/releases/burndown.php
new file mode 100644
index 0000000..3e241c6
--- /dev/null
+++ b/src/plugins/taskboard/common/views/releases/burndown.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Copyright (C) 2015 Vitaliy Pylypiv <vitaliy.pylypiv at gmail.com>
+ *
+ * This file is part of FusionForge.
+ *
+ * FusionForge is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * FusionForge is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+global $group_id, $group, $taskboard;
+
+$release_id = getIntFromRequest('release_id', NULL);
+
+$release = new TaskBoardRelease( $taskboard, $release_id );
+
+html_use_jqueryjqplotpluginCanvas();
+html_use_jqueryjqplotplugindateAxisRenderer();
+html_use_jqueryjqplotpluginhighlighter();
+
+$taskboard->header(
+		array(
+			'title' => _('Taskboard for ').$group->getPublicName()._(': '). _('Releases')._(': ')._('Burndown chart')._(': ').$release->getTitle() ,
+			'pagename' => _('Releases')._(': ')._('Burndown chart')._(': ').$release->getTitle(),
+			'sectionvals' => array($group->getPublicName()),
+			'group' => $group_id
+		)
+	);
+
+if ($taskboard->isError()) {
+	echo $HTML->error_msg($taskboard->getErrorMessage());
+} else {
+	echo html_e('div', array('id' => 'messages', 'style' => 'display: none;'), '', false);
+}
+
+// $xaxisData is used to have an every date on the X axis
+$xaxisData = array();
+$chartDate = $release->getStartDate();
+while( $chartDate <= $release->getEndDate() ) {
+	$xaxisData[] = array(  date( 'r', $chartDate) , 0);
+	$chartDate += 86400;
+}
+
+$release_volume = $release->getVolume();
+
+// ideal burndown
+$dataIdeal = array(
+	array( $release->getStartDate() * 1000,  $release_volume['tasks']),
+	array( $release->getEndDate() * 1000, 0)
+);
+
+$release_snapshots = $release->getSnapshots();
+$dataRemainingTasks = array();
+$dataRemainingEfforts = array();
+
+foreach( $release_snapshots as $snapshot ) {
+	if( count($dataRemainingTasks) == 0 && $snapshot['snapshot_date'] != $release->getStartDate() ) {
+		// initialize start point if snapshot is missing for the first day
+		$dataRemainingTasks[] = array( $release->getStartDate() * 1000, $release_volume['tasks'] );
+		$dataRemainingEfforts[] = array( $release->getStartDate() * 1000, $release_volume['man_days'] );
+	}
+	
+	$dataRemainingTasks[] = array( $snapshot['snapshot_date'] * 1000, ( $release_volume['tasks'] - $snapshot['completed_tasks'] ) );
+	$dataRemainingEfforts[] = array( $snapshot['snapshot_date'] * 1000, ( $release_volume['man_days'] -  $snapshot['completed_man_days'] ) );
+}
+
+
+
+?>
+<div id="taskboard-burndown-chart-nav">
+	<button id="taskboard-view-btn"><?php echo _('Taskboard'); ?></button>
+	<br/>
+</div>
+
+<figure>
+	<figcaption><?php echo  _("Burndown chart") . ' : ' . $release->getTitle() ?></figcaption>
+	<div id="taskboard-burndown-chart">
+	</div>
+</figure>
+
+<script>
+	var burndownChart;
+	var xaxisData = <?php echo json_encode( $xaxisData ); ?>;
+	var dataRemainingTasks = <?php echo json_encode( $dataRemainingTasks ); ?>;
+	var dataRemainingEfforts = <?php echo json_encode( $dataRemainingEfforts ); ?>;
+	var dataRemainingIdeal = <?php echo json_encode( $dataIdeal ); ?>;
+
+	jQuery( document ).ready(function( $ ) {
+		jQuery('#taskboard-view-btn').click( function ( e ) {
+			window.location = '<?php echo util_make_url ('/plugins/'.$pluginTaskboard->name.'?group_id='. $group_id . '&_release=' . $release_id ); ?>';
+			e.preventDefault();
+		});
+		
+		burndownChart = jQuery.jqplot(
+			'taskboard-burndown-chart',
+			[ xaxisData, dataRemainingIdeal, dataRemainingTasks, dataRemainingEfforts ],
+			{
+				axesDefaults: {
+					pad : 1
+				},
+				seriesColors: [ '#000', '#DDDDDD', '#00FA9A', '#B22222' ],
+				legend: {
+					show: true,
+					location: 'ne', 
+					xoffset: 12,
+					yoffset: 12
+				},
+				axes : { 
+					xaxis : {
+						renderer : jQuery.jqplot.DateAxisRenderer, 
+						tickRenderer: jQuery.jqplot.CanvasAxisTickRenderer,
+						tickOptions:{ 
+							angle: -90,
+							fontSize : '1.3em',
+							formatString : '%Y-%m-%d'
+						},
+						numberTicks: <?php echo count($xaxisData) - 2; ?>,
+						min: <?php echo $release->getStartDate() * 1000; ?>,
+						max: <?php echo $release->getEndDate() * 1000; ?>
+					},
+					yaxis : {
+						autoscale:true,
+						min : 0,
+						label: "<?php echo _('Completed tasks') ?>" ,
+						labelRenderer: jQuery.jqplot.CanvasAxisLabelRenderer,
+							labelOptions:{
+							fontSize : '12px'
+						}
+					},
+					y2axis: {
+						autoscale:true,
+						min : 0,
+						tickOptions:{ 
+						isMinorTick: true,
+						formatString: "%.1f <?php echo _('m/d') ?>"
+						}
+					}
+				},
+				series:[
+					{ show : false }, // to indicate all dates
+					{ label : "<?php echo _('Ideal burndown') ;?>", lineWidth:1, markerOptions : { style : 'circle', size : 5 } },
+					{ label : "<?php echo _('Remaining tasks') ;?>", lineWidth:1, markerOptions : { style : 'circle', size : 5 },  yaxis: 'yaxis' },
+					{ label : "<?php echo _('Remaining efforts') ;?>", lineWidth:1, markerOptions : { style : 'circle', size : 5 } , yaxis:'y2axis' }
+				],
+				highlighter: {
+					show: true,
+					sizeAdjust: 8
+				},
+				cursor: {
+					show: false
+				}
+			}
+		);
+	});
+</script>
\ No newline at end of file
diff --git a/src/plugins/taskboard/db/20150607-plugin-taskboard-integer-to-real.sql b/src/plugins/taskboard/db/20150607-plugin-taskboard-integer-to-real.sql
new file mode 100644
index 0000000..5b8e358
--- /dev/null
+++ b/src/plugins/taskboard/db/20150607-plugin-taskboard-integer-to-real.sql
@@ -0,0 +1 @@
+alter table plugin_taskboard_releases_snapshots alter column completed_man_days real;
diff --git a/src/plugins/taskboard/db/taskboard-init.sql b/src/plugins/taskboard/db/taskboard-init.sql
index 86d1e4f..6ce142f 100644
--- a/src/plugins/taskboard/db/taskboard-init.sql
+++ b/src/plugins/taskboard/db/taskboard-init.sql
@@ -23,7 +23,7 @@ CREATE TABLE plugin_taskboard_trackers (
 
 CREATE TABLE plugin_taskboard_columns (
     taskboard_column_id SERIAL primary key,
-    taskboard_id integer REFERENCES plugin_taskboard(taskboard_id) ON DELETE CASCADE, 
+    taskboard_id integer REFERENCES plugin_taskboard(taskboard_id) ON DELETE CASCADE,
     title text NOT NULL,
     title_background_color text,
     column_background_color text,
@@ -34,8 +34,8 @@ CREATE TABLE plugin_taskboard_columns (
 
 CREATE TABLE plugin_taskboard_columns_resolutions (
     taskboard_column_value_id SERIAL primary key,
-    taskboard_column_id integer REFERENCES plugin_taskboard_columns( taskboard_column_id ) ON DELETE CASCADE, 
-    taskboard_column_resolution text NOT NULL 
+    taskboard_column_id integer REFERENCES plugin_taskboard_columns( taskboard_column_id ) ON DELETE CASCADE,
+    taskboard_column_resolution text NOT NULL
 );
 -- ALTER TABLE plugin_taskboard_columns_resolutions OWNER TO gforge;
 
@@ -43,7 +43,7 @@ CREATE TABLE plugin_taskboard_columns_resolutions (
 -- records with empty source is a records by default
 -- possible set rules:
 -- resolution = 'Fixed' (alias = element_name, for extra fields, having elements)
--- remaining_estimated_cost = 0 (alias = value, for extra fields, using values ) 
+-- remaining_estimated_cost = 0 (alias = value, for extra fields, using values )
 --
 CREATE TABLE plugin_taskboard_columns_sources (
     taskboard_column_source_id SERIAL primary key,
@@ -75,6 +75,6 @@ CREATE TABLE plugin_taskboard_releases_snapshots (
     completed_story_points integer NOT NULL DEFAULT 0,
     completed_man_days integer NOT NULL DEFAULT 0
 );
-ALTER TABLE plugin_taskboard_releases_snapshots 
-    ADD CONSTRAINT plugin_taskboard_releases_snapshots_date 
+ALTER TABLE plugin_taskboard_releases_snapshots
+    ADD CONSTRAINT plugin_taskboard_releases_snapshots_date
         UNIQUE (taskboard_release_id, snapshot_date);
diff --git a/src/plugins/taskboard/etc/taskboard.ini b/src/plugins/taskboard/etc/taskboard.ini
index 3a72ad5..0ee3181 100644
--- a/src/plugins/taskboard/etc/taskboard.ini
+++ b/src/plugins/taskboard/etc/taskboard.ini
@@ -1,4 +1,3 @@
 [taskboard]
-
 trackers_adapter_class = TaskBoardBasicAdapter
 plugin_status = valid
diff --git a/src/plugins/taskboard/www/css/agile-board.css b/src/plugins/taskboard/www/css/agile-board.css
index beb0d4b..d387c1b 100644
--- a/src/plugins/taskboard/www/css/agile-board.css
+++ b/src/plugins/taskboard/www/css/agile-board.css
@@ -122,8 +122,9 @@
 }
 
 #taskboard-release-description {
-	margin:0 auto;
 	float: left;
+	font-size: 14px;
+	margin: 4px;
 }
 
 #taskboard-release-snapshot {
diff --git a/src/plugins/taskboard/www/index.php b/src/plugins/taskboard/www/index.php
index 182f687..bdf2fec 100644
--- a/src/plugins/taskboard/www/index.php
+++ b/src/plugins/taskboard/www/index.php
@@ -139,7 +139,7 @@ if ($taskboard->getReleaseField()) {
 					<?php echo _('Release')._(': ').$release_box; ?>
 				</td>
 				<?php if ( forge_check_perm('tracker_admin', $group_id ) ) { ?>
-				<td>
+				<td style="vertical-align: middle;">
 					<div id="taskboard-release-description"></div>
 					<div id="taskboard-release-snapshot">
 						<input type="hidden" name="taskboard_release_id" id="taskboard-release-id" value="" />
@@ -191,8 +191,7 @@ if ($taskboard->getReleaseField()) {
 	<?php
 		$used_trackers = $taskboard->getUsedTrackersIds();
 		if(count($used_trackers) == 1) {
-			$tracker = $taskboard->TrackersAdapter->getTasksTracker($used_trackers[0]);
-			echo html_e('input', array('type' => 'hidden', 'name' => 'tracker_id', 'id' => 'tracker_id', 'value' => $tracker->getID()));
+			echo html_e('input', array('type' => 'hidden', 'name' => 'tracker_id', 'id' => 'tracker_id', 'value' => $used_trackers[0]));
 		} else {
 			// select target tracker if more then single trackers are configured
 			echo "<div>\n";
@@ -228,7 +227,9 @@ var gAjaxUrl = '<?php echo util_make_url ('/plugins/'.$pluginTaskboard->name.'/a
 var gMessages = {
 	'notasks' : "<?php echo _('There are no tasks found.') ?>",
 	'progressByTasks' : "<?php echo _('Progress by tasks') ?>",
-	'progressByCost' : "<?php echo _('Progress by cost') ?>"
+	'progressByCost' : "<?php echo _('Progress by cost') ?>",
+	'remainingCost' : "<?php echo _('Remaining m/d') ?>",
+	'completedCost' : "<?php echo _('Completed m/d') ?>"
 };
 
 <?php
diff --git a/src/plugins/taskboard/www/js/agile-board.js b/src/plugins/taskboard/www/js/agile-board.js
index 9b16e81..d55b7fc 100644
--- a/src/plugins/taskboard/www/js/agile-board.js
+++ b/src/plugins/taskboard/www/js/agile-board.js
@@ -141,8 +141,11 @@ function drawBoardProgress() {
 					totalTasks ++;
 					
 					if( aUserStories[i].tasks[t].estimated_dev_effort ) {
-						totalCostEstimated += parseInt( aUserStories[i].tasks[t].estimated_dev_effort );
-						totalCostRemaining += parseInt( aUserStories[i].tasks[t].remaining_dev_effort );
+						totalCostEstimated += parseFloat( aUserStories[i].tasks[t].estimated_dev_effort );
+					}
+					
+					if( aUserStories[i].tasks[t].remaining_dev_effort ) {
+						totalCostRemaining += parseFloat( aUserStories[i].tasks[t].remaining_dev_effort );
 					}
 					
 					lastPhaseWithTasks = j;
@@ -176,14 +179,14 @@ function drawBoardProgress() {
 
 	html += '</td></tr><table>';
 
-	if( parseInt(totalCostEstimated) > 0 ) {
+	if( parseFloat(totalCostEstimated) > 0 ) {
 		var totalCostCompleted = totalCostEstimated - totalCostRemaining;
 		var wt = parseInt( totalCostCompleted/totalCostEstimated * 100);
 		// show progress by cost
 		html += '<table>';
 		html += '<tr><td width="' + parseInt( 100 / aPhases.length )  + '%" style="padding: 0;">' + gMessages.progressByCost + ':</td><td style="padding: 0;">';
-		html += '<div class="agile-board-progress-bar-done" style="width: ' + wt + '%;">' + totalCostCompleted + '</div>';
-		html += '<div class="agile-board-progress-bar-remains" style="width: ' + ( 100 - wt ) + '%;">' + totalCostRemaining + '</div>';
+		html += '<div class="agile-board-progress-bar-done" style="width: ' + wt + '%;" title="' + gMessages['completedCost'] + '">' + totalCostCompleted + '</div>';
+		html += '<div class="agile-board-progress-bar-remains" style="width: ' + ( 100 - wt ) + '%;" title="' + gMessages['remainingCost'] + '">' + totalCostRemaining + '</div>';
 		html += '</td></tr><table>';
 	}
 	
@@ -479,7 +482,7 @@ function initEditable() {
 				}
 			});
 		}
-	});	
+	});
 	
 }
 


hooks/post-receive
-- 
FusionForge



More information about the Fusionforge-commits mailing list