Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -1163,6 +1163,7 @@ '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 @@ '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 @@ 'PhabricatorApplicationTransactionValidationException' => 'Exception', 'PhabricatorApplicationTransactionView' => 'AphrontView', 'PhabricatorApplicationTransactions' => 'PhabricatorApplication', + 'PhabricatorApplicationTypeahead' => 'PhabricatorApplication', 'PhabricatorApplicationUIExamples' => 'PhabricatorApplication', 'PhabricatorApplicationUninstallController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationXHProf' => 'PhabricatorApplication', @@ -4896,7 +4903,12 @@ 'PhabricatorTrivialTestCase' => 'PhabricatorTestCase', 'PhabricatorTwoColumnExample' => 'PhabricatorUIExample', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', + 'PhabricatorTypeaheadCompositeDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorTypeaheadDatasource' => 'Phobject', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', + 'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController', + 'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorUIExampleRenderController' => 'PhabricatorController', 'PhabricatorUIListFilterExample' => 'PhabricatorUIExample', Index: src/aphront/configuration/AphrontDefaultApplicationConfiguration.php =================================================================== --- src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -16,11 +16,6 @@ public function getURIMap() { return $this->getResourceURIMapRules() + array( - '/typeahead/' => array( - 'common/(?P\w+)/' - => 'PhabricatorTypeaheadCommonDatasourceController', - ), - '/oauthserver/' => array( 'auth/' => 'PhabricatorOAuthServerAuthController', 'test/' => 'PhabricatorOAuthServerTestController', Index: src/applications/typeahead/application/PhabricatorApplicationTypeahead.php =================================================================== --- /dev/null +++ src/applications/typeahead/application/PhabricatorApplicationTypeahead.php @@ -0,0 +1,24 @@ + array( + 'common/(?P\w+)/' + => 'PhabricatorTypeaheadCommonDatasourceController', + 'class/(?:(?P\w+)/)?' + => 'PhabricatorTypeaheadModularDatasourceController', + ), + ); + } + + public function shouldAppearInLaunchView() { + return false; + } + + public function canUninstall() { + return false; + } + +} Index: src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php =================================================================== --- src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php +++ src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php @@ -14,7 +14,6 @@ } public function processRequest() { - $request = $this->getRequest(); $viewer = $request->getUser(); $query = $request->getStr('q'); Index: src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php =================================================================== --- /dev/null +++ src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php @@ -0,0 +1,113 @@ +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'), + )); + } + +} Index: src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php =================================================================== --- /dev/null +++ src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php @@ -0,0 +1,49 @@ +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; + } + +} Index: src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php =================================================================== --- /dev/null +++ src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -0,0 +1,50 @@ +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(); + +} Index: src/applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php =================================================================== --- /dev/null +++ src/applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php @@ -0,0 +1,43 @@ +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; + } + +} Index: src/applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php =================================================================== --- /dev/null +++ src/applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php @@ -0,0 +1,21 @@ +datasources; + } + + public function getPlaceholderText() { + throw new Exception(pht('This source is not usable directly.')); + } + + public function addDatasource(PhabricatorTypeaheadDatasource $source) { + $this->datasources[] = $source; + return $this; + } + +}