[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class PhabricatorNotificationBuilder { 4 5 private $stories; 6 private $user = null; 7 8 public function __construct(array $stories) { 9 $this->stories = $stories; 10 } 11 12 public function setUser($user) { 13 $this->user = $user; 14 return $this; 15 } 16 17 public function buildView() { 18 19 $stories = $this->stories; 20 $stories = mpull($stories, null, 'getChronologicalKey'); 21 22 // Aggregate notifications. Generally, we can aggregate notifications only 23 // by object, e.g. "a updated T123" and "b updated T123" can become 24 // "a and b updated T123", but we can't combine "a updated T123" and 25 // "a updated T234" into "a updated T123 and T234" because there would be 26 // nowhere sensible for the notification to link to, and no reasonable way 27 // to unambiguously clear it. 28 29 // Each notification emits keys it can aggregate on. For instance, if this 30 // story is "a updated T123", it might emit a key like this: 31 // 32 // task:phid123:unread => PhabricatorFeedStoryManiphestAggregate 33 // 34 // All the unread notifications about the task with PHID "phid123" will 35 // emit the same key, telling us we can aggregate them into a single 36 // story of type "PhabricatorFeedStoryManiphestAggregate", which could 37 // read like "a and b updated T123". 38 // 39 // A story might be able to aggregate in multiple ways. Although this is 40 // unlikely for stories in a notification context, stories in a feed context 41 // can also aggregate by actor: 42 // 43 // task:phid123 => PhabricatorFeedStoryManiphestAggregate 44 // actor:user123 => PhabricatorFeedStoryActorAggregate 45 // 46 // This means the story can either become "a and b updated T123" or 47 // "a updated T123 and T456". When faced with multiple possibilities, it's 48 // our job to choose the best aggregation. 49 // 50 // For now, we use a simple greedy algorithm and repeatedly select the 51 // aggregate story which consumes the largest number of individual stories 52 // until no aggregate story exists that consumes more than one story. 53 54 55 // Build up a map of all the possible aggregations. 56 57 $chronokey_map = array(); 58 $aggregation_map = array(); 59 $agg_types = array(); 60 foreach ($stories as $chronokey => $story) { 61 $chronokey_map[$chronokey] = $story->getNotificationAggregations(); 62 foreach ($chronokey_map[$chronokey] as $key => $type) { 63 $agg_types[$key] = $type; 64 $aggregation_map[$key]['keys'][$chronokey] = true; 65 } 66 } 67 68 // Repeatedly select the largest available aggregation until none remain. 69 70 $aggregated_stories = array(); 71 while ($aggregation_map) { 72 73 // Count the size of each aggregation, removing any which will consume 74 // fewer than 2 stories. 75 76 foreach ($aggregation_map as $key => $dict) { 77 $size = count($dict['keys']); 78 if ($size > 1) { 79 $aggregation_map[$key]['size'] = $size; 80 } else { 81 unset($aggregation_map[$key]); 82 } 83 } 84 85 // If we're out of aggregations, break out. 86 87 if (!$aggregation_map) { 88 break; 89 } 90 91 // Select the aggregation we're going to make, and remove it from the 92 // map. 93 94 $aggregation_map = isort($aggregation_map, 'size'); 95 $agg_info = idx(last($aggregation_map), 'keys'); 96 $agg_key = last_key($aggregation_map); 97 unset($aggregation_map[$agg_key]); 98 99 // Select all the stories it aggregates, and remove them from the master 100 // list of stories and from all other possible aggregations. 101 102 $sub_stories = array(); 103 foreach ($agg_info as $chronokey => $ignored) { 104 $sub_stories[$chronokey] = $stories[$chronokey]; 105 unset($stories[$chronokey]); 106 foreach ($chronokey_map[$chronokey] as $key => $type) { 107 unset($aggregation_map[$key]['keys'][$chronokey]); 108 } 109 unset($chronokey_map[$chronokey]); 110 } 111 112 // Build the aggregate story. 113 114 krsort($sub_stories); 115 $story_class = $agg_types[$agg_key]; 116 $conv = array(head($sub_stories)->getStoryData()); 117 118 $new_story = newv($story_class, $conv); 119 $new_story->setAggregateStories($sub_stories); 120 $aggregated_stories[] = $new_story; 121 } 122 123 // Combine the aggregate stories back into the list of stories. 124 125 $stories = array_merge($stories, $aggregated_stories); 126 $stories = mpull($stories, null, 'getChronologicalKey'); 127 krsort($stories); 128 129 $null_view = new AphrontNullView(); 130 131 foreach ($stories as $story) { 132 try { 133 $view = $story->renderView(); 134 } catch (Exception $ex) { 135 // TODO: Render a nice debuggable notice instead? 136 continue; 137 } 138 $null_view->appendChild($view->renderNotification($this->user)); 139 } 140 141 return $null_view; 142 } 143 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |