@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Show how to call Conduit API methods from clients

Summary: Fixes T3628. Ref T5955.

Test Plan:
On the method page, you see a generic example:

{F396471}

After making a call, you see a specific example with your parameters:

{F396472}

{F396474}

{F396475}

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T3628, T5955

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

+343 -50
+2 -2
resources/celerity/map.php
··· 39 39 'rsrc/css/application/base/phabricator-application-launch-view.css' => '16ca323f', 40 40 'rsrc/css/application/base/standard-page-view.css' => 'd3e1abe9', 41 41 'rsrc/css/application/chatlog/chatlog.css' => '852140ff', 42 + 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 42 43 'rsrc/css/application/config/config-options.css' => '7fedf08b', 43 44 'rsrc/css/application/config/config-template.css' => '8e6c6fcd', 44 45 'rsrc/css/application/config/config-welcome.css' => '6abd79be', ··· 345 346 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 346 347 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 347 348 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 348 - 'rsrc/js/application/calendar/event-all-day.js' => '712540b4', 349 349 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 350 350 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '9e507b59', 351 351 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', ··· 508 508 'aphront-typeahead-control-css' => '0e403212', 509 509 'auth-css' => '1e655982', 510 510 'changeset-view-manager' => '58562350', 511 + 'conduit-api-css' => '7bc725c4', 511 512 'config-options-css' => '7fedf08b', 512 513 'config-welcome-css' => '6abd79be', 513 514 'conpherence-durable-column-view' => '2e68a92f', ··· 584 585 'javelin-behavior-doorkeeper-tag' => 'e5822781', 585 586 'javelin-behavior-durable-column' => '657c2b50', 586 587 'javelin-behavior-error-log' => '6882e80a', 587 - 'javelin-behavior-event-all-day' => '712540b4', 588 588 'javelin-behavior-fancy-datepicker' => '5c0f680f', 589 589 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 590 590 'javelin-behavior-herald-rule-editor' => '7ebaeed3',
+4
src/applications/conduit/call/ConduitCall.php
··· 150 150 return $method; 151 151 } 152 152 153 + public function getMethodImplementation() { 154 + return $this->handler; 155 + } 156 + 153 157 154 158 }
+15 -2
src/applications/conduit/controller/PhabricatorConduitAPIController.php
··· 21 21 $method = $this->method; 22 22 23 23 $api_request = null; 24 + $method_implementation = null; 24 25 25 26 $log = new PhabricatorConduitMethodCallLog(); 26 27 $log->setMethod($method); ··· 36 37 list($metadata, $params) = $this->decodeConduitParams($request, $method); 37 38 38 39 $call = new ConduitCall($method, $params); 40 + $method_implementation = $call->getMethodImplementation(); 39 41 40 42 $result = null; 41 43 ··· 151 153 return $this->buildHumanReadableResponse( 152 154 $method, 153 155 $api_request, 154 - $response->toDictionary()); 156 + $response->toDictionary(), 157 + $method_implementation); 155 158 case 'json': 156 159 default: 157 160 return id(new AphrontJSONResponse()) ··· 525 528 private function buildHumanReadableResponse( 526 529 $method, 527 530 ConduitAPIRequest $request = null, 528 - $result = null) { 531 + $result = null, 532 + ConduitAPIMethod $method_implementation = null) { 529 533 530 534 $param_rows = array(); 531 535 $param_rows[] = array('Method', $this->renderAPIValue($method)); ··· 574 578 ->addTextCrumb($method, $method_uri) 575 579 ->addTextCrumb(pht('Call')); 576 580 581 + $example_panel = null; 582 + if ($request && $method_implementation) { 583 + $params = $request->getAllParameters(); 584 + $example_panel = $this->renderExampleBox( 585 + $method_implementation, 586 + $params); 587 + } 588 + 577 589 return $this->buildApplicationPage( 578 590 array( 579 591 $crumbs, 580 592 $param_panel, 581 593 $result_panel, 594 + $example_panel, 582 595 ), 583 596 array( 584 597 'title' => pht('Method Call Result'),
+60 -46
src/applications/conduit/controller/PhabricatorConduitConsoleController.php
··· 3 3 final class PhabricatorConduitConsoleController 4 4 extends PhabricatorConduitController { 5 5 6 - private $method; 7 - 8 6 public function shouldAllowPublic() { 9 7 return true; 10 8 } 11 9 12 - public function willProcessRequest(array $data) { 13 - $this->method = $data['method']; 14 - } 15 - 16 - public function processRequest() { 17 - 18 - $request = $this->getRequest(); 19 - $viewer = $request->getUser(); 10 + public function handleRequest(AphrontRequest $request) { 11 + $viewer = $this->getViewer(); 12 + $method_name = $request->getURIData('method'); 20 13 21 14 $method = id(new PhabricatorConduitMethodQuery()) 22 15 ->setViewer($viewer) 23 - ->withMethods(array($this->method)) 16 + ->withMethods(array($method_name)) 24 17 ->executeOne(); 25 - 26 18 if (!$method) { 27 19 return new Aphront404Response(); 28 20 } 29 21 30 - $can_call_method = false; 22 + $call_uri = '/api/'.$method->getAPIMethodName(); 31 23 32 24 $status = $method->getMethodStatus(); 33 25 $reason = $method->getMethodStatusDescription(); ··· 48 40 break; 49 41 } 50 42 51 - $error_types = $method->getErrorTypes(); 52 - $error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.'); 53 - $error_description = array(); 54 - foreach ($error_types as $error => $meaning) { 55 - $error_description[] = hsprintf( 56 - '<li><strong>%s:</strong> %s</li>', 57 - $error, 58 - $meaning); 59 - } 60 - $error_description = phutil_tag('ul', array(), $error_description); 61 - 62 - $form = new AphrontFormView(); 63 - $form 43 + $form = id(new AphrontFormView()) 44 + ->setAction($call_uri) 64 45 ->setUser($request->getUser()) 65 - ->setAction('/api/'.$this->method) 66 - ->appendChild( 67 - id(new AphrontFormStaticControl()) 68 - ->setLabel('Description') 69 - ->setValue($method->getMethodDescription())) 70 - ->appendChild( 71 - id(new AphrontFormStaticControl()) 72 - ->setLabel('Returns') 73 - ->setValue($method->getReturnType())) 74 - ->appendChild( 75 - id(new AphrontFormMarkupControl()) 76 - ->setLabel('Errors') 77 - ->setValue($error_description)) 78 - ->appendChild(hsprintf( 79 - '<p class="aphront-form-instructions">Enter parameters using '. 80 - '<strong>JSON</strong>. For instance, to enter a list, type: '. 81 - '<tt>["apple", "banana", "cherry"]</tt>')); 46 + ->appendRemarkupInstructions( 47 + pht( 48 + 'Enter parameters using **JSON**. For instance, to enter a '. 49 + 'list, type: `["apple", "banana", "cherry"]`')); 82 50 83 51 $params = $method->getParamTypes(); 84 52 foreach ($params as $param => $desc) { ··· 117 85 ->setHeader($method->getAPIMethodName()); 118 86 119 87 $form_box = id(new PHUIObjectBoxView()) 120 - ->setHeader($header) 121 - ->setFormErrors($errors) 88 + ->setHeaderText(pht('Call Method')) 122 89 ->appendChild($form); 123 90 124 91 $content = array(); 125 92 93 + $properties = $this->buildMethodProperties($method); 94 + 95 + $info_box = id(new PHUIObjectBoxView()) 96 + ->setHeaderText(pht('API Method: %s', $method->getAPIMethodName())) 97 + ->setFormErrors($errors) 98 + ->appendChild($properties); 99 + 100 + $content[] = $info_box; 101 + $content[] = $form_box; 102 + $content[] = $this->renderExampleBox($method, null); 103 + 126 104 $query = $method->newQueryObject(); 127 105 if ($query) { 128 106 $orders = $query->getBuiltinOrders(); ··· 185 163 return $this->buildApplicationPage( 186 164 array( 187 165 $crumbs, 188 - $form_box, 189 166 $content, 190 167 ), 191 168 array( 192 169 'title' => $method->getAPIMethodName(), 193 170 )); 194 171 } 172 + 173 + private function buildMethodProperties(ConduitAPIMethod $method) { 174 + $viewer = $this->getViewer(); 175 + 176 + $view = id(new PHUIPropertyListView()); 177 + 178 + $view->addProperty( 179 + pht('Returns'), 180 + $method->getReturnType()); 181 + 182 + $error_types = $method->getErrorTypes(); 183 + $error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.'); 184 + $error_description = array(); 185 + foreach ($error_types as $error => $meaning) { 186 + $error_description[] = hsprintf( 187 + '<li><strong>%s:</strong> %s</li>', 188 + $error, 189 + $meaning); 190 + } 191 + $error_description = phutil_tag('ul', array(), $error_description); 192 + 193 + $view->addProperty( 194 + pht('Errors'), 195 + $error_description); 196 + 197 + 198 + $description = $method->getMethodDescription(); 199 + $description = PhabricatorMarkupEngine::renderOneObject( 200 + id(new PhabricatorMarkupOneOff())->setContent($description), 201 + 'default', 202 + $viewer); 203 + $view->addSectionHeader(pht('Description')); 204 + $view->addTextContent($description); 205 + 206 + return $view; 207 + } 208 + 195 209 196 210 }
+246
src/applications/conduit/controller/PhabricatorConduitController.php
··· 24 24 return $this->buildSideNavView()->getMenu(); 25 25 } 26 26 27 + protected function renderExampleBox(ConduitAPIMethod $method, $params) { 28 + $arc_example = id(new PHUIPropertyListView()) 29 + ->addRawContent($this->renderExample($method, 'arc', $params)); 30 + 31 + $curl_example = id(new PHUIPropertyListView()) 32 + ->addRawContent($this->renderExample($method, 'curl', $params)); 33 + 34 + $php_example = id(new PHUIPropertyListView()) 35 + ->addRawContent($this->renderExample($method, 'php', $params)); 36 + 37 + $panel_link = phutil_tag( 38 + 'a', 39 + array( 40 + 'href' => '/settings/panel/apitokens/', 41 + ), 42 + pht('Conduit API Tokens')); 43 + 44 + $panel_link = phutil_tag('strong', array(), $panel_link); 45 + 46 + $messages = array( 47 + pht( 48 + 'Use the %s panel in Settings to generate or manage API tokens.', 49 + $panel_link), 50 + ); 51 + 52 + $info_view = id(new PHUIInfoView()) 53 + ->setErrors($messages) 54 + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); 55 + 56 + return id(new PHUIObjectBoxView()) 57 + ->setHeaderText(pht('Examples')) 58 + ->setInfoView($info_view) 59 + ->addPropertyList($arc_example, pht('arc call-conduit')) 60 + ->addPropertyList($curl_example, pht('cURL')) 61 + ->addPropertyList($php_example, pht('PHP')); 62 + } 63 + 64 + private function renderExample( 65 + ConduitAPIMethod $method, 66 + $kind, 67 + $params) { 68 + 69 + switch ($kind) { 70 + case 'arc': 71 + $example = $this->buildArcanistExample($method, $params); 72 + break; 73 + case 'php': 74 + $example = $this->buildPHPExample($method, $params); 75 + break; 76 + case 'curl': 77 + $example = $this->buildCURLExample($method, $params); 78 + break; 79 + default: 80 + throw new Exception(pht('Conduit client "%s" is not known.', $kind)); 81 + } 82 + 83 + return $example; 84 + } 85 + 86 + private function buildArcanistExample( 87 + ConduitAPIMethod $method, 88 + $params) { 89 + 90 + $parts = array(); 91 + 92 + $parts[] = '$ echo '; 93 + if ($params === null) { 94 + $parts[] = phutil_tag('strong', array(), '<json-parameters>'); 95 + } else { 96 + $params = $this->simplifyParams($params); 97 + $params = id(new PhutilJSON())->encodeFormatted($params); 98 + $params = trim($params); 99 + $params = csprintf('%s', $params); 100 + $parts[] = phutil_tag('strong', array('class' => 'real'), $params); 101 + } 102 + 103 + $parts[] = ' | '; 104 + $parts[] = 'arc call-conduit '; 105 + 106 + $parts[] = '--conduit-uri '; 107 + $parts[] = phutil_tag( 108 + 'strong', 109 + array('class' => 'real'), 110 + PhabricatorEnv::getURI('/')); 111 + $parts[] = ' '; 112 + 113 + $parts[] = '--conduit-token '; 114 + $parts[] = phutil_tag('strong', array(), '<conduit-token>'); 115 + $parts[] = ' '; 116 + 117 + $parts[] = $method->getAPIMethodName(); 118 + 119 + return $this->renderExampleCode($parts); 120 + } 121 + 122 + private function buildPHPExample( 123 + ConduitAPIMethod $method, 124 + $params) { 125 + 126 + $parts = array(); 127 + 128 + $libphutil_path = 'path/to/libphutil/src/__phutil_library_init__.php'; 129 + 130 + $parts[] = '<?php'; 131 + $parts[] = "\n\n"; 132 + 133 + $parts[] = 'require_once '; 134 + $parts[] = phutil_var_export($libphutil_path, true); 135 + $parts[] = ";\n\n"; 136 + 137 + $parts[] = '$api_token = "'; 138 + $parts[] = phutil_tag('strong', array(), pht('<api-token>')); 139 + $parts[] = "\";\n"; 140 + 141 + $parts[] = '$api_parameters = '; 142 + if ($params === null) { 143 + $parts[] = 'array('; 144 + $parts[] = phutil_tag('strong', array(), pht('<parameters>')); 145 + $parts[] = ');'; 146 + } else { 147 + $params = $this->simplifyParams($params); 148 + $params = phutil_var_export($params, true); 149 + $parts[] = phutil_tag('strong', array('class' => 'real'), $params); 150 + $parts[] = ';'; 151 + } 152 + $parts[] = "\n\n"; 153 + 154 + $parts[] = '$client = new ConduitClient('; 155 + $parts[] = phutil_tag( 156 + 'strong', 157 + array('class' => 'real'), 158 + phutil_var_export(PhabricatorEnv::getURI('/'), true)); 159 + $parts[] = ");\n"; 160 + 161 + $parts[] = '$client->setConduitToken($api_token);'; 162 + $parts[] = "\n\n"; 163 + 164 + $parts[] = '$result = $client->callMethodSynchronous('; 165 + $parts[] = phutil_tag( 166 + 'strong', 167 + array('class' => 'real'), 168 + phutil_var_export($method->getAPIMethodName(), true)); 169 + $parts[] = ', '; 170 + $parts[] = '$api_parameters'; 171 + $parts[] = ");\n"; 172 + 173 + $parts[] = 'print_r($result);'; 174 + 175 + return $this->renderExampleCode($parts); 176 + } 177 + 178 + private function buildCURLExample( 179 + ConduitAPIMethod $method, 180 + $params) { 181 + 182 + $call_uri = '/api/'.$method->getAPIMethodName(); 183 + 184 + $parts = array(); 185 + 186 + $linebreak = array('\\', phutil_tag('br'), ' '); 187 + 188 + $parts[] = '$ curl '; 189 + $parts[] = phutil_tag( 190 + 'strong', 191 + array('class' => 'real'), 192 + csprintf('%R', PhabricatorEnv::getURI($call_uri))); 193 + $parts[] = ' '; 194 + $parts[] = $linebreak; 195 + 196 + $parts[] = '-d api.token='; 197 + $parts[] = phutil_tag('strong', array(), 'api-token'); 198 + $parts[] = ' '; 199 + $parts[] = $linebreak; 200 + 201 + if ($params === null) { 202 + $parts[] = '-d '; 203 + $parts[] = phutil_tag('strong', array(), 'param'); 204 + $parts[] = '='; 205 + $parts[] = phutil_tag('strong', array(), 'value'); 206 + $parts[] = ' '; 207 + $parts[] = $linebreak; 208 + $parts[] = phutil_tag('strong', array(), '...'); 209 + } else { 210 + $lines = array(); 211 + $params = $this->simplifyParams($params); 212 + 213 + foreach ($params as $key => $value) { 214 + $pieces = $this->getQueryStringParts(null, $key, $value); 215 + foreach ($pieces as $piece) { 216 + $lines[] = array( 217 + '-d ', 218 + phutil_tag('strong', array('class' => 'real'), $piece), 219 + ); 220 + } 221 + } 222 + 223 + $parts[] = phutil_implode_html(array(' ', $linebreak), $lines); 224 + } 225 + 226 + return $this->renderExampleCode($parts); 227 + } 228 + 229 + private function renderExampleCode($example) { 230 + require_celerity_resource('conduit-api-css'); 231 + 232 + return phutil_tag( 233 + 'div', 234 + array( 235 + 'class' => 'PhabricatorMonospaced conduit-api-example-code', 236 + ), 237 + $example); 238 + } 239 + 240 + private function simplifyParams(array $params) { 241 + foreach ($params as $key => $value) { 242 + if ($value === null) { 243 + unset($params[$key]); 244 + } 245 + } 246 + return $params; 247 + } 248 + 249 + private function getQueryStringParts($prefix, $key, $value) { 250 + if ($prefix === null) { 251 + $head = phutil_escape_uri($key); 252 + } else { 253 + $head = $prefix.'['.phutil_escape_uri($key).']'; 254 + } 255 + 256 + if (!is_array($value)) { 257 + return array( 258 + $head.'='.phutil_escape_uri($value), 259 + ); 260 + } 261 + 262 + $results = array(); 263 + foreach ($value as $subkey => $subvalue) { 264 + $subparts = $this->getQueryStringParts($head, $subkey, $subvalue); 265 + foreach ($subparts as $subpart) { 266 + $results[] = $subpart; 267 + } 268 + } 269 + 270 + return $results; 271 + } 272 + 27 273 }
+16
webroot/rsrc/css/application/conduit/conduit-api.css
··· 1 + /** 2 + * @provides conduit-api-css 3 + */ 4 + .conduit-api-example-code { 5 + margin: 16px; 6 + white-space: pre; 7 + color: {$darkgreytext}; 8 + } 9 + 10 + .conduit-api-example-code strong { 11 + color: {$red}; 12 + } 13 + 14 + .conduit-api-example-code strong.real { 15 + color: {$blue}; 16 + }