1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +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:
epriestley 2014-02-14 10:23:25 -08:00
parent 7a96de10f0
commit 75c4a185a9
9 changed files with 312 additions and 6 deletions

View file

@ -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',

View file

@ -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',

View file

@ -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;
}
}

View file

@ -14,7 +14,6 @@ final class PhabricatorTypeaheadCommonDatasourceController
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$query = $request->getStr('q');

View file

@ -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'),
));
}
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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;
}
}