PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/src/applications/herald/controller/transcript/HeraldTranscriptController.php

http://github.com/facebook/phabricator
PHP | 535 lines | 376 code | 80 blank | 79 comment | 53 complexity | a9f66a7e7fbbe6a7c48ef3d8f0ad8623 MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * Copyright 2012 Facebook, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. class HeraldTranscriptController extends HeraldController {
  18. const FILTER_AFFECTED = 'affected';
  19. const FILTER_OWNED = 'owned';
  20. const FILTER_ALL = 'all';
  21. private $id;
  22. private $filter;
  23. private $handles;
  24. public function getFilter() {
  25. return 'transcript';
  26. }
  27. public function willProcessRequest(array $data) {
  28. $this->id = $data['id'];
  29. $map = $this->getFilterMap();
  30. $this->filter = idx($data, 'filter');
  31. if (empty($map[$this->filter])) {
  32. $this->filter = self::FILTER_AFFECTED;
  33. }
  34. }
  35. public function processRequest() {
  36. $xscript = id(new HeraldTranscript())->load($this->id);
  37. if (!$xscript) {
  38. throw new Exception('Uknown transcript!');
  39. }
  40. require_celerity_resource('herald-test-css');
  41. $nav = $this->buildSideNav();
  42. $object_xscript = $xscript->getObjectTranscript();
  43. if (!$object_xscript) {
  44. $notice = id(new AphrontErrorView())
  45. ->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
  46. ->setTitle('Old Transcript')
  47. ->appendChild(
  48. '<p>Details of this transcript have been garbage collected.</p>');
  49. $nav->appendChild($notice);
  50. } else {
  51. $filter = $this->getFilterPHIDs();
  52. $this->filterTranscript($xscript, $filter);
  53. $phids = array_merge($filter, $this->getTranscriptPHIDs($xscript));
  54. $phids = array_unique($phids);
  55. $phids = array_filter($phids);
  56. $handles = id(new PhabricatorObjectHandleData($phids))
  57. ->loadHandles();
  58. $this->handles = $handles;
  59. $apply_xscript_panel = $this->buildApplyTranscriptPanel(
  60. $xscript);
  61. $nav->appendChild($apply_xscript_panel);
  62. $action_xscript_panel = $this->buildActionTranscriptPanel(
  63. $xscript);
  64. $nav->appendChild($action_xscript_panel);
  65. $object_xscript_panel = $this->buildObjectTranscriptPanel(
  66. $xscript);
  67. $nav->appendChild($object_xscript_panel);
  68. }
  69. /*
  70. TODO
  71. $notice = null;
  72. if ($xscript->getDryRun()) {
  73. $notice =
  74. <tools:notice title="Dry Run">
  75. This was a dry run to test Herald rules, no actions were executed.
  76. </tools:notice>;
  77. }
  78. */
  79. return $this->buildStandardPageResponse(
  80. $nav,
  81. array(
  82. 'title' => 'Transcript',
  83. ));
  84. }
  85. protected function renderConditionTestValue($condition, $handles) {
  86. $value = $condition->getTestValue();
  87. if (!is_scalar($value) && $value !== null) {
  88. foreach ($value as $key => $phid) {
  89. $handle = idx($handles, $phid);
  90. if ($handle) {
  91. $value[$key] = $handle->getName();
  92. } else {
  93. // This shouldn't ever really happen as we are supposed to have
  94. // grabbed handles for everything, but be super liberal in what
  95. // we accept here since we expect all sorts of weird issues as we
  96. // version the system.
  97. $value[$key] = 'Unknown Object #'.$phid;
  98. }
  99. }
  100. sort($value);
  101. $value = implode(', ', $value);
  102. }
  103. return
  104. '<span class="condition-test-value">'.
  105. phutil_escape_html($value).
  106. '</span>';
  107. }
  108. private function buildSideNav() {
  109. $nav = new AphrontSideNavView();
  110. $items = array();
  111. $filters = $this->getFilterMap();
  112. foreach ($filters as $key => $name) {
  113. $nav->addNavItem(
  114. phutil_render_tag(
  115. 'a',
  116. array(
  117. 'href' => '/herald/transcript/'.$this->id.'/'.$key.'/',
  118. 'class' =>
  119. ($key == $this->filter)
  120. ? 'aphront-side-nav-selected'
  121. : null,
  122. ),
  123. phutil_escape_html($name)));
  124. }
  125. return $nav;
  126. }
  127. protected function getFilterMap() {
  128. return array(
  129. self::FILTER_AFFECTED => 'Rules that Affected Me',
  130. self::FILTER_OWNED => 'Rules I Own',
  131. self::FILTER_ALL => 'All Rules',
  132. );
  133. }
  134. protected function getFilterPHIDs() {
  135. return array($this->getRequest()->getUser()->getPHID());
  136. /* TODO
  137. $viewer_id = $this->getRequest()->getUser()->getPHID();
  138. $fbids = array();
  139. if ($this->filter == self::FILTER_AFFECTED) {
  140. $fbids[] = $viewer_id;
  141. require_module_lazy('intern/subscriptions');
  142. $datastore = new SubscriberDatabaseStore();
  143. $lists = $datastore->getUserMailmanLists($viewer_id);
  144. foreach ($lists as $list) {
  145. $fbids[] = $list;
  146. }
  147. }
  148. return $fbids;
  149. */
  150. }
  151. protected function getTranscriptPHIDs($xscript) {
  152. $phids = array();
  153. $object_xscript = $xscript->getObjectTranscript();
  154. if (!$object_xscript) {
  155. return array();
  156. }
  157. $phids[] = $object_xscript->getPHID();
  158. foreach ($xscript->getApplyTranscripts() as $apply_xscript) {
  159. // TODO: This is total hacks. Add another amazing layer of abstraction.
  160. $target = (array)$apply_xscript->getTarget();
  161. foreach ($target as $phid) {
  162. if ($phid) {
  163. $phids[] = $phid;
  164. }
  165. }
  166. }
  167. foreach ($xscript->getRuleTranscripts() as $rule_xscript) {
  168. $phids[] = $rule_xscript->getRuleOwner();
  169. }
  170. $condition_xscripts = $xscript->getConditionTranscripts();
  171. if ($condition_xscripts) {
  172. $condition_xscripts = call_user_func_array(
  173. 'array_merge',
  174. $condition_xscripts);
  175. }
  176. foreach ($condition_xscripts as $condition_xscript) {
  177. $value = $condition_xscript->getTestValue();
  178. // TODO: Also total hacks.
  179. if (is_array($value)) {
  180. foreach ($value as $phid) {
  181. if ($phid) { // TODO: Probably need to make sure this "looks like" a
  182. // PHID or decrease the level of hacks here; this used
  183. // to be an is_numeric() check in Facebook land.
  184. $phids[] = $phid;
  185. }
  186. }
  187. }
  188. }
  189. return $phids;
  190. }
  191. protected function filterTranscript($xscript, $filter_phids) {
  192. $filter_owned = ($this->filter == self::FILTER_OWNED);
  193. $filter_affected = ($this->filter == self::FILTER_AFFECTED);
  194. if (!$filter_owned && !$filter_affected) {
  195. // No filtering to be done.
  196. return;
  197. }
  198. if (!$xscript->getObjectTranscript()) {
  199. return;
  200. }
  201. $user_phid = $this->getRequest()->getUser()->getPHID();
  202. $keep_apply_xscripts = array();
  203. $keep_rule_xscripts = array();
  204. $filter_phids = array_fill_keys($filter_phids, true);
  205. $rule_xscripts = $xscript->getRuleTranscripts();
  206. foreach ($xscript->getApplyTranscripts() as $id => $apply_xscript) {
  207. $rule_id = $apply_xscript->getRuleID();
  208. if ($filter_owned) {
  209. if (empty($rule_xscripts[$rule_id])) {
  210. // No associated rule so you can't own this effect.
  211. continue;
  212. }
  213. if ($rule_xscripts[$rule_id]->getRuleOwner() != $user_phid) {
  214. continue;
  215. }
  216. } else if ($filter_affected) {
  217. $targets = (array)$apply_xscript->getTarget();
  218. if (!array_select_keys($filter_phids, $targets)) {
  219. continue;
  220. }
  221. }
  222. $keep_apply_xscripts[$id] = true;
  223. if ($rule_id) {
  224. $keep_rule_xscripts[$rule_id] = true;
  225. }
  226. }
  227. foreach ($rule_xscripts as $rule_id => $rule_xscript) {
  228. if ($filter_owned && $rule_xscript->getRuleOwner() == $user_phid) {
  229. $keep_rule_xscripts[$rule_id] = true;
  230. }
  231. }
  232. $xscript->setRuleTranscripts(
  233. array_intersect_key(
  234. $xscript->getRuleTranscripts(),
  235. $keep_rule_xscripts));
  236. $xscript->setApplyTranscripts(
  237. array_intersect_key(
  238. $xscript->getApplyTranscripts(),
  239. $keep_apply_xscripts));
  240. $xscript->setConditionTranscripts(
  241. array_intersect_key(
  242. $xscript->getConditionTranscripts(),
  243. $keep_rule_xscripts));
  244. }
  245. private function buildApplyTranscriptPanel($xscript) {
  246. $handles = $this->handles;
  247. $action_names = HeraldActionConfig::getActionMessageMapForRuleType(
  248. HeraldRuleTypeConfig::RULE_TYPE_GLOBAL);
  249. $rows = array();
  250. foreach ($xscript->getApplyTranscripts() as $apply_xscript) {
  251. // TODO: Hacks, this is an approximate guess at the target type.
  252. $target = (array)$apply_xscript->getTarget();
  253. if (!$target) {
  254. if ($apply_xscript->getAction() == HeraldActionConfig::ACTION_NOTHING) {
  255. $target = '';
  256. } else {
  257. $target = '<empty>';
  258. }
  259. } else {
  260. foreach ($target as $k => $phid) {
  261. $target[$k] = $handles[$phid]->getName();
  262. }
  263. $target = implode("\n", $target);
  264. }
  265. $target = phutil_escape_html($target);
  266. if ($apply_xscript->getApplied()) {
  267. $outcome = '<span class="outcome-success">SUCCESS</span>';
  268. } else {
  269. $outcome = '<span class="outcome-failure">FAILURE</span>';
  270. }
  271. $outcome .= ' '.phutil_escape_html($apply_xscript->getAppliedReason());
  272. $rows[] = array(
  273. phutil_escape_html($action_names[$apply_xscript->getAction()]),
  274. $target,
  275. '<strong>Taken because:</strong> '.
  276. phutil_escape_html($apply_xscript->getReason()).
  277. '<br />'.
  278. '<strong>Outcome:</strong> '.$outcome,
  279. );
  280. }
  281. $table = new AphrontTableView($rows);
  282. $table->setNoDataString('No actions were taken.');
  283. $table->setHeaders(
  284. array(
  285. 'Action',
  286. 'Target',
  287. 'Details',
  288. ));
  289. $table->setColumnClasses(
  290. array(
  291. '',
  292. '',
  293. 'wide',
  294. ));
  295. $panel = new AphrontPanelView();
  296. $panel->setHeader('Actions Taken');
  297. $panel->appendChild($table);
  298. return $panel;
  299. }
  300. private function buildActionTranscriptPanel($xscript) {
  301. $action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID');
  302. $field_names = HeraldFieldConfig::getFieldMap();
  303. $condition_names = HeraldConditionConfig::getConditionMap();
  304. $handles = $this->handles;
  305. $rule_markup = array();
  306. foreach ($xscript->getRuleTranscripts() as $rule_id => $rule) {
  307. $cond_markup = array();
  308. foreach ($xscript->getConditionTranscriptsForRule($rule_id) as $cond) {
  309. if ($cond->getNote()) {
  310. $note =
  311. '<div class="herald-condition-note">'.
  312. phutil_escape_html($cond->getNote()).
  313. '</div>';
  314. } else {
  315. $note = null;
  316. }
  317. if ($cond->getResult()) {
  318. $result =
  319. '<span class="herald-outcome condition-pass">'.
  320. "\xE2\x9C\x93".
  321. '</span>';
  322. } else {
  323. $result =
  324. '<span class="herald-outcome condition-fail">'.
  325. "\xE2\x9C\x98".
  326. '</span>';
  327. }
  328. $cond_markup[] =
  329. '<li>'.
  330. $result.' Condition: '.
  331. phutil_escape_html($field_names[$cond->getFieldName()]).
  332. ' '.
  333. phutil_escape_html($condition_names[$cond->getCondition()]).
  334. ' '.
  335. $this->renderConditionTestValue($cond, $handles).
  336. $note.
  337. '</li>';
  338. }
  339. if ($rule->getResult()) {
  340. $result = '<span class="herald-outcome rule-pass">PASS</span>';
  341. $class = 'herald-rule-pass';
  342. } else {
  343. $result = '<span class="herald-outcome rule-fail">FAIL</span>';
  344. $class = 'herald-rule-fail';
  345. }
  346. $cond_markup[] =
  347. '<li>'.$result.' '.phutil_escape_html($rule->getReason()).'</li>';
  348. /*
  349. if ($rule->getResult()) {
  350. $actions = idx($action_xscript, $rule_id, array());
  351. if ($actions) {
  352. $cond_markup[] = <li><div class="action-header">Actions</div></li>;
  353. foreach ($actions as $action) {
  354. $target = $action->getTarget();
  355. if ($target) {
  356. foreach ((array)$target as $k => $phid) {
  357. $target[$k] = $handles[$phid]->getName();
  358. }
  359. $target = <strong>: {implode(', ', $target)}</strong>;
  360. }
  361. $cond_markup[] =
  362. <li>
  363. {$action_names[$action->getAction()]}
  364. {$target}
  365. </li>;
  366. }
  367. }
  368. }
  369. */
  370. $user_phid = $this->getRequest()->getUser()->getPHID();
  371. $name = $rule->getRuleName();
  372. if ($rule->getRuleOwner() == $user_phid) {
  373. // $name = <a href={"/herald/rule/".$rule->getRuleID()."/"}>{$name}</a>;
  374. }
  375. $rule_markup[] =
  376. phutil_render_tag(
  377. 'li',
  378. array(
  379. 'class' => $class,
  380. ),
  381. '<div class="rule-name">'.
  382. '<strong>'.phutil_escape_html($name).'</strong> '.
  383. phutil_escape_html($handles[$rule->getRuleOwner()]->getName()).
  384. '</div>'.
  385. '<ul>'.implode("\n", $cond_markup).'</ul>');
  386. }
  387. $panel = new AphrontPanelView();
  388. $panel->setHeader('Rule Details');
  389. $panel->appendChild(
  390. '<ul class="herald-explain-list">'.
  391. implode("\n", $rule_markup).
  392. '</ul>');
  393. return $panel;
  394. }
  395. private function buildObjectTranscriptPanel($xscript) {
  396. $field_names = HeraldFieldConfig::getFieldMap();
  397. $object_xscript = $xscript->getObjectTranscript();
  398. $data = array();
  399. if ($object_xscript) {
  400. $phid = $object_xscript->getPHID();
  401. $handles = id(new PhabricatorObjectHandleData(array($phid)))
  402. ->loadHandles();
  403. $data += array(
  404. 'Object Name' => $object_xscript->getName(),
  405. 'Object Type' => $object_xscript->getType(),
  406. 'Object PHID' => $phid,
  407. 'Object Link' => $handles[$phid]->renderLink(),
  408. );
  409. }
  410. $data += $xscript->getMetadataMap();
  411. if ($object_xscript) {
  412. foreach ($object_xscript->getFields() as $field => $value) {
  413. $field = idx($field_names, $field, '['.$field.'?]');
  414. $data['Field: '.$field] = $value;
  415. }
  416. }
  417. $rows = array();
  418. foreach ($data as $name => $value) {
  419. if (!is_scalar($value) && !is_null($value)) {
  420. $value = implode("\n", $value);
  421. }
  422. if (strlen($value) > 256) {
  423. $value = phutil_render_tag(
  424. 'textarea',
  425. array(
  426. 'class' => 'herald-field-value-transcript',
  427. ),
  428. phutil_escape_html($value));
  429. } else if ($name === 'Object Link') {
  430. // The link cannot be escaped
  431. } else {
  432. $value = phutil_escape_html($value);
  433. }
  434. $rows[] = array(
  435. phutil_escape_html($name),
  436. $value,
  437. );
  438. }
  439. $table = new AphrontTableView($rows);
  440. $table->setColumnClasses(
  441. array(
  442. 'header',
  443. 'wide',
  444. ));
  445. $panel = new AphrontPanelView();
  446. $panel->setHeader('Object Transcript');
  447. $panel->appendChild($table);
  448. return $panel;
  449. }
  450. }