Dependency display

Task views now include the list of dependencies and reverse
dependencies. In addition, it is impossible to mark a task as completed
if it has unsatisfied dependencies, and it is impossible to re-activate
a task that has completed reverse dependencies.
This commit is contained in:
Emmanuel BENOîT 2012-02-05 19:59:51 +01:00
parent 9677ad4dd3
commit fd50fda73a
6 changed files with 121 additions and 11 deletions

View file

@ -98,6 +98,27 @@ class DAO_Tasks
. 'INNER JOIN users u USING (user_id) ' . 'INNER JOIN users u USING (user_id) '
. 'WHERE n.task_id = $1 ' . 'WHERE n.task_id = $1 '
. 'ORDER BY n.note_added DESC' )->execute( $id ); . 'ORDER BY n.note_added DESC' )->execute( $id );
$task->dependencies = $this->query(
'SELECT t.task_id AS id , t.task_title AS title , t.item_id AS item , '
. 'i.item_name AS item_name , '
. '( ct.completed_task_time IS NOT NULL ) AS completed '
. 'FROM task_dependencies td '
. 'INNER JOIN tasks t ON t.task_id = td.task_id_depends '
. 'INNER JOIN items i USING ( item_id ) '
. 'LEFT OUTER JOIN completed_tasks ct ON ct.task_id = t.task_id '
. 'WHERE td.task_id = $1 '
. 'ORDER BY i.item_name , t.task_priority , t.task_title' )->execute( $id );
$task->reverseDependencies = $this->query(
'SELECT t.task_id AS id , t.task_title AS title , t.item_id AS item , '
. 'i.item_name AS item_name , '
. '( ct.completed_task_time IS NOT NULL ) AS completed '
. 'FROM task_dependencies td '
. 'INNER JOIN tasks t USING( task_id ) '
. 'INNER JOIN items i USING ( item_id ) '
. 'LEFT OUTER JOIN completed_tasks ct USING ( task_id ) '
. 'WHERE td.task_id_depends = $1 '
. 'ORDER BY i.item_name , t.task_priority , t.task_title' )->execute( $id );
return $task; return $task;
} }
@ -113,6 +134,30 @@ class DAO_Tasks
} }
public function canFinish( $task )
{
assert( $task->completed_at == null );
foreach ( $task->dependencies as $dependency ) {
if ( $dependency->completed != 't' ) {
return false;
}
}
return true;
}
public function canRestart( $task )
{
assert( $task->completed_at != null );
foreach ( $task->reverseDependencies as $dependency ) {
if ( $dependency->completed == 't' ) {
return false;
}
}
return true;
}
public function delete( $task ) public function delete( $task )
{ {
$this->query( 'DELETE FROM tasks WHERE task_id = $1' )->execute( $task ); $this->query( 'DELETE FROM tasks WHERE task_id = $1' )->execute( $task );

View file

@ -66,15 +66,20 @@ class Ctrl_TaskDetails
$items->getLineage( $this->task->item = $items->get( $this->task->item ) ); $items->getLineage( $this->task->item = $items->get( $this->task->item ) );
$box = Loader::View( 'box' , $bTitle , Loader::View( 'task_details' , $this->task ) ); $box = Loader::View( 'box' , $bTitle , Loader::View( 'task_details' , $this->task ) );
$tasks = Loader::DAO( 'tasks' );
if ( $this->task->completed_by === null ) { if ( $this->task->completed_by === null ) {
$box->addButton( BoxButton::create( 'Edit task' , 'tasks/edit?id=' . $this->task->id ) $box->addButton( BoxButton::create( 'Edit task' , 'tasks/edit?id=' . $this->task->id )
->setClass( 'icon edit' ) ) ->setClass( 'icon edit' ) );
->addButton( BoxButton::create( 'Mark as completed' , if ( $tasks->canFinish( $this->task ) ) {
'tasks/finish?id=' . $this->task->id ) $box->addButton( BoxButton::create( 'Mark as completed' , 'tasks/finish?id=' . $this->task->id )
->setClass( 'icon stop' ) ); ->setClass( 'icon stop' ) );
};
} else { } else {
$box->addButton( BoxButton::create( 'Re-activate' , 'tasks/restart?id=' . $this->task->id ) if ( $tasks->canRestart( $this->task ) ) {
$box->addButton( BoxButton::create( 'Re-activate' , 'tasks/restart?id=' . $this->task->id )
->setClass( 'icon start' ) ); ->setClass( 'icon start' ) );
}
$timestamp = strtotime( $this->task->completed_at ); $timestamp = strtotime( $this->task->completed_at );
} }
@ -88,6 +93,34 @@ class Ctrl_TaskDetails
} }
class Ctrl_TaskDependencies
extends Controller
{
private $task;
public function __construct( $task )
{
$this->task = $task;
}
public function handle( Page $page )
{
$views = array(
Loader::View( 'box' , 'Dependencies' ,
Loader::View( 'task_dependencies' , $this->task , false ) )
);
if ( ! empty( $this->task->reverseDependencies ) ) {
array_push( $views , Loader::View( 'box' , 'Reverse dependencies' ,
Loader::View( 'task_dependencies' , $this->task , true ) ) );
}
return $views;
}
}
class Ctrl_TaskNotes class Ctrl_TaskNotes
extends Controller extends Controller
{ {
@ -161,9 +194,9 @@ class Ctrl_ToggleTask
return 'tasks'; return 'tasks';
} }
if ( $this->isRestart ) { if ( $this->isRestart && $tasks->canRestart( $task ) ) {
$tasks->restart( $id , '[AUTO] Task re-activated.' ); $tasks->restart( $id , '[AUTO] Task re-activated.' );
} else { } else if ( ! $this->isRestart && $tasks->canFinish( $task ) ) {
$tasks->finish( $id , '[AUTO] Task completed.' ); $tasks->finish( $id , '[AUTO] Task completed.' );
} }

View file

@ -22,6 +22,7 @@ $package[ 'ctrls' ][] = 'edit_note_form';
$package[ 'ctrls' ][] = 'edit_note'; $package[ 'ctrls' ][] = 'edit_note';
$package[ 'ctrls' ][] = 'edit_task_form'; $package[ 'ctrls' ][] = 'edit_task_form';
$package[ 'ctrls' ][] = 'edit_task'; $package[ 'ctrls' ][] = 'edit_task';
$package[ 'ctrls' ][] = 'task_dependencies';
$package[ 'ctrls' ][] = 'task_details'; $package[ 'ctrls' ][] = 'task_details';
$package[ 'ctrls' ][] = 'task_notes'; $package[ 'ctrls' ][] = 'task_notes';
$package[ 'ctrls' ][] = 'toggle_task'; $package[ 'ctrls' ][] = 'toggle_task';
@ -31,5 +32,6 @@ $package[ 'views' ][] = 'all_tasks';
$package[ 'views' ][] = 'tasks'; $package[ 'views' ][] = 'tasks';
$package[ 'views' ][] = 'task_details'; $package[ 'views' ][] = 'task_details';
$package[ 'views' ][] = 'task_note'; $package[ 'views' ][] = 'task_note';
$package[ 'views' ][] = 'task_dependencies';
$package[ 'pages' ][] = 'tasks_tasks'; $package[ 'pages' ][] = 'tasks_tasks';

View file

@ -150,7 +150,10 @@ class Ctrl_ViewTask
} }
$page->setTitle( $task->title . ' (task)' ); $page->setTitle( $task->title . ' (task)' );
$result = array( Loader::Ctrl( 'task_details' , $task ) ); $result = array(
Loader::Ctrl( 'task_details' , $task ) ,
Loader::Ctrl( 'task_dependencies' , $task ) ,
);
if ( $task->completed_by === null ) { if ( $task->completed_by === null ) {
array_push( $result , Loader::Ctrl( 'add_task_note_form' , $task ) ); array_push( $result , Loader::Ctrl( 'add_task_note_form' , $task ) );

View file

@ -272,7 +272,7 @@ class View_TaskNote
class View_TaskDependencies class View_TaskDependencies
implements View extends BaseURLAwareView
{ {
private $task; private $task;
private $reverse; private $reverse;
@ -286,16 +286,35 @@ class View_TaskDependencies
public function render( ) public function render( )
{ {
$source = $this->reverse ? 'reverseDependencies' : 'dependencies'; $source = $this->reverse ? 'reverseDependencies' : 'dependencies';
if ( empty( $this->task->$source ) ) {
return HTML::make( 'div' )
->setAttribute( 'class' , 'no-table' )
->appendText( 'This task has no dependencies.' );
}
$list = HTML::make( 'ul' )->setAttribute( 'class' , 'dep-list' ); $list = HTML::make( 'ul' )->setAttribute( 'class' , 'dep-list' );
$prevItem = null;
$itemList = null;
foreach ( $this->task->$source as $dependency ) { foreach ( $this->task->$source as $dependency ) {
if ( $prevItem !== $dependency->item ) {
$itemList = HTML::make( 'ul' );
$list->appendElement( HTML::make( 'li' )
->appendText( 'In ' )
->appendElement( HTML::make( 'a' )
->setAttribute( 'href' , $this->base . '/items/view?id=' . $dependency->item )
->appendText( $dependency->item_name ) )
->appendElement( $itemList ) );
$prevItem = $dependency->item;
}
$link = HTML::make( 'a' ) $link = HTML::make( 'a' )
->setAttribute( 'href' , 'tasks/view?id=' . $dependency->id ) ->setAttribute( 'href' , $this->base . '/tasks/view?id=' . $dependency->id )
->appendText( $dependency->title ); ->appendText( $dependency->title );
if ( ! $this->reverse ) { if ( ! $this->reverse ) {
$link->setAttribute( 'class' , ( $dependency->completed == 't' ) $link->setAttribute( 'class' , ( $dependency->completed == 't' )
? 'satisfied' : 'missing' ); ? 'satisfied' : 'missing' );
} }
$list->appendElement( HTML::make( 'li' )->appendElement( $link ) ); $itemList->appendElement( HTML::make( 'li' )->appendElement( $link ) );
} }
return $list;
} }
} }

View file

@ -393,3 +393,11 @@ dl.tasks dt.sub-title.completed {
color: #bfbfbf; color: #bfbfbf;
text-shadow: 1px 1px 2px #3f3f3f; text-shadow: 1px 1px 2px #3f3f3f;
} }
ul.dep-list a.missing {
color: red;
}
ul.dep-list a.satisfied {
color: green;
}