[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/herald/controller/ -> HeraldTranscriptController.php (source)

   1  <?php
   2  
   3  final class HeraldTranscriptController extends HeraldController {
   4  
   5    const FILTER_AFFECTED = 'affected';
   6    const FILTER_OWNED    = 'owned';
   7    const FILTER_ALL      = 'all';
   8  
   9    private $id;
  10    private $filter;
  11    private $handles;
  12    private $adapter;
  13  
  14    public function willProcessRequest(array $data) {
  15      $this->id = $data['id'];
  16      $map = $this->getFilterMap();
  17      $this->filter = idx($data, 'filter');
  18      if (empty($map[$this->filter])) {
  19        $this->filter = self::FILTER_ALL;
  20      }
  21    }
  22  
  23    private function getAdapter() {
  24      return $this->adapter;
  25    }
  26  
  27    public function processRequest() {
  28      $request = $this->getRequest();
  29      $viewer = $request->getUser();
  30  
  31      $xscript = id(new HeraldTranscriptQuery())
  32        ->setViewer($viewer)
  33        ->withIDs(array($this->id))
  34        ->executeOne();
  35      if (!$xscript) {
  36        return new Aphront404Response();
  37      }
  38  
  39      require_celerity_resource('herald-test-css');
  40  
  41      $nav = $this->buildSideNav();
  42  
  43      $object_xscript = $xscript->getObjectTranscript();
  44      if (!$object_xscript) {
  45        $notice = id(new AphrontErrorView())
  46          ->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
  47          ->setTitle(pht('Old Transcript'))
  48          ->appendChild(phutil_tag(
  49            'p',
  50            array(),
  51            pht('Details of this transcript have been garbage collected.')));
  52        $nav->appendChild($notice);
  53      } else {
  54        $map = HeraldAdapter::getEnabledAdapterMap($viewer);
  55        $object_type = $object_xscript->getType();
  56        if (empty($map[$object_type])) {
  57          // TODO: We should filter these out in the Query, but we have to load
  58          // the objectTranscript right now, which is potentially enormous. We
  59          // should denormalize the object type, or move the data into a separate
  60          // table, and then filter this earlier (and thus raise a better error).
  61          // For now, just block access so we don't violate policies.
  62          throw new Exception(
  63            pht('This transcript has an invalid or inaccessible adapter.'));
  64        }
  65  
  66        $this->adapter = HeraldAdapter::getAdapterForContentType($object_type);
  67  
  68        $filter = $this->getFilterPHIDs();
  69        $this->filterTranscript($xscript, $filter);
  70        $phids = array_merge($filter, $this->getTranscriptPHIDs($xscript));
  71        $phids = array_unique($phids);
  72        $phids = array_filter($phids);
  73  
  74        $handles = $this->loadViewerHandles($phids);
  75        $this->handles = $handles;
  76  
  77        if ($xscript->getDryRun()) {
  78          $notice = new AphrontErrorView();
  79          $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
  80          $notice->setTitle(pht('Dry Run'));
  81          $notice->appendChild(pht('This was a dry run to test Herald '.
  82            'rules, no actions were executed.'));
  83          $nav->appendChild($notice);
  84        }
  85  
  86        $warning_panel = $this->buildWarningPanel($xscript);
  87        $nav->appendChild($warning_panel);
  88  
  89        $apply_xscript_panel = $this->buildApplyTranscriptPanel(
  90          $xscript);
  91        $nav->appendChild($apply_xscript_panel);
  92  
  93        $action_xscript_panel = $this->buildActionTranscriptPanel(
  94          $xscript);
  95        $nav->appendChild($action_xscript_panel);
  96  
  97        $object_xscript_panel = $this->buildObjectTranscriptPanel(
  98          $xscript);
  99        $nav->appendChild($object_xscript_panel);
 100      }
 101  
 102      $crumbs = id($this->buildApplicationCrumbs())
 103        ->addTextCrumb(
 104          pht('Transcripts'),
 105          $this->getApplicationURI('/transcript/'))
 106        ->addTextCrumb($xscript->getID());
 107      $nav->setCrumbs($crumbs);
 108  
 109      return $this->buildApplicationPage(
 110        $nav,
 111        array(
 112          'title' => pht('Transcript'),
 113        ));
 114    }
 115  
 116    protected function renderConditionTestValue($condition, $handles) {
 117      switch ($condition->getFieldName()) {
 118        case HeraldAdapter::FIELD_RULE:
 119          $value = array($condition->getTestValue());
 120          break;
 121        default:
 122          $value = $condition->getTestValue();
 123          break;
 124      }
 125  
 126      if (!is_scalar($value) && $value !== null) {
 127        foreach ($value as $key => $phid) {
 128          $handle = idx($handles, $phid);
 129          if ($handle) {
 130            $value[$key] = $handle->getName();
 131          } else {
 132            // This shouldn't ever really happen as we are supposed to have
 133            // grabbed handles for everything, but be super liberal in what
 134            // we accept here since we expect all sorts of weird issues as we
 135            // version the system.
 136            $value[$key] = 'Unknown Object #'.$phid;
 137          }
 138        }
 139        sort($value);
 140        $value = implode(', ', $value);
 141      }
 142  
 143      return phutil_tag('span', array('class' => 'condition-test-value'), $value);
 144    }
 145  
 146    private function buildSideNav() {
 147      $nav = new AphrontSideNavFilterView();
 148      $nav->setBaseURI(new PhutilURI('/herald/transcript/'.$this->id.'/'));
 149  
 150      $items = array();
 151      $filters = $this->getFilterMap();
 152      foreach ($filters as $key => $name) {
 153        $nav->addFilter($key, $name);
 154      }
 155      $nav->selectFilter($this->filter, null);
 156  
 157      return $nav;
 158    }
 159  
 160    protected function getFilterMap() {
 161      return array(
 162        self::FILTER_ALL      => pht('All Rules'),
 163        self::FILTER_OWNED    => pht('Rules I Own'),
 164        self::FILTER_AFFECTED => pht('Rules that Affected Me'),
 165      );
 166    }
 167  
 168  
 169    protected function getFilterPHIDs() {
 170      return array($this->getRequest()->getUser()->getPHID());
 171    }
 172  
 173    protected function getTranscriptPHIDs($xscript) {
 174      $phids = array();
 175  
 176      $object_xscript = $xscript->getObjectTranscript();
 177      if (!$object_xscript) {
 178        return array();
 179      }
 180  
 181      $phids[] = $object_xscript->getPHID();
 182  
 183      foreach ($xscript->getApplyTranscripts() as $apply_xscript) {
 184        // TODO: This is total hacks. Add another amazing layer of abstraction.
 185        $target = (array)$apply_xscript->getTarget();
 186        foreach ($target as $phid) {
 187          if ($phid) {
 188            $phids[] = $phid;
 189          }
 190        }
 191      }
 192  
 193      foreach ($xscript->getRuleTranscripts() as $rule_xscript) {
 194        $phids[] = $rule_xscript->getRuleOwner();
 195      }
 196  
 197      $condition_xscripts = $xscript->getConditionTranscripts();
 198      if ($condition_xscripts) {
 199        $condition_xscripts = call_user_func_array(
 200          'array_merge',
 201          $condition_xscripts);
 202      }
 203      foreach ($condition_xscripts as $condition_xscript) {
 204        switch ($condition_xscript->getFieldName()) {
 205          case HeraldAdapter::FIELD_RULE:
 206            $phids[] = $condition_xscript->getTestValue();
 207            break;
 208          default:
 209            $value = $condition_xscript->getTestValue();
 210            // TODO: Also total hacks.
 211            if (is_array($value)) {
 212              foreach ($value as $phid) {
 213                if ($phid) { // TODO: Probably need to make sure this
 214                  // "looks like" a PHID or decrease the level of hacks here;
 215                  // this used to be an is_numeric() check in Facebook land.
 216                  $phids[] = $phid;
 217                }
 218              }
 219            }
 220            break;
 221        }
 222      }
 223  
 224      return $phids;
 225    }
 226  
 227    protected function filterTranscript($xscript, $filter_phids) {
 228      $filter_owned = ($this->filter == self::FILTER_OWNED);
 229      $filter_affected = ($this->filter == self::FILTER_AFFECTED);
 230  
 231      if (!$filter_owned && !$filter_affected) {
 232        // No filtering to be done.
 233        return;
 234      }
 235  
 236      if (!$xscript->getObjectTranscript()) {
 237        return;
 238      }
 239  
 240      $user_phid = $this->getRequest()->getUser()->getPHID();
 241  
 242      $keep_apply_xscripts = array();
 243      $keep_rule_xscripts  = array();
 244  
 245      $filter_phids = array_fill_keys($filter_phids, true);
 246  
 247      $rule_xscripts = $xscript->getRuleTranscripts();
 248      foreach ($xscript->getApplyTranscripts() as $id => $apply_xscript) {
 249        $rule_id = $apply_xscript->getRuleID();
 250        if ($filter_owned) {
 251          if (empty($rule_xscripts[$rule_id])) {
 252            // No associated rule so you can't own this effect.
 253            continue;
 254          }
 255          if ($rule_xscripts[$rule_id]->getRuleOwner() != $user_phid) {
 256            continue;
 257          }
 258        } else if ($filter_affected) {
 259          $targets = (array)$apply_xscript->getTarget();
 260          if (!array_select_keys($filter_phids, $targets)) {
 261            continue;
 262          }
 263        }
 264        $keep_apply_xscripts[$id] = true;
 265        if ($rule_id) {
 266          $keep_rule_xscripts[$rule_id] = true;
 267        }
 268      }
 269  
 270      foreach ($rule_xscripts as $rule_id => $rule_xscript) {
 271        if ($filter_owned && $rule_xscript->getRuleOwner() == $user_phid) {
 272          $keep_rule_xscripts[$rule_id] = true;
 273        }
 274      }
 275  
 276      $xscript->setRuleTranscripts(
 277        array_intersect_key(
 278          $xscript->getRuleTranscripts(),
 279          $keep_rule_xscripts));
 280  
 281      $xscript->setApplyTranscripts(
 282        array_intersect_key(
 283          $xscript->getApplyTranscripts(),
 284          $keep_apply_xscripts));
 285  
 286      $xscript->setConditionTranscripts(
 287        array_intersect_key(
 288          $xscript->getConditionTranscripts(),
 289          $keep_rule_xscripts));
 290    }
 291  
 292    private function buildWarningPanel(HeraldTranscript $xscript) {
 293      $request = $this->getRequest();
 294      $panel = null;
 295      if ($xscript->getObjectTranscript()) {
 296        $handles = $this->handles;
 297        $object_xscript = $xscript->getObjectTranscript();
 298        $handle = $handles[$object_xscript->getPHID()];
 299        if ($handle->getType() ==
 300            PhabricatorRepositoryCommitPHIDType::TYPECONST) {
 301          $commit = id(new DiffusionCommitQuery())
 302            ->setViewer($request->getUser())
 303            ->withPHIDs(array($handle->getPHID()))
 304            ->executeOne();
 305          if ($commit) {
 306            $repository = $commit->getRepository();
 307            if ($repository->isImporting()) {
 308              $title = pht(
 309                'The %s repository is still importing.',
 310                $repository->getMonogram());
 311              $body = pht(
 312                'Herald rules will not trigger until import completes.');
 313            } else if (!$repository->isTracked()) {
 314              $title = pht(
 315                'The %s repository is not tracked.',
 316                $repository->getMonogram());
 317              $body = pht(
 318                'Herald rules will not trigger until tracking is enabled.');
 319            } else {
 320              return $panel;
 321            }
 322            $panel = id(new AphrontErrorView())
 323              ->setSeverity(AphrontErrorView::SEVERITY_WARNING)
 324              ->setTitle($title)
 325              ->appendChild($body);
 326          }
 327        }
 328      }
 329      return $panel;
 330    }
 331  
 332    private function buildApplyTranscriptPanel(HeraldTranscript $xscript) {
 333      $handles = $this->handles;
 334      $adapter = $this->getAdapter();
 335  
 336      $rule_type_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL;
 337      $action_names = $adapter->getActionNameMap($rule_type_global);
 338  
 339      $list = new PHUIObjectItemListView();
 340      $list->setStates(true);
 341      $list->setNoDataString(pht('No actions were taken.'));
 342      foreach ($xscript->getApplyTranscripts() as $apply_xscript) {
 343  
 344        $target = $apply_xscript->getTarget();
 345        switch ($apply_xscript->getAction()) {
 346          case HeraldAdapter::ACTION_NOTHING:
 347            $target = null;
 348            break;
 349          case HeraldAdapter::ACTION_FLAG:
 350            $target = PhabricatorFlagColor::getColorName($target);
 351            break;
 352          case HeraldAdapter::ACTION_BLOCK:
 353            // Target is a text string.
 354            $target = $target;
 355            break;
 356          default:
 357            if (is_array($target) && $target) {
 358              foreach ($target as $k => $phid) {
 359                if (isset($handles[$phid])) {
 360                  $target[$k] = $handles[$phid]->getName();
 361                }
 362              }
 363              $target = implode(', ', $target);
 364            } else if (is_string($target)) {
 365              $target = $target;
 366            } else {
 367              $target = '<empty>';
 368            }
 369            break;
 370        }
 371  
 372        $item = new PHUIObjectItemView();
 373  
 374        if ($apply_xscript->getApplied()) {
 375          $item->setState(PHUIObjectItemView::STATE_SUCCESS);
 376        } else {
 377          $item->setState(PHUIObjectItemView::STATE_FAIL);
 378        }
 379  
 380        $rule = idx($action_names, $apply_xscript->getAction(), pht('Unknown'));
 381  
 382        $item->setHeader(pht('%s: %s', $rule, $target));
 383        $item->addAttribute($apply_xscript->getReason());
 384        $item->addAttribute(
 385          pht('Outcome: %s', $apply_xscript->getAppliedReason()));
 386  
 387        $list->addItem($item);
 388      }
 389  
 390      $box = new PHUIObjectBoxView();
 391      $box->setHeaderText(pht('Actions Taken'));
 392      $box->appendChild($list);
 393  
 394      return $box;
 395    }
 396  
 397    private function buildActionTranscriptPanel(HeraldTranscript $xscript) {
 398      $action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID');
 399  
 400      $adapter = $this->getAdapter();
 401  
 402  
 403      $field_names = $adapter->getFieldNameMap();
 404      $condition_names = $adapter->getConditionNameMap();
 405  
 406      $handles = $this->handles;
 407  
 408      $rule_markup = array();
 409      foreach ($xscript->getRuleTranscripts() as $rule_id => $rule) {
 410        $cond_markup = array();
 411        foreach ($xscript->getConditionTranscriptsForRule($rule_id) as $cond) {
 412          if ($cond->getNote()) {
 413            $note = phutil_tag_div('herald-condition-note', $cond->getNote());
 414          } else {
 415            $note = null;
 416          }
 417  
 418          if ($cond->getResult()) {
 419            $result = phutil_tag(
 420              'span',
 421              array('class' => 'herald-outcome condition-pass'),
 422              "\xE2\x9C\x93");
 423          } else {
 424            $result = phutil_tag(
 425              'span',
 426              array('class' => 'herald-outcome condition-fail'),
 427              "\xE2\x9C\x98");
 428          }
 429  
 430          $cond_markup[] = phutil_tag(
 431            'li',
 432            array(),
 433            pht(
 434              '%s Condition: %s %s %s%s',
 435              $result,
 436              idx($field_names, $cond->getFieldName(), pht('Unknown')),
 437              idx($condition_names, $cond->getCondition(), pht('Unknown')),
 438              $this->renderConditionTestValue($cond, $handles),
 439              $note));
 440        }
 441  
 442        if ($rule->getResult()) {
 443          $result = phutil_tag(
 444            'span',
 445            array('class' => 'herald-outcome rule-pass'),
 446            pht('PASS'));
 447          $class = 'herald-rule-pass';
 448        } else {
 449          $result = phutil_tag(
 450            'span',
 451            array('class' => 'herald-outcome rule-fail'),
 452            pht('FAIL'));
 453          $class = 'herald-rule-fail';
 454        }
 455  
 456        $cond_markup[] = phutil_tag(
 457          'li',
 458          array(),
 459          array($result, $rule->getReason()));
 460        $user_phid = $this->getRequest()->getUser()->getPHID();
 461  
 462        $name = $rule->getRuleName();
 463  
 464        $rule_markup[] =
 465          phutil_tag(
 466            'li',
 467            array(
 468              'class' => $class,
 469            ),
 470            phutil_tag_div('rule-name', array(
 471              phutil_tag('strong', array(), $name),
 472              ' ',
 473              phutil_tag('ul', array(), $cond_markup),
 474            )));
 475      }
 476  
 477      $box = null;
 478      if ($rule_markup) {
 479        $box = new PHUIObjectBoxView();
 480        $box->setHeaderText(pht('Rule Details'));
 481        $box->appendChild(phutil_tag(
 482          'ul',
 483          array('class' => 'herald-explain-list'),
 484          $rule_markup));
 485      }
 486      return $box;
 487    }
 488  
 489    private function buildObjectTranscriptPanel(HeraldTranscript $xscript) {
 490  
 491      $adapter = $this->getAdapter();
 492      $field_names = $adapter->getFieldNameMap();
 493  
 494      $object_xscript = $xscript->getObjectTranscript();
 495  
 496      $data = array();
 497      if ($object_xscript) {
 498        $phid = $object_xscript->getPHID();
 499        $handles = $this->handles;
 500  
 501        $data += array(
 502          pht('Object Name') => $object_xscript->getName(),
 503          pht('Object Type') => $object_xscript->getType(),
 504          pht('Object PHID') => $phid,
 505          pht('Object Link') => $handles[$phid]->renderLink(),
 506        );
 507      }
 508  
 509      $data += $xscript->getMetadataMap();
 510  
 511      if ($object_xscript) {
 512        foreach ($object_xscript->getFields() as $field => $value) {
 513          $field = idx($field_names, $field, '['.$field.'?]');
 514          $data['Field: '.$field] = $value;
 515        }
 516      }
 517  
 518      $rows = array();
 519      foreach ($data as $name => $value) {
 520        if (!($value instanceof PhutilSafeHTML)) {
 521          if (!is_scalar($value) && !is_null($value)) {
 522            $value = implode("\n", $value);
 523          }
 524  
 525          if (strlen($value) > 256) {
 526            $value = phutil_tag(
 527              'textarea',
 528              array(
 529                'class' => 'herald-field-value-transcript',
 530              ),
 531              $value);
 532          }
 533        }
 534  
 535        $rows[] = array($name, $value);
 536      }
 537  
 538      $property_list = new PHUIPropertyListView();
 539      $property_list->setStacked(true);
 540      foreach ($rows as $row) {
 541        $property_list->addProperty($row[0], $row[1]);
 542      }
 543  
 544      $box = new PHUIObjectBoxView();
 545      $box->setHeaderText(pht('Object Transcript'));
 546      $box->appendChild($property_list);
 547  
 548      return $box;
 549    }
 550  
 551  
 552  }


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