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