diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php index 5ee76b44f7..b767ead4f0 100644 --- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php +++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php @@ -1,188 +1,206 @@ getViewer(); $method_name = $request->getURIData('method'); $method = id(new PhabricatorConduitMethodQuery()) ->setViewer($viewer) ->withMethods(array($method_name)) ->executeOne(); if (!$method) { return new Aphront404Response(); } $method->setViewer($viewer); $call_uri = '/api/'.$method->getAPIMethodName(); - $status = $method->getMethodStatus(); - $reason = $method->getMethodStatusDescription(); $errors = array(); - switch ($status) { - case ConduitAPIMethod::METHOD_STATUS_DEPRECATED: - $reason = nonempty($reason, pht('This method is deprecated.')); - $errors[] = pht('Deprecated Method: %s', $reason); - break; - case ConduitAPIMethod::METHOD_STATUS_UNSTABLE: - $reason = nonempty( - $reason, - pht( - 'This method is new and unstable. Its interface is subject '. - 'to change.')); - $errors[] = pht('Unstable Method: %s', $reason); - break; - } - $form = id(new AphrontFormView()) ->setAction($call_uri) ->setUser($request->getUser()) ->appendRemarkupInstructions( pht( 'Enter parameters using **JSON**. For instance, to enter a '. 'list, type: `%s`', '["apple", "banana", "cherry"]')); $params = $method->getParamTypes(); foreach ($params as $param => $desc) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel($param) ->setName("params[{$param}]") ->setCaption($desc)); } $must_login = !$viewer->isLoggedIn() && $method->shouldRequireAuthentication(); if ($must_login) { $errors[] = pht( 'Login Required: This method requires authentication. You must '. 'log in before you can make calls to it.'); } else { $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Output Format')) ->setName('output') ->setOptions( array( 'human' => pht('Human Readable'), 'json' => pht('JSON'), ))) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($this->getApplicationURI()) ->setValue(pht('Call Method'))); } $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($method->getAPIMethodName()) ->setHeaderIcon('fa-tty'); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Call Method')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $properties = $this->buildMethodProperties($method); $info_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('API Method: %s', $method->getAPIMethodName())) ->setFormErrors($errors) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($properties); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($method->getAPIMethodName()); $crumbs->setBorder(true); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter(array( $info_box, $method->getMethodDocumentation(), $form_box, $this->renderExampleBox($method, null), )); $title = $method->getAPIMethodName(); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($view); } private function buildMethodProperties(ConduitAPIMethod $method) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()); + $status = $method->getMethodStatus(); + $reason = $method->getMethodStatusDescription(); + + switch ($status) { + case ConduitAPIMethod::METHOD_STATUS_UNSTABLE: + $stability_icon = 'fa-exclamation-triangle yellow'; + $stability_label = pht('Unstable Method'); + $stability_info = nonempty( + $reason, + pht( + 'This method is new and unstable. Its interface is subject '. + 'to change.')); + break; + case ConduitAPIMethod::METHOD_STATUS_DEPRECATED: + $stability_icon = 'fa-exclamation-triangle red'; + $stability_label = pht('Deprecated Method'); + $stability_info = nonempty($reason, pht('This method is deprecated.')); + break; + default: + $stability_label = null; + break; + } + + if ($stability_label) { + $view->addProperty( + pht('Stability'), + array( + id(new PHUIIconView())->setIcon($stability_icon), + ' ', + phutil_tag('strong', array(), $stability_label.':'), + ' ', + $stability_info, + )); + } + $view->addProperty( pht('Returns'), $method->getReturnType()); $error_types = $method->getErrorTypes(); $error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.'); $error_description = array(); foreach ($error_types as $error => $meaning) { $error_description[] = hsprintf( '
  • %s: %s
  • ', $error, $meaning); } $error_description = phutil_tag('ul', array(), $error_description); $view->addProperty( pht('Errors'), $error_description); $scope = $method->getRequiredScope(); switch ($scope) { case ConduitAPIMethod::SCOPE_ALWAYS: $oauth_icon = 'fa-globe green'; $oauth_description = pht( 'OAuth clients may always call this method.'); break; case ConduitAPIMethod::SCOPE_NEVER: $oauth_icon = 'fa-ban red'; $oauth_description = pht( 'OAuth clients may never call this method.'); break; default: $oauth_icon = 'fa-unlock-alt blue'; $oauth_description = pht( 'OAuth clients may call this method after requesting access to '. 'the "%s" scope.', $scope); break; } $view->addProperty( pht('OAuth Scope'), array( id(new PHUIIconView())->setIcon($oauth_icon), ' ', $oauth_description, )); $view->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent( new PHUIRemarkupView($viewer, $method->getMethodDescription())); return $view; } } diff --git a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php index a3b7b52cab..c79f612545 100644 --- a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php +++ b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php @@ -1,161 +1,166 @@ 'optional string', 'projectOwner' => 'optional string', 'userAffiliated' => 'optional string', 'repositoryCallsign' => 'optional string', 'path' => 'optional string', ); } protected function defineReturnType() { return 'dict dict of package info>'; } protected function defineErrorTypes() { return array( 'ERR-INVALID-USAGE' => pht( 'Provide one of a single owner phid (user/project), a single '. 'affiliated user phid (user), or a repository/path.'), 'ERR-INVALID-PARAMETER' => pht('Parameter should be a phid.'), 'ERR_REP_NOT_FOUND' => pht('The repository callsign is not recognized.'), ); } protected static function queryAll() { return id(new PhabricatorOwnersPackage())->loadAll(); } protected static function queryByOwner($owner) { $is_valid_phid = phid_get_type($owner) == PhabricatorPeopleUserPHIDType::TYPECONST || phid_get_type($owner) == PhabricatorProjectProjectPHIDType::TYPECONST; if (!$is_valid_phid) { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription( pht( 'Expected user/project PHID for owner, got %s.', $owner)); } $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( 'userPHID = %s', $owner); $package_ids = mpull($owners, 'getPackageID'); $packages = array(); foreach ($package_ids as $id) { $packages[] = id(new PhabricatorOwnersPackage())->load($id); } return $packages; } private static function queryByPath( PhabricatorUser $viewer, $repo_callsign, $path) { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withCallsigns(array($repo_callsign)) ->executeOne(); if (!$repository) { throw id(new ConduitException('ERR_REP_NOT_FOUND')) ->setErrorDescription( pht( 'Repository callsign %s not recognized', $repo_callsign)); } if ($path == null) { return PhabricatorOwnersPackage::loadPackagesForRepository($repository); } else { return PhabricatorOwnersPackage::loadOwningPackages( $repository, $path); } } public static function buildPackageInformationDictionaries($packages) { assert_instances_of($packages, 'PhabricatorOwnersPackage'); $result = array(); foreach ($packages as $package) { $p_owners = $package->loadOwners(); $p_paths = $package->loadPaths(); $owners = array_values(mpull($p_owners, 'getUserPHID')); $paths = array(); foreach ($p_paths as $p) { $paths[] = array($p->getRepositoryPHID(), $p->getPath()); } $result[$package->getPHID()] = array( 'phid' => $package->getPHID(), 'name' => $package->getName(), 'description' => $package->getDescription(), 'owners' => $owners, 'paths' => $paths, ); } return $result; } protected function execute(ConduitAPIRequest $request) { $is_owner_query = ($request->getValue('userOwner') || $request->getValue('projectOwner')) ? 1 : 0; $is_affiliated_query = $request->getValue('userAffiliated') ? 1 : 0; $repo = $request->getValue('repositoryCallsign'); $path = $request->getValue('path'); $is_path_query = $repo ? 1 : 0; if ($is_owner_query + $is_path_query + $is_affiliated_query === 0) { // if no search terms are provided, return everything $packages = self::queryAll(); } else if ($is_owner_query + $is_path_query + $is_affiliated_query > 1) { // otherwise, exactly one of these should be provided throw new ConduitException('ERR-INVALID-USAGE'); } if ($is_affiliated_query) { $query = id(new PhabricatorOwnersPackageQuery()) ->setViewer($request->getUser()); $query->withAuthorityPHIDs(array($request->getValue('userAffiliated'))); $packages = $query->execute(); } else if ($is_owner_query) { $owner = nonempty( $request->getValue('userOwner'), $request->getValue('projectOwner')); $packages = self::queryByOwner($owner); } else if ($is_path_query) { $packages = self::queryByPath($request->getUser(), $repo, $path); } return self::buildPackageInformationDictionaries($packages); } } diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php index b5cd52471b..d1e8e5f3f8 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -1,601 +1,604 @@ getCustomQueryMaps($query); // Make sure we emit empty maps as objects, not lists. foreach ($maps as $key => $map) { if (!$map) { $maps[$key] = (object)$map; } } if (!$maps) { $maps = (object)$maps; } return $maps; } protected function getCustomQueryMaps($query) { return array(); } public function getApplication() { $engine = $this->newSearchEngine(); $class = $engine->getApplicationClassName(); return PhabricatorApplication::getByClass($class); } public function getMethodStatus() { return self::METHOD_STATUS_UNSTABLE; } public function getMethodStatusDescription() { - return pht('ApplicationSearch methods are highly unstable.'); + return pht( + 'ApplicationSearch methods are fairly stable, but were introduced '. + 'relatively recently and may continue to evolve as more applications '. + 'adopt them.'); } final protected function defineParamTypes() { return array( 'queryKey' => 'optional string', 'constraints' => 'optional map', 'attachments' => 'optional map', 'order' => 'optional order', ) + $this->getPagerParamTypes(); } final protected function defineReturnType() { return 'map'; } final protected function execute(ConduitAPIRequest $request) { $engine = $this->newSearchEngine() ->setViewer($request->getUser()); return $engine->buildConduitResponse($request, $this); } final public function getMethodDescription() { return pht( 'This is a standard **ApplicationSearch** method which will let you '. 'list, query, or search for objects. For documentation on these '. 'endpoints, see **[[ %s | Conduit API: Using Search Endpoints ]]**.', PhabricatorEnv::getDoclink('Conduit API: Using Edit Endpoints')); } final public function getMethodDocumentation() { $viewer = $this->getViewer(); $engine = $this->newSearchEngine() ->setViewer($viewer); $query = $engine->newQuery(); $out = array(); $out[] = $this->buildQueriesBox($engine); $out[] = $this->buildConstraintsBox($engine); $out[] = $this->buildOrderBox($engine, $query); $out[] = $this->buildFieldsBox($engine); $out[] = $this->buildAttachmentsBox($engine); $out[] = $this->buildPagingBox($engine); return $out; } private function buildQueriesBox( PhabricatorApplicationSearchEngine $engine) { $viewer = $this->getViewer(); $info = pht(<<loadAllNamedQueries(); $rows = array(); foreach ($named_queries as $named_query) { $builtin = $named_query->getIsBuiltin() ? pht('Builtin') : pht('Custom'); $rows[] = array( $named_query->getQueryKey(), $named_query->getQueryName(), $builtin, ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Query Key'), pht('Name'), pht('Builtin'), )) ->setColumnClasses( array( 'prewrap', 'pri wide', null, )); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Builtin and Saved Queries')) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)) ->appendChild($table); } private function buildConstraintsBox( PhabricatorApplicationSearchEngine $engine) { $info = pht(<<getSearchFieldsForConduit(); // As a convenience, put these fields at the very top, even if the engine // specifies and alternate display order for the web UI. These fields are // very important in the API and nearly useless in the web UI. $fields = array_select_keys( $fields, array('ids', 'phids')) + $fields; $rows = array(); foreach ($fields as $field) { $key = $field->getConduitKey(); $label = $field->getLabel(); $type_object = $field->getConduitParameterType(); if ($type_object) { $type = $type_object->getTypeName(); $description = $field->getDescription(); } else { $type = null; $description = phutil_tag('em', array(), pht('Not supported.')); } $rows[] = array( $key, $label, $type, $description, ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Label'), pht('Type'), pht('Description'), )) ->setColumnClasses( array( 'prewrap', 'pri', 'prewrap', 'wide', )); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Custom Query Constraints')) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)) ->appendChild($table); } private function buildOrderBox( PhabricatorApplicationSearchEngine $engine, $query) { $orders_info = pht(<<getBuiltinOrders(); $rows = array(); foreach ($orders as $key => $order) { $rows[] = array( $key, $order['name'], implode(', ', $order['vector']), ); } $orders_table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Description'), pht('Columns'), )) ->setColumnClasses( array( 'pri', '', 'wide', )); $columns_info = pht(<<getOrderableColumns(); $rows = array(); foreach ($columns as $key => $column) { $rows[] = array( $key, idx($column, 'unique') ? pht('Yes') : pht('No'), ); } $columns_table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Unique'), )) ->setColumnClasses( array( 'pri', 'wide', )); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Result Ordering')) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($orders_info)) ->appendChild($orders_table) ->appendChild($this->buildRemarkup($columns_info)) ->appendChild($columns_table); } private function buildFieldsBox( PhabricatorApplicationSearchEngine $engine) { $info = pht(<<getAllConduitFieldSpecifications(); $rows = array(); foreach ($specs as $key => $spec) { $type = $spec->getType(); $description = $spec->getDescription(); $rows[] = array( $key, $type, $description, ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Type'), pht('Description'), )) ->setColumnClasses( array( 'pri', 'mono', 'wide', )); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Object Fields')) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)) ->appendChild($table); } private function buildAttachmentsBox( PhabricatorApplicationSearchEngine $engine) { $info = pht(<<getConduitSearchAttachments(); $rows = array(); foreach ($attachments as $key => $attachment) { $rows[] = array( $key, $attachment->getAttachmentName(), $attachment->getAttachmentDescription(), ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Name'), pht('Description'), )) ->setColumnClasses( array( 'prewrap', 'pri', 'wide', )); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Attachments')) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)) ->appendChild($table); } private function buildPagingBox( PhabricatorApplicationSearchEngine $engine) { $info = pht(<<setHeaderText(pht('Paging and Limits')) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)); } private function buildRemarkup($remarkup) { $viewer = $this->getViewer(); $view = new PHUIRemarkupView($viewer, $remarkup); return id(new PHUIBoxView()) ->appendChild($view) ->addPadding(PHUI::PADDING_LARGE); } } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php index b6f95ecd1b..e478de1e20 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php @@ -1,163 +1,166 @@ newEditEngine(); $class = $engine->getEngineApplicationClass(); return PhabricatorApplication::getByClass($class); } public function getMethodStatus() { return self::METHOD_STATUS_UNSTABLE; } public function getMethodStatusDescription() { - return pht('ApplicationEditor methods are highly unstable.'); + return pht( + 'ApplicationEditor methods are fairly stable, but were introduced '. + 'relativelyr cently and may continue to evolve as more applications '. + 'adopt them.'); } final protected function defineParamTypes() { return array( 'transactions' => 'list>', 'objectIdentifier' => 'optional id|phid|string', ); } final protected function defineReturnType() { return 'map'; } final protected function execute(ConduitAPIRequest $request) { $engine = $this->newEditEngine() ->setViewer($request->getUser()); return $engine->buildConduitResponse($request); } final public function getMethodDescription() { return pht( 'This is a standard **ApplicationEditor** method which allows you to '. 'create and modify objects by applying transactions. For documentation '. 'on these endpoints, see '. '**[[ %s | Conduit API: Using Edit Endpoints ]]**.', PhabricatorEnv::getDoclink('Conduit API: Using Edit Endpoints')); } final public function getMethodDocumentation() { $viewer = $this->getViewer(); $engine = $this->newEditEngine() ->setViewer($viewer); $types = $engine->getConduitEditTypes(); $out = array(); $out[] = $this->buildEditTypesBoxes($engine, $types); return $out; } private function buildEditTypesBoxes( PhabricatorEditEngine $engine, array $types) { $boxes = array(); $summary_info = pht( 'This endpoint supports these types of transactions. See below for '. 'detailed information about each transaction type.'); $rows = array(); foreach ($types as $type) { $rows[] = array( $type->getEditType(), $type->getConduitDescription(), ); } $summary_table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Description'), )) ->setColumnClasses( array( 'prewrap', 'wide', )); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Transaction Types')) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($summary_info)) ->appendChild($summary_table); foreach ($types as $type) { $section = array(); $section[] = $type->getConduitDescription(); $type_documentation = $type->getConduitDocumentation(); if (strlen($type_documentation)) { $section[] = $type_documentation; } $section = implode("\n\n", $section); $rows = array(); $rows[] = array( 'type', 'const', $type->getEditType(), ); $rows[] = array( 'value', $type->getConduitType(), $type->getConduitTypeDescription(), ); $type_table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Type'), pht('Description'), )) ->setColumnClasses( array( 'prewrap', 'prewrap', 'wide', )); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Transaction Type: %s', $type->getEditType())) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($section)) ->appendChild($type_table); } return $boxes; } private function buildRemarkup($remarkup) { $viewer = $this->getViewer(); $view = new PHUIRemarkupView($viewer, $remarkup); return id(new PHUIBoxView()) ->appendChild($view) ->addPadding(PHUI::PADDING_LARGE); } }