@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Policy Transactions - add a details view for custom policy

Summary: 'cuz those can be complicated. Fixes T4738. I needed to do a fair amount of heavy lifting to get the policy stuff rendering correctly. For now, I made this end point very one purpose and tried to make that clear.

Test Plan: looked at some custom policies. see screenshots.

Reviewers: chad, epriestley

Reviewed By: epriestley

Subscribers: epriestley, Korvin

Maniphest Tasks: T4738

Differential Revision: https://secure.phabricator.com/D8890

+238 -23
+2
resources/celerity/map.php
··· 87 87 'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad', 88 88 'rsrc/css/application/phriction/phriction-document-css.css' => '7d7f0071', 89 89 'rsrc/css/application/policy/policy-edit.css' => '05cca26a', 90 + 'rsrc/css/application/policy/policy-transaction-detail.css' => '21b7daba', 90 91 'rsrc/css/application/policy/policy.css' => '957ea14c', 91 92 'rsrc/css/application/ponder/comments.css' => '6cdccea7', 92 93 'rsrc/css/application/ponder/feed.css' => 'e62615b6', ··· 771 772 'phui-workpanel-view-css' => '97b69459', 772 773 'policy-css' => '957ea14c', 773 774 'policy-edit-css' => '05cca26a', 775 + 'policy-transaction-detail-css' => '21b7daba', 774 776 'ponder-comment-table-css' => '6cdccea7', 775 777 'ponder-feed-view-css' => 'e62615b6', 776 778 'ponder-post-css' => 'ebab8a70',
+2
src/__phutil_library_map__.php
··· 1176 1176 'PhabricatorApplicationTransactionTextDiffDetailView' => 'applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php', 1177 1177 'PhabricatorApplicationTransactionValidationError' => 'applications/transactions/error/PhabricatorApplicationTransactionValidationError.php', 1178 1178 'PhabricatorApplicationTransactionValidationException' => 'applications/transactions/exception/PhabricatorApplicationTransactionValidationException.php', 1179 + 'PhabricatorApplicationTransactionValueController' => 'applications/transactions/controller/PhabricatorApplicationTransactionValueController.php', 1179 1180 'PhabricatorApplicationTransactionView' => 'applications/transactions/view/PhabricatorApplicationTransactionView.php', 1180 1181 'PhabricatorApplicationTransactions' => 'applications/transactions/application/PhabricatorApplicationTransactions.php', 1181 1182 'PhabricatorApplicationTypeahead' => 'applications/typeahead/application/PhabricatorApplicationTypeahead.php', ··· 3917 3918 'PhabricatorApplicationTransactionTextDiffDetailView' => 'AphrontView', 3918 3919 'PhabricatorApplicationTransactionValidationError' => 'Phobject', 3919 3920 'PhabricatorApplicationTransactionValidationException' => 'Exception', 3921 + 'PhabricatorApplicationTransactionValueController' => 'PhabricatorApplicationTransactionController', 3920 3922 'PhabricatorApplicationTransactionView' => 'AphrontView', 3921 3923 'PhabricatorApplicationTransactions' => 'PhabricatorApplication', 3922 3924 'PhabricatorApplicationTypeahead' => 'PhabricatorApplication',
+22
src/applications/policy/rule/PhabricatorPolicyRule.php
··· 34 34 return $value; 35 35 } 36 36 37 + public function getRequiredHandlePHIDsForSummary($value) { 38 + $phids = array(); 39 + switch ($this->getValueControlType()) { 40 + case self::CONTROL_TYPE_TOKENIZER: 41 + $phids = $value; 42 + break; 43 + case self::CONTROL_TYPE_TEXT: 44 + case self::CONTROL_TYPE_SELECT: 45 + case self::CONTROL_TYPE_NONE: 46 + default: 47 + if (phid_get_type($value) != 48 + PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { 49 + $phids = array($value); 50 + } else { 51 + $phids = array(); 52 + } 53 + break; 54 + } 55 + 56 + return $phids; 57 + } 58 + 37 59 /** 38 60 * Return true if the given value creates a rule with a meaningful effect. 39 61 * An example of a rule with no meaningful effect is a "users" rule with no
+13 -2
src/applications/policy/storage/PhabricatorPolicy.php
··· 11 11 private $shortName; 12 12 private $type; 13 13 private $href; 14 + private $workflow; 14 15 private $icon; 15 16 16 17 protected $rules = array(); ··· 132 133 return $this->href; 133 134 } 134 135 136 + public function setWorkflow($workflow) { 137 + $this->workflow = $workflow; 138 + return $this; 139 + } 140 + 141 + public function getWorkflow() { 142 + return $this->workflow; 143 + } 144 + 135 145 public function getIcon() { 136 146 switch ($this->getType()) { 137 147 case PhabricatorPolicyType::TYPE_GLOBAL: ··· 234 244 } 235 245 236 246 if ($this->getHref()) { 237 - $desc = phutil_tag( 247 + $desc = javelin_tag( 238 248 'a', 239 249 array( 240 250 'href' => $this->getHref(), 241 251 'class' => 'policy-link', 252 + 'sigil' => $this->getWorkflow() ? 'workflow' : null, 242 253 ), 243 254 array( 244 255 $img, ··· 256 267 case PhabricatorPolicyType::TYPE_PROJECT: 257 268 return pht('%s (Project)', $desc); 258 269 case PhabricatorPolicyType::TYPE_CUSTOM: 259 - return pht('Custom Policy'); 270 + return $desc; 260 271 case PhabricatorPolicyType::TYPE_MASKED: 261 272 return pht( 262 273 '%s (You do not have permission to view policy details.)',
+2
src/applications/transactions/application/PhabricatorApplicationTransactions.php
··· 19 19 => 'PhabricatorApplicationTransactionCommentHistoryController', 20 20 'detail/(?<phid>[^/]+)/' 21 21 => 'PhabricatorApplicationTransactionDetailController', 22 + '(?P<value>old|new)/(?<phid>[^/]+)/' 23 + => 'PhabricatorApplicationTransactionValueController', 22 24 ), 23 25 ); 24 26 }
+21
src/applications/transactions/controller/PhabricatorApplicationTransactionController.php
··· 3 3 abstract class PhabricatorApplicationTransactionController 4 4 extends PhabricatorController { 5 5 6 + protected function guessCancelURI( 7 + PhabricatorUser $viewer, 8 + PhabricatorApplicationTransaction $xaction) { 9 + 10 + // Take an educated guess at the URI where the transactions appear so we 11 + // can send the cancel button somewhere sensible. This won't always get the 12 + // best answer (for example, Diffusion's history is visible on a page other 13 + // than the main object view page) but should always get a reasonable one. 14 + 15 + $cancel_uri = '/'; 16 + $handle = id(new PhabricatorHandleQuery()) 17 + ->setViewer($viewer) 18 + ->withPHIDs(array($xaction->getObjectPHID())) 19 + ->executeOne(); 20 + if ($handle) { 21 + $cancel_uri = $handle->getURI(); 22 + } 23 + 24 + return $cancel_uri; 25 + } 26 + 6 27 }
+1 -14
src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php
··· 23 23 24 24 $details = $xaction->renderChangeDetails($viewer); 25 25 26 - // Take an educated guess at the URI where the transactions appear so we 27 - // can send the cancel button somewhere sensible. This won't always get the 28 - // best answer (for example, Diffusion's history is visible on a page other 29 - // than the main object view page) but should always get a reasonable one. 30 - 31 - $cancel_uri = '/'; 32 - $handle = id(new PhabricatorHandleQuery()) 33 - ->setViewer($viewer) 34 - ->withPHIDs(array($xaction->getObjectPHID())) 35 - ->executeOne(); 36 - if ($handle) { 37 - $cancel_uri = $handle->getURI(); 38 - } 39 - 26 + $cancel_uri = $this->guessCancelURI($viewer, $xaction); 40 27 $dialog = id(new AphrontDialogView()) 41 28 ->setUser($viewer) 42 29 ->setTitle(pht('Change Details'))
+141
src/applications/transactions/controller/PhabricatorApplicationTransactionValueController.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationTransactionValueController 4 + extends PhabricatorApplicationTransactionController { 5 + 6 + private $value; 7 + private $phid; 8 + 9 + public function willProcessRequest(array $data) { 10 + $this->phid = $data['phid']; 11 + $this->value = $data['value']; 12 + } 13 + 14 + public function processRequest() { 15 + $request = $this->getRequest(); 16 + $viewer = $request->getUser(); 17 + 18 + $xaction = id(new PhabricatorObjectQuery()) 19 + ->setViewer($viewer) 20 + ->withPHIDs(array($this->phid)) 21 + ->executeOne(); 22 + if (!$xaction) { 23 + return new Aphront404Response(); 24 + } 25 + 26 + // For now, this pathway only supports policy transactions 27 + // to show the details of custom policies. If / when this pathway 28 + // supports more transaction types, rendering coding should be moved 29 + // into PhabricatorTransactions e.g. feed rendering code. 30 + switch ($xaction->getTransactionType()) { 31 + case PhabricatorTransactions::TYPE_VIEW_POLICY: 32 + case PhabricatorTransactions::TYPE_EDIT_POLICY: 33 + case PhabricatorTransactions::TYPE_JOIN_POLICY: 34 + break; 35 + default: 36 + return new Aphront404Response(); 37 + break; 38 + } 39 + 40 + if ($this->value == 'old') { 41 + $value = $xaction->getOldValue(); 42 + } else { 43 + $value = $xaction->getNewValue(); 44 + } 45 + $policy = id(new PhabricatorPolicyQuery()) 46 + ->setViewer($viewer) 47 + ->withPHIDs(array($value)) 48 + ->executeOne(); 49 + if (!$policy) { 50 + return new Aphront404Response(); 51 + } 52 + if ($policy->getType() != PhabricatorPolicyType::TYPE_CUSTOM) { 53 + return new Aphront404Response(); 54 + } 55 + 56 + $rule_objects = array(); 57 + foreach ($policy->getCustomRuleClasses() as $class) { 58 + $rule_objects[$class] = newv($class, array()); 59 + } 60 + $policy->attachRuleObjects($rule_objects); 61 + $handle_phids = $this->extractPHIDs($policy, $rule_objects); 62 + $handles = $this->loadHandles($handle_phids); 63 + 64 + $this->requireResource('policy-transaction-detail-css'); 65 + $cancel_uri = $this->guessCancelURI($viewer, $xaction); 66 + $dialog = id(new AphrontDialogView()) 67 + ->setUser($viewer) 68 + ->setTitle($policy->getFullName()) 69 + ->setWidth(AphrontDialogView::WIDTH_FORM) 70 + ->appendChild( 71 + $this->renderPolicyDetails($policy, $rule_objects)) 72 + ->addCancelButton($cancel_uri, pht('Close')); 73 + 74 + return id(new AphrontDialogResponse())->setDialog($dialog); 75 + } 76 + 77 + private function extractPHIDs( 78 + PhabricatorPolicy $policy, 79 + array $rule_objects) { 80 + 81 + $phids = array(); 82 + foreach ($policy->getRules() as $rule) { 83 + $rule_object = $rule_objects[$rule['rule']]; 84 + $phids[] = 85 + $rule_object->getRequiredHandlePHIDsForSummary($rule['value']); 86 + } 87 + return array_filter(array_mergev($phids)); 88 + } 89 + 90 + private function renderPolicyDetails( 91 + PhabricatorPolicy $policy, 92 + array $rule_objects) { 93 + $details = array(); 94 + $details[] = phutil_tag( 95 + 'p', 96 + array( 97 + 'class' => 'policy-transaction-detail-intro' 98 + ), 99 + pht('These rules are processed in order:')); 100 + 101 + foreach ($policy->getRules() as $index => $rule) { 102 + $rule_object = $rule_objects[$rule['rule']]; 103 + if ($rule['action'] == 'allow') { 104 + $icon = 'fa-check-circle green'; 105 + } else { 106 + $icon = 'fa-minus-circle red'; 107 + } 108 + $icon = id(new PHUIIconView()) 109 + ->setIconFont($icon) 110 + ->setText( 111 + ucfirst($rule['action']).' '.$rule_object->getRuleDescription()); 112 + $handle_phids = 113 + $rule_object->getRequiredHandlePHIDsForSummary($rule['value']); 114 + if ($handle_phids) { 115 + $value = $this->renderHandlesForPHIDs($handle_phids, ','); 116 + } else { 117 + $value = $rule['value']; 118 + } 119 + $details[] = phutil_tag('div', 120 + array( 121 + 'class' => 'policy-transaction-detail-row' 122 + ), 123 + array( 124 + $icon, 125 + $value)); 126 + } 127 + 128 + $details[] = phutil_tag( 129 + 'p', 130 + array( 131 + 'class' => 'policy-transaction-detail-end' 132 + ), 133 + pht( 134 + 'If no rules match, %s all other users.', 135 + phutil_tag('b', 136 + array(), 137 + $policy->getDefaultAction()))); 138 + return $details; 139 + } 140 + 141 + }
+15 -7
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 311 311 } 312 312 } 313 313 314 - public function renderPolicyName($phid) { 314 + private function renderPolicyName($phid, $state = 'old') { 315 315 $policy = PhabricatorPolicy::newFromPolicyAndHandle( 316 316 $phid, 317 317 $this->getHandleIfExists($phid)); 318 318 if ($this->renderingTarget == self::TARGET_HTML) { 319 + switch ($policy->getType()) { 320 + case PhabricatorPolicyType::TYPE_CUSTOM: 321 + $policy->setHref('/transactions/'.$state.'/'.$this->getPHID().'/'); 322 + $policy->setWorkflow(true); 323 + break; 324 + default: 325 + break; 326 + } 319 327 $output = $policy->renderDescription(); 320 328 } else { 321 329 $output = hsprintf('%s', $policy->getFullName()); ··· 504 512 '%s changed the visibility of this %s from "%s" to "%s".', 505 513 $this->renderHandleLink($author_phid), 506 514 $this->getApplicationObjectTypeName(), 507 - $this->renderPolicyName($old), 508 - $this->renderPolicyName($new)); 515 + $this->renderPolicyName($old, 'old'), 516 + $this->renderPolicyName($new, 'new')); 509 517 case PhabricatorTransactions::TYPE_EDIT_POLICY: 510 518 return pht( 511 519 '%s changed the edit policy of this %s from "%s" to "%s".', 512 520 $this->renderHandleLink($author_phid), 513 521 $this->getApplicationObjectTypeName(), 514 - $this->renderPolicyName($old), 515 - $this->renderPolicyName($new)); 522 + $this->renderPolicyName($old, 'old'), 523 + $this->renderPolicyName($new, 'new')); 516 524 case PhabricatorTransactions::TYPE_JOIN_POLICY: 517 525 return pht( 518 526 '%s changed the join policy of this %s from "%s" to "%s".', 519 527 $this->renderHandleLink($author_phid), 520 528 $this->getApplicationObjectTypeName(), 521 - $this->renderPolicyName($old), 522 - $this->renderPolicyName($new)); 529 + $this->renderPolicyName($old, 'old'), 530 + $this->renderPolicyName($new, 'new')); 523 531 case PhabricatorTransactions::TYPE_SUBSCRIBERS: 524 532 $add = array_diff($new, $old); 525 533 $rem = array_diff($old, $new);
+19
webroot/rsrc/css/application/policy/policy-transaction-detail.css
··· 1 + /** 2 + * @provides policy-transaction-detail-css 3 + */ 4 + 5 + .policy-transaction-detail-intro { 6 + margin-bottom: 12px; 7 + } 8 + 9 + .policy-transaction-detail-row { 10 + margin: 4px 0px 4px 8px; 11 + } 12 + 13 + .policy-transaction-detail-row .phui-icon-view { 14 + padding-right: 4px; 15 + } 16 + 17 + .policy-transaction-detail-end { 18 + margin-top: 12px; 19 + }