@@ -50,7 +50,6 @@ public function __construct($project, $viewer) {
5050
5151 if (!$ start OR !$ end )
5252 {
53- // TODO make this show a real error
5453 throw new Exception ("This project is not set up for Burndowns, make "
5554 ."sure it has 'Sprint' in the name, and then edit it to add the sprint "
5655 ."start and end date. " );
@@ -176,7 +175,15 @@ public function __construct($project, $viewer) {
176175 $ previous = $ current ;
177176 }
178177
179- // Compute "Ideal Points remaining" column
178+ $ this ->computeIdealPoints ();
179+
180+ }
181+
182+ /**
183+ * Compute the values for the "Ideal Points" line.
184+ */
185+ private function computeIdealPoints () {
186+
180187 // This is a cheap hacky way to get business days, and does not account for
181188 // holidays at all.
182189 $ total_business_days = 0 ;
@@ -371,23 +378,14 @@ public function buildBurnDownTable() {
371378 * @returns PHUIObjectBoxView
372379 */
373380 public function buildTasksTable () {
374- $ rows = array ();
375- foreach ($ this ->tasks as $ task ) {
376- $ rows [] = array (
377- phutil_tag (
378- 'a ' ,
379- array (
380- 'href ' => '/ ' .$ task ->getMonogram (),
381- ),
382- $ task ->getMonogram ().': ' .$ task ->getTitle ()),
383- $ task ->getStatus (),
384- );
385- }
381+
382+ $ rows = $ this ->buildTasksTree ();
386383
387384 $ table = id (new AphrontTableView ($ rows ))
388385 ->setHeaders (
389386 array (
390387 pht ('Task ' ),
388+ pht ('Assigned to ' ),
391389 pht ('Status ' ),
392390 ));
393391
@@ -398,6 +396,115 @@ public function buildTasksTable() {
398396 return $ box ;
399397 }
400398
399+ /**
400+ * This builds a tree of the tasks in this project. Due to the acyclic nature
401+ * of tasks, we ntake some steps to reduce and call out duplication.
402+ *
403+ * We ignore any tasks not in this sprint.
404+ *
405+ * @return array
406+ */
407+ private function buildTasksTree () {
408+ // Shorter constants
409+ $ DEPENDS_ON = PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK ;
410+ $ DEPENDED_ON = PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK ;
411+
412+ // Load all edges of depends and depended on tasks
413+ $ edges = id (new PhabricatorEdgeQuery ())
414+ ->withSourcePHIDs (array_keys ($ this ->tasks ))
415+ ->withEdgeTypes (array ($ DEPENDS_ON , $ DEPENDED_ON ))
416+ ->execute ();
417+
418+ // First we build a flat map. Each task is in the map at the root level,
419+ // and lists it's parents and children.
420+ $ map = array ();
421+ foreach ($ this ->tasks as $ task ) {
422+ if ($ parents = $ edges [$ task ->getPHID ()][$ DEPENDED_ON ]) {
423+ foreach ($ parents as $ parent ) {
424+ // Make sure this task is in this sprint.
425+ if (isset ($ this ->tasks [$ parent ['dst ' ]]))
426+ $ map [$ task ->getPHID ()]['parents ' ][] = $ parent ['dst ' ];
427+ }
428+ }
429+
430+ if ($ children = $ edges [$ task ->getPHID ()][$ DEPENDS_ON ]) {
431+ foreach ($ children as $ child ) {
432+ // Make sure this task is in this sprint.
433+ if (isset ($ this ->tasks [$ child ['dst ' ]])) {
434+ $ map [$ task ->getPHID ()]['children ' ][] = $ child ['dst ' ];
435+ }
436+ }
437+ }
438+ }
439+
440+ // We also collect the phids we need to fetch owner information
441+ $ handle_phids = array ();
442+ foreach ($ this ->tasks as $ task ) {
443+ // Get the owner (assigned to) phid
444+ $ handle_phids [$ task ->getOwnerPHID ()] = $ task ->getOwnerPHID ();
445+ }
446+
447+ $ handles = id (new PhabricatorHandleQuery ())
448+ ->setViewer ($ this ->viewer )
449+ ->withPHIDs ($ handle_phids )
450+ ->execute ();
451+
452+ // Now we loop through the tasks, and add them to the output
453+ $ output = array ();
454+ foreach ($ this ->tasks as $ task ) {
455+ // If parents is set, it means this task has a parent in this sprint so
456+ // skip it, the parent will handle adding this task to the output
457+ if (isset ($ map [$ task ->getPHID ()]['parents ' ])) {
458+ continue ;
459+ }
460+
461+ $ this ->addTaskToTree ($ output , $ task , $ map , $ handles );
462+ }
463+
464+ return $ output ;
465+ }
466+
467+ private function addTaskToTree (&$ output , $ task , &$ map , $ handles ,
468+ $ depth = 0 ) {
469+ static $ included = array ();
470+
471+ // Get the owner object so we can render the owner username/link
472+ $ owner = $ handles [$ task ->getOwnerPHID ()];
473+
474+ // If this task is already is this tree, this is a repeat.
475+ $ repeat = isset ($ included [$ task ->getPHID ()]);
476+
477+ $ depth_indent ='' ;
478+ for ($ i =0 ; $ i <$ depth ; $ i ++) {
479+ $ depth_indent .=' ' ;
480+ }
481+
482+ // Build the row
483+ $ output [] = array (
484+ phutil_safe_html ($ depth_indent .phutil_tag (
485+ 'a ' ,
486+ array (
487+ 'href ' => '/ ' .$ task ->getMonogram (),
488+ 'class ' => $ task ->getStatus () !== 'open '
489+ ? 'phui-tag-core-closed '
490+ : '' ,
491+ ),
492+ $ task ->getMonogram ().': ' .$ task ->getTitle ()
493+ ).($ repeat ? ' <em title="This task is a child of more than one task in this list. Children are only shown on ' .
494+ 'the first occurance">[Repeat]</em> ' :'' )),
495+ $ task ->getOwnerPHID () ? $ owner ->renderLink () : 'none assigned ' ,
496+ $ task ->getStatus (),
497+ );
498+ $ included [$ task ->getPHID ()] = $ task ->getPHID ();
499+
500+ if (isset ($ map [$ task ->getPHID ()]['children ' ])) {
501+ foreach ($ map [$ task ->getPHID ()]['children ' ] as $ child ) {
502+ $ child = $ this ->tasks [$ child ];
503+ $ this ->addTaskToTree ($ output , $ child , $ map , $ handles , $ depth +1 );
504+ }
505+ }
506+ }
507+
401508 /**
402509 * Format the Event data for display on the page.
403510 *
0 commit comments