[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 }
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 |