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:
parent
9677ad4dd3
commit
fd50fda73a
6 changed files with 121 additions and 11 deletions
|
@ -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 );
|
||||||
|
|
|
@ -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.' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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 ) );
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue