[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/notification/builder/ -> PhabricatorNotificationBuilder.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1