mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-20 04:20:55 +01:00
Begin modularizing typeahead sources
Summary: Ref T4420. This sets up the basics for modular typeahead sources. Basically, the huge `switch()` is just replaced with class-based runtime dispatch. The only clever bit I'm doing here is with `CompositeDatasource`, which pretty much just combines the results from several other datasources. We can use this to implement some of the weird cases where we need multiple types of results, although I think I can entirely eliminate many of them entirely. It also makes top-level implementation simpler, since more logic can go inside the sources. Sources are also application-aware, will be responsible for placeholder text, and have a slightly nicer debug view. Test Plan: {F112859} Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T4420 Differential Revision: https://secure.phabricator.com/D8228
This commit is contained in:
parent
7a96de10f0
commit
75c4a185a9
9 changed files with 312 additions and 6 deletions
|
@ -1163,6 +1163,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationTransactionValidationException' => 'applications/transactions/exception/PhabricatorApplicationTransactionValidationException.php',
|
||||
'PhabricatorApplicationTransactionView' => 'applications/transactions/view/PhabricatorApplicationTransactionView.php',
|
||||
'PhabricatorApplicationTransactions' => 'applications/transactions/application/PhabricatorApplicationTransactions.php',
|
||||
'PhabricatorApplicationTypeahead' => 'applications/typeahead/application/PhabricatorApplicationTypeahead.php',
|
||||
'PhabricatorApplicationUIExamples' => 'applications/uiexample/application/PhabricatorApplicationUIExamples.php',
|
||||
'PhabricatorApplicationUninstallController' => 'applications/meta/controller/PhabricatorApplicationUninstallController.php',
|
||||
'PhabricatorApplicationXHProf' => 'applications/xhprof/application/PhabricatorApplicationXHProf.php',
|
||||
|
@ -2120,8 +2121,13 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTrivialTestCase' => 'infrastructure/testing/__tests__/PhabricatorTrivialTestCase.php',
|
||||
'PhabricatorTwoColumnExample' => 'applications/uiexample/examples/PhabricatorTwoColumnExample.php',
|
||||
'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php',
|
||||
'PhabricatorTypeaheadCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php',
|
||||
'PhabricatorTypeaheadDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php',
|
||||
'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadDatasourceController.php',
|
||||
'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php',
|
||||
'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php',
|
||||
'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php',
|
||||
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php',
|
||||
'PhabricatorUIConfigOptions' => 'applications/config/option/PhabricatorUIConfigOptions.php',
|
||||
'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php',
|
||||
'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/PhabricatorUIExampleRenderController.php',
|
||||
|
@ -3801,6 +3807,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationTransactionValidationException' => 'Exception',
|
||||
'PhabricatorApplicationTransactionView' => 'AphrontView',
|
||||
'PhabricatorApplicationTransactions' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationTypeahead' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationUIExamples' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationUninstallController' => 'PhabricatorApplicationsController',
|
||||
'PhabricatorApplicationXHProf' => 'PhabricatorApplication',
|
||||
|
@ -4896,7 +4903,12 @@ phutil_register_library_map(array(
|
|||
'PhabricatorTrivialTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorTwoColumnExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
|
||||
'PhabricatorTypeaheadCompositeDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorTypeaheadDatasource' => 'Phobject',
|
||||
'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
|
||||
'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
|
||||
'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorUIExampleRenderController' => 'PhabricatorController',
|
||||
'PhabricatorUIListFilterExample' => 'PhabricatorUIExample',
|
||||
|
|
|
@ -16,11 +16,6 @@ class AphrontDefaultApplicationConfiguration
|
|||
|
||||
public function getURIMap() {
|
||||
return $this->getResourceURIMapRules() + array(
|
||||
'/typeahead/' => array(
|
||||
'common/(?P<type>\w+)/'
|
||||
=> 'PhabricatorTypeaheadCommonDatasourceController',
|
||||
),
|
||||
|
||||
'/oauthserver/' => array(
|
||||
'auth/' => 'PhabricatorOAuthServerAuthController',
|
||||
'test/' => 'PhabricatorOAuthServerTestController',
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorApplicationTypeahead extends PhabricatorApplication {
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/typeahead/' => array(
|
||||
'common/(?P<type>\w+)/'
|
||||
=> 'PhabricatorTypeaheadCommonDatasourceController',
|
||||
'class/(?:(?P<class>\w+)/)?'
|
||||
=> 'PhabricatorTypeaheadModularDatasourceController',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function shouldAppearInLaunchView() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canUninstall() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,6 @@ final class PhabricatorTypeaheadCommonDatasourceController
|
|||
}
|
||||
|
||||
public function processRequest() {
|
||||
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
$query = $request->getStr('q');
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorTypeaheadModularDatasourceController
|
||||
extends PhabricatorTypeaheadDatasourceController {
|
||||
|
||||
private $class;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->class = idx($data, 'class');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
$query = $request->getStr('q');
|
||||
|
||||
// Default this to the query string to make debugging a little bit easier.
|
||||
$raw_query = nonempty($request->getStr('raw'), $query);
|
||||
|
||||
// This makes form submission easier in the debug view.
|
||||
$this->class = nonempty($request->getStr('class'), $this->class);
|
||||
|
||||
$sources = id(new PhutilSymbolLoader())
|
||||
->setAncestorClass('PhabricatorTypeaheadDatasource')
|
||||
->loadObjects();
|
||||
|
||||
if (isset($sources[$this->class])) {
|
||||
$source = $sources[$this->class];
|
||||
|
||||
$composite = new PhabricatorTypeaheadRuntimeCompositeDatasource();
|
||||
$composite->addDatasource($source);
|
||||
|
||||
$composite
|
||||
->setViewer($viewer)
|
||||
->setQuery($query)
|
||||
->setRawQuery($raw_query);
|
||||
|
||||
$results = $composite->loadResults();
|
||||
} else {
|
||||
$results = array();
|
||||
}
|
||||
|
||||
$content = mpull($results, 'getWireFormat');
|
||||
|
||||
if ($request->isAjax()) {
|
||||
return id(new AphrontAjaxResponse())->setContent($content);
|
||||
}
|
||||
|
||||
// If there's a non-Ajax request to this endpoint, show results in a tabular
|
||||
// format to make it easier to debug typeahead output.
|
||||
|
||||
$options = array_fuse(array_keys($sources));
|
||||
asort($options);
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->setAction('/typeahead/class/')
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setLabel(pht('Source Class'))
|
||||
->setName('class')
|
||||
->setValue($this->class)
|
||||
->setOptions($options))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Query'))
|
||||
->setName('q')
|
||||
->setValue($request->getStr('q')))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Raw Query'))
|
||||
->setName('raw')
|
||||
->setValue($request->getStr('raw')))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Query')));
|
||||
|
||||
$form_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Token Query'))
|
||||
->setForm($form);
|
||||
|
||||
$table = new AphrontTableView($content);
|
||||
$table->setHeaders(
|
||||
array(
|
||||
'Name',
|
||||
'URI',
|
||||
'PHID',
|
||||
'Priority',
|
||||
'Display Name',
|
||||
'Display Type',
|
||||
'Image URI',
|
||||
'Priority Type',
|
||||
));
|
||||
|
||||
$result_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Token Results (%s)', $this->class))
|
||||
->appendChild($table);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$form_box,
|
||||
$result_box,
|
||||
),
|
||||
array(
|
||||
'title' => pht('Typeahead Results'),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorTypeaheadCompositeDatasource
|
||||
extends PhabricatorTypeaheadDatasource {
|
||||
|
||||
abstract public function getComponentDatasources();
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function loadResults() {
|
||||
$results = array();
|
||||
foreach ($this->getUsableDatasources() as $source) {
|
||||
$source
|
||||
->setRawQuery($this->getRawQuery())
|
||||
->setQuery($this->getQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setLimit($this->getLimit());
|
||||
|
||||
$results[] = $source->loadResults();
|
||||
}
|
||||
return array_mergev($results);
|
||||
}
|
||||
|
||||
private function getUsableDatasources() {
|
||||
$sources = $this->getComponentDatasources();
|
||||
|
||||
$usable = array();
|
||||
foreach ($sources as $source) {
|
||||
$application_class = $source->getDatasourceApplicationClass();
|
||||
|
||||
if ($application_class) {
|
||||
$result = id(new PhabricatorApplicationQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withClasses(array($application_class))
|
||||
->execute();
|
||||
if (!$result) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$usable[] = $source;
|
||||
}
|
||||
|
||||
return $usable;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorTypeaheadDatasource extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $query;
|
||||
private $rawQuery;
|
||||
private $limit;
|
||||
|
||||
public function setLimit($limit) {
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLimit() {
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
public function setRawQuery($raw_query) {
|
||||
$this->rawQuery = $raw_query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRawQuery() {
|
||||
return $this->rawQuery;
|
||||
}
|
||||
|
||||
public function setQuery($query) {
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQuery() {
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
abstract public function getPlaceholderText();
|
||||
abstract public function getDatasourceApplicationClass();
|
||||
abstract public function loadResults();
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorTypeaheadMonogramDatasource
|
||||
extends PhabricatorTypeaheadDatasource {
|
||||
|
||||
public function getPlaceholderText() {
|
||||
return pht('Type an object name...');
|
||||
}
|
||||
|
||||
public function getDatasourceApplicationClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function loadResults() {
|
||||
$viewer = $this->getViewer();
|
||||
$raw_query = $this->getRawQuery();
|
||||
|
||||
$results = array();
|
||||
|
||||
$objects = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames(array($raw_query))
|
||||
->execute();
|
||||
if ($objects) {
|
||||
$handles = id(new PhabricatorHandleQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(mpull($objects, 'getPHID'))
|
||||
->execute();
|
||||
$handle = head($handles);
|
||||
if ($handle) {
|
||||
$results[] = id(new PhabricatorTypeaheadResult())
|
||||
->setName($handle->getFullName())
|
||||
->setDisplayType($handle->getTypeName())
|
||||
->setURI($handle->getURI())
|
||||
->setPHID($handle->getPHID())
|
||||
->setPriorityType('jump');
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorTypeaheadRuntimeCompositeDatasource
|
||||
extends PhabricatorTypeaheadCompositeDatasource {
|
||||
|
||||
private $datasources = array();
|
||||
|
||||
public function getComponentDatasources() {
|
||||
return $this->datasources;
|
||||
}
|
||||
|
||||
public function getPlaceholderText() {
|
||||
throw new Exception(pht('This source is not usable directly.'));
|
||||
}
|
||||
|
||||
public function addDatasource(PhabricatorTypeaheadDatasource $source) {
|
||||
$this->datasources[] = $source;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue