Add a basic search typeahead

Summary:
This needs a bunch of refinement but pretty much works. Currently shows only users and applications. Plans:

  - Show actual search results too.
  - Clean up the datasource endpoint so it's less of a mess.
  - Make other typeaheads look more like this one.
  - Improve sorting.
  - Make object names hit the named objects as the first match.

Test Plan: Will attach screenshots.

Reviewers: btrahan, vrana, chad

Reviewed By: vrana

CC: aran

Maniphest Tasks: T1569

Differential Revision: https://secure.phabricator.com/D3110
This commit is contained in:
epriestley
2012-07-31 17:58:21 -07:00
parent b43e6f2a5f
commit 852ecc2102
7 changed files with 261 additions and 26 deletions

View File

@@ -1439,6 +1439,21 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/core/behavior-oncopy.js',
),
'javelin-behavior-phabricator-search-typeahead' =>
array(
'uri' => '/res/9ceffb09/rsrc/js/application/core/behavior-search-typeahead.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-typeahead-ondemand-source',
2 => 'javelin-typeahead',
3 => 'javelin-dom',
4 => 'javelin-uri',
5 => 'javelin-stratcom',
),
'disk' => '/rsrc/js/application/core/behavior-search-typeahead.js',
),
'javelin-behavior-phabricator-tooltips' =>
array(
'uri' => '/res/49f92a92/rsrc/js/application/core/behavior-tooltip.js',
@@ -2263,7 +2278,7 @@ celerity_register_resource_map(array(
),
'phabricator-main-menu-view' =>
array(
'uri' => '/res/795788ca/rsrc/css/application/base/main-menu-view.css',
'uri' => '/res/5bae3234/rsrc/css/application/base/main-menu-view.css',
'type' => 'css',
'requires' =>
array(

View File

@@ -30,15 +30,35 @@ abstract class PhabricatorApplication {
public function getName() {
return substr(__CLASS__, strlen('PhabricatorApplication'));
return substr(get_class($this), strlen('PhabricatorApplication'));
}
public function getShortDescription() {
return $this->getName().' Application';
}
public function isEnabled() {
return true;
}
public function getPHID() {
return 'PHID-APPS-'.get_class($this);
}
/* -( Application Information )-------------------------------------------- */
public function getTypeaheadURI() {
return $this->getBaseURI();
}
public function getBaseURI() {
return null;
}
public function getIconURI() {
return PhabricatorUser::getDefaultProfileImageURI();
}
/* -( URI Routing )-------------------------------------------------------- */
public function getRoutes() {

View File

@@ -24,5 +24,13 @@ final class PhabricatorApplicationDifferential extends PhabricatorApplication {
);
}
public function getBaseURI() {
return '/differential/';
}
public function getShortDescription() {
return 'Code Review Application';
}
}

View File

@@ -28,7 +28,10 @@ final class PhabricatorTypeaheadCommonDatasourceController
$request = $this->getRequest();
$query = $request->getStr('q');
$need_rich_data = false;
$need_users = false;
$need_applications = false;
$need_all_users = false;
$need_lists = false;
$need_projs = false;
@@ -38,6 +41,11 @@ final class PhabricatorTypeaheadCommonDatasourceController
$need_arcanist_projects = false;
$need_noproject = false;
switch ($this->type) {
case 'mainsearch':
$need_users = true;
$need_applications = true;
$need_rich_data = true;
break;
case 'searchowner':
$need_users = true;
$need_upforgrabs = true;
@@ -78,9 +86,20 @@ final class PhabricatorTypeaheadCommonDatasourceController
case 'arcanistprojects':
$need_arcanist_projects = true;
break;
}
// TODO: We transfer these fields without keys as an opitimization, but this
// function is hard to read as a result. Until we can sort it out, here's
// what the position arguments mean:
//
// 0: (required) name to match against what the user types
// 1: (optional) URI
// 2: (required) PHID
// 3: (optional) priority matching string
// 4: (optional) display name [overrides position 0]
// 5: (optional) display type
// 6: (optional) image URI
$data = array();
if ($need_upforgrabs) {
@@ -106,11 +125,16 @@ final class PhabricatorTypeaheadCommonDatasourceController
'userName',
'realName',
'phid');
if ($need_rich_data) {
$columns[] = 'profileImagePHID';
}
if ($query) {
$conn_r = id(new PhabricatorUser())->establishConnection('r');
$ids = queryfx_all(
$conn_r,
'SELECT DISTINCT userID FROM %T WHERE token LIKE %>',
'SELECT DISTINCT userID FROM %T WHERE token LIKE %> OR 1 = 1',
PhabricatorUser::NAMETOKEN_TABLE,
$query);
$ids = ipull($ids, 'userID');
@@ -125,6 +149,12 @@ final class PhabricatorTypeaheadCommonDatasourceController
} else {
$users = id(new PhabricatorUser())->loadColumns($columns);
}
if ($need_rich_data) {
$phids = mpull($users, 'getPHID');
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
}
foreach ($users as $user) {
if (!$need_all_users) {
if ($user->getIsSystemAgent()) {
@@ -134,12 +164,18 @@ final class PhabricatorTypeaheadCommonDatasourceController
continue;
}
}
$data[] = array(
$spec = array(
$user->getUsername().' ('.$user->getRealName().')',
'/p/'.$user->getUsername(),
$user->getPHID(),
$user->getUsername(),
null,
'User',
);
if ($need_rich_data) {
$spec[] = $handles[$user->getPHID()]->getImageURI();
}
$data[] = $spec;
}
}
@@ -201,6 +237,33 @@ final class PhabricatorTypeaheadCommonDatasourceController
}
}
if ($need_applications) {
$applications = PhabricatorApplication::getAllInstalledApplications();
foreach ($applications as $application) {
$uri = $application->getTypeaheadURI();
if (!$uri) {
continue;
}
$data[] = array(
$application->getName().' '.$application->getShortDescription(),
$uri,
$application->getPHID(),
$application->getName(),
$application->getName(),
$application->getShortDescription(),
$application->getIconURI(),
);
}
}
if (!$need_rich_data) {
foreach ($data as $key => $info) {
unset($data[$key][4]);
unset($data[$key][5]);
unset($data[$key][6]);
}
}
return id(new AphrontAjaxResponse())
->setContent($data);
}

View File

@@ -42,6 +42,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
public function render() {
$user = $this->user;
$target_id = celerity_generate_unique_node_id();
$search_id = $this->getID();
$input = phutil_render_tag(
@@ -49,16 +50,28 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
array(
'type' => 'text',
'name' => 'query',
'id' => $search_id,
'id' => $search_id,
'autocomplete' => 'off',
));
$scope = $this->scope;
Javelin::initBehavior(
'placeholder',
$target = javelin_render_tag(
'div',
array(
'id' => $search_id,
'text' => PhabricatorSearchScope::getScopePlaceholder($scope),
'id' => $target_id,
'class' => 'phabricator-main-menu-search-target',
),
'');
Javelin::initBehavior(
'phabricator-search-typeahead',
array(
'id' => $target_id,
'input' => $search_id,
'src' => '/typeahead/common/mainsearch/',
'limit' => 10,
'placeholder' => PhabricatorSearchScope::getScopePlaceholder($scope),
));
$scope_input = phutil_render_tag(
@@ -79,6 +92,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
$input.
'<button>Search</button>'.
$scope_input.
$target.
'</div>');
$group = new PhabricatorMainMenuGroupView();