mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-29 17:00:59 +01:00
Modernize "owner" typeahead datasource
Summary: Ref T4420. This one is users plus "upforgrabs". I renamed that to "none" and gave it a special visual style to make it more discoverable. Future diffs will improve this. Test Plan: - Used it in global search. - Used it in batch editor. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T4420 Differential Revision: https://secure.phabricator.com/D9891
This commit is contained in:
parent
27daa116c2
commit
7f0fb63c44
8 changed files with 60 additions and 214 deletions
|
@ -2333,12 +2333,13 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php',
|
'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php',
|
||||||
'PhabricatorTrivialTestCase' => 'infrastructure/testing/__tests__/PhabricatorTrivialTestCase.php',
|
'PhabricatorTrivialTestCase' => 'infrastructure/testing/__tests__/PhabricatorTrivialTestCase.php',
|
||||||
'PhabricatorTwoColumnExample' => 'applications/uiexample/examples/PhabricatorTwoColumnExample.php',
|
'PhabricatorTwoColumnExample' => 'applications/uiexample/examples/PhabricatorTwoColumnExample.php',
|
||||||
'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php',
|
|
||||||
'PhabricatorTypeaheadCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php',
|
'PhabricatorTypeaheadCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php',
|
||||||
'PhabricatorTypeaheadDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php',
|
'PhabricatorTypeaheadDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php',
|
||||||
'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadDatasourceController.php',
|
'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadDatasourceController.php',
|
||||||
'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php',
|
'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php',
|
||||||
'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php',
|
'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php',
|
||||||
|
'PhabricatorTypeaheadNoOwnerDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadNoOwnerDatasource.php',
|
||||||
|
'PhabricatorTypeaheadOwnerDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadOwnerDatasource.php',
|
||||||
'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php',
|
'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php',
|
||||||
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php',
|
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php',
|
||||||
'PhabricatorUIConfigOptions' => 'applications/config/option/PhabricatorUIConfigOptions.php',
|
'PhabricatorUIConfigOptions' => 'applications/config/option/PhabricatorUIConfigOptions.php',
|
||||||
|
@ -5173,12 +5174,13 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||||
'PhabricatorTrivialTestCase' => 'PhabricatorTestCase',
|
'PhabricatorTrivialTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorTwoColumnExample' => 'PhabricatorUIExample',
|
'PhabricatorTwoColumnExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
|
|
||||||
'PhabricatorTypeaheadCompositeDatasource' => 'PhabricatorTypeaheadDatasource',
|
'PhabricatorTypeaheadCompositeDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
'PhabricatorTypeaheadDatasource' => 'Phobject',
|
'PhabricatorTypeaheadDatasource' => 'Phobject',
|
||||||
'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
|
'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
|
||||||
'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
|
'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
|
||||||
'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource',
|
'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
|
'PhabricatorTypeaheadNoOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
|
'PhabricatorTypeaheadOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||||
'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||||
'PhabricatorUIExampleRenderController' => 'PhabricatorController',
|
'PhabricatorUIExampleRenderController' => 'PhabricatorController',
|
||||||
|
|
|
@ -63,6 +63,7 @@ final class ManiphestBatchEditController extends ManiphestController {
|
||||||
|
|
||||||
$projects_source = new PhabricatorProjectDatasource();
|
$projects_source = new PhabricatorProjectDatasource();
|
||||||
$mailable_source = new PhabricatorMetaMTAMailableDatasource();
|
$mailable_source = new PhabricatorMetaMTAMailableDatasource();
|
||||||
|
$owner_source = new PhabricatorTypeaheadOwnerDatasource();
|
||||||
|
|
||||||
require_celerity_resource('maniphest-batch-editor');
|
require_celerity_resource('maniphest-batch-editor');
|
||||||
Javelin::initBehavior(
|
Javelin::initBehavior(
|
||||||
|
@ -76,9 +77,8 @@ final class ManiphestBatchEditController extends ManiphestController {
|
||||||
'placeholder' => $projects_source->getPlaceholderText(),
|
'placeholder' => $projects_source->getPlaceholderText(),
|
||||||
),
|
),
|
||||||
'owner' => array(
|
'owner' => array(
|
||||||
'src' => '/typeahead/common/searchowner/',
|
'src' => $owner_source->getDatasourceURI(),
|
||||||
'placeholder' => pht(
|
'placeholder' => $owner_source->getPlaceholderText(),
|
||||||
'Type a user name or "upforgrabs" to unassign...'),
|
|
||||||
'limit' => 1,
|
'limit' => 1,
|
||||||
),
|
),
|
||||||
'cc' => array(
|
'cc' => array(
|
||||||
|
|
|
@ -142,7 +142,7 @@ final class PhabricatorSearchApplicationSearchEngine
|
||||||
id(new AphrontFormTokenizerControl())
|
id(new AphrontFormTokenizerControl())
|
||||||
->setName('ownerPHIDs')
|
->setName('ownerPHIDs')
|
||||||
->setLabel('Owners')
|
->setLabel('Owners')
|
||||||
->setDatasource('/typeahead/common/searchowner/')
|
->setDatasource(new PhabricatorTypeaheadOwnerDatasource())
|
||||||
->setValue($owner_handles))
|
->setValue($owner_handles))
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormCheckboxControl())
|
id(new AphrontFormCheckboxControl())
|
||||||
|
|
|
@ -5,8 +5,6 @@ final class PhabricatorApplicationTypeahead extends PhabricatorApplication {
|
||||||
public function getRoutes() {
|
public function getRoutes() {
|
||||||
return array(
|
return array(
|
||||||
'/typeahead/' => array(
|
'/typeahead/' => array(
|
||||||
'common/(?P<type>\w+)/'
|
|
||||||
=> 'PhabricatorTypeaheadCommonDatasourceController',
|
|
||||||
'class/(?:(?P<class>\w+)/)?'
|
'class/(?:(?P<class>\w+)/)?'
|
||||||
=> 'PhabricatorTypeaheadModularDatasourceController',
|
=> 'PhabricatorTypeaheadModularDatasourceController',
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
final class PhabricatorTypeaheadCommonDatasourceController
|
|
||||||
extends PhabricatorTypeaheadDatasourceController {
|
|
||||||
|
|
||||||
private $type;
|
|
||||||
|
|
||||||
public function shouldAllowPublic() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function willProcessRequest(array $data) {
|
|
||||||
$this->type = $data['type'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function processRequest() {
|
|
||||||
$request = $this->getRequest();
|
|
||||||
$viewer = $request->getUser();
|
|
||||||
$query = $request->getStr('q');
|
|
||||||
$raw_query = $request->getStr('raw');
|
|
||||||
|
|
||||||
$need_users = false;
|
|
||||||
$need_upforgrabs = false;
|
|
||||||
$need_rich_data = false;
|
|
||||||
switch ($this->type) {
|
|
||||||
case 'searchowner':
|
|
||||||
$need_users = true;
|
|
||||||
$need_upforgrabs = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = array();
|
|
||||||
|
|
||||||
if ($need_upforgrabs) {
|
|
||||||
$results[] = id(new PhabricatorTypeaheadResult())
|
|
||||||
->setName('upforgrabs (Up For Grabs)')
|
|
||||||
->setPHID(ManiphestTaskOwner::OWNER_UP_FOR_GRABS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($need_users) {
|
|
||||||
$columns = array(
|
|
||||||
'isSystemAgent',
|
|
||||||
'isAdmin',
|
|
||||||
'isDisabled',
|
|
||||||
'userName',
|
|
||||||
'realName',
|
|
||||||
'phid');
|
|
||||||
|
|
||||||
if ($query) {
|
|
||||||
// This is an arbitrary limit which is just larger than any limit we
|
|
||||||
// actually use in the application.
|
|
||||||
|
|
||||||
// TODO: The datasource should pass this in the query.
|
|
||||||
$limit = 15;
|
|
||||||
|
|
||||||
$user_table = new PhabricatorUser();
|
|
||||||
$conn_r = $user_table->establishConnection('r');
|
|
||||||
$ids = queryfx_all(
|
|
||||||
$conn_r,
|
|
||||||
'SELECT id FROM %T WHERE username LIKE %>
|
|
||||||
ORDER BY username ASC LIMIT %d',
|
|
||||||
$user_table->getTableName(),
|
|
||||||
$query,
|
|
||||||
$limit);
|
|
||||||
$ids = ipull($ids, 'id');
|
|
||||||
|
|
||||||
if (count($ids) < $limit) {
|
|
||||||
// If we didn't find enough username hits, look for real name hits.
|
|
||||||
// We need to pull the entire pagesize so that we end up with the
|
|
||||||
// right number of items if this query returns many duplicate IDs
|
|
||||||
// that we've already selected.
|
|
||||||
|
|
||||||
$realname_ids = queryfx_all(
|
|
||||||
$conn_r,
|
|
||||||
'SELECT DISTINCT userID FROM %T WHERE token LIKE %>
|
|
||||||
ORDER BY token ASC LIMIT %d',
|
|
||||||
PhabricatorUser::NAMETOKEN_TABLE,
|
|
||||||
$query,
|
|
||||||
$limit);
|
|
||||||
$realname_ids = ipull($realname_ids, 'userID');
|
|
||||||
$ids = array_merge($ids, $realname_ids);
|
|
||||||
|
|
||||||
$ids = array_unique($ids);
|
|
||||||
$ids = array_slice($ids, 0, $limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always add the logged-in user because some tokenizers autosort them
|
|
||||||
// first. They'll be filtered out on the client side if they don't
|
|
||||||
// match the query.
|
|
||||||
$ids[] = $request->getUser()->getID();
|
|
||||||
|
|
||||||
if ($ids) {
|
|
||||||
$users = id(new PhabricatorUser())->loadColumnsWhere(
|
|
||||||
$columns,
|
|
||||||
'id IN (%Ld)',
|
|
||||||
$ids);
|
|
||||||
} else {
|
|
||||||
$users = array();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$users = id(new PhabricatorUser())->loadColumns($columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($need_rich_data) {
|
|
||||||
$phids = mpull($users, 'getPHID');
|
|
||||||
$handles = $this->loadViewerHandles($phids);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($users as $user) {
|
|
||||||
$closed = null;
|
|
||||||
if ($user->getIsDisabled()) {
|
|
||||||
$closed = pht('Disabled');
|
|
||||||
} else if ($user->getIsSystemAgent()) {
|
|
||||||
$closed = pht('Bot/Script');
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = id(new PhabricatorTypeaheadResult())
|
|
||||||
->setName($user->getFullName())
|
|
||||||
->setURI('/p/'.$user->getUsername())
|
|
||||||
->setPHID($user->getPHID())
|
|
||||||
->setPriorityString($user->getUsername())
|
|
||||||
->setIcon('fa-user bluegrey')
|
|
||||||
->setPriorityType('user')
|
|
||||||
->setClosed($closed);
|
|
||||||
|
|
||||||
if ($need_rich_data) {
|
|
||||||
$display_type = 'User';
|
|
||||||
if ($user->getIsAdmin()) {
|
|
||||||
$display_type = 'Administrator';
|
|
||||||
}
|
|
||||||
$result->setDisplayType($display_type);
|
|
||||||
$result->setImageURI($handles[$user->getPHID()]->getImageURI());
|
|
||||||
}
|
|
||||||
$results[] = $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$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.
|
|
||||||
|
|
||||||
$rows = array();
|
|
||||||
foreach ($results as $result) {
|
|
||||||
$wire = $result->getWireFormat();
|
|
||||||
$rows[] = $wire;
|
|
||||||
}
|
|
||||||
|
|
||||||
$table = new AphrontTableView($rows);
|
|
||||||
$table->setHeaders(
|
|
||||||
array(
|
|
||||||
'Name',
|
|
||||||
'URI',
|
|
||||||
'PHID',
|
|
||||||
'Priority',
|
|
||||||
'Display Name',
|
|
||||||
'Display Type',
|
|
||||||
'Image URI',
|
|
||||||
'Priority Type',
|
|
||||||
'Sprite Class',
|
|
||||||
));
|
|
||||||
|
|
||||||
$panel = new AphrontPanelView();
|
|
||||||
$panel->setHeader('Typeahead Results');
|
|
||||||
$panel->appendChild($table);
|
|
||||||
|
|
||||||
return $this->buildStandardPageResponse(
|
|
||||||
$panel,
|
|
||||||
array(
|
|
||||||
'title' => pht('Typeahead Results'),
|
|
||||||
'device' => true
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorTypeaheadNoOwnerDatasource
|
||||||
|
extends PhabricatorTypeaheadDatasource {
|
||||||
|
|
||||||
|
public function getPlaceholderText() {
|
||||||
|
return pht('Type "none"...');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDatasourceApplicationClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadResults() {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$raw_query = $this->getRawQuery();
|
||||||
|
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
$results[] = id(new PhabricatorTypeaheadResult())
|
||||||
|
->setName(pht('None'))
|
||||||
|
->setIcon('fa-ban orange')
|
||||||
|
->setPHID(ManiphestTaskOwner::OWNER_UP_FOR_GRABS);
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorTypeaheadOwnerDatasource
|
||||||
|
extends PhabricatorTypeaheadCompositeDatasource {
|
||||||
|
|
||||||
|
public function getPlaceholderText() {
|
||||||
|
return pht('Type a user name or "none"...');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComponentDatasources() {
|
||||||
|
return array(
|
||||||
|
new PhabricatorPeopleDatasource(),
|
||||||
|
new PhabricatorTypeaheadNoOwnerDatasource(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
|
||||||
private $limit;
|
private $limit;
|
||||||
private $placeholder;
|
private $placeholder;
|
||||||
|
|
||||||
public function setDatasource($datasource) {
|
public function setDatasource(PhabricatorTypeaheadDatasource $datasource) {
|
||||||
$this->datasource = $datasource;
|
$this->datasource = $datasource;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -43,8 +43,11 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
|
||||||
$id = celerity_generate_unique_node_id();
|
$id = celerity_generate_unique_node_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$placeholder = null;
|
||||||
if (!strlen($this->placeholder)) {
|
if (!strlen($this->placeholder)) {
|
||||||
$placeholder = $this->getDefaultPlaceholder();
|
if ($this->datasource) {
|
||||||
|
$placeholder = $this->datasource->getPlaceholderText();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$placeholder = $this->placeholder;
|
$placeholder = $this->placeholder;
|
||||||
}
|
}
|
||||||
|
@ -59,10 +62,9 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
|
||||||
$username = $this->user->getUsername();
|
$username = $this->user->getUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->datasource instanceof PhabricatorTypeaheadDatasource) {
|
$datasource_uri = null;
|
||||||
|
if ($this->datasource) {
|
||||||
$datasource_uri = $this->datasource->getDatasourceURI();
|
$datasource_uri = $this->datasource->getDatasourceURI();
|
||||||
} else {
|
|
||||||
$datasource_uri = $this->datasource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->disableBehavior) {
|
if (!$this->disableBehavior) {
|
||||||
|
@ -80,26 +82,4 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
|
||||||
return $template->render();
|
return $template->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getDefaultPlaceholder() {
|
|
||||||
$datasource = $this->datasource;
|
|
||||||
|
|
||||||
if ($datasource instanceof PhabricatorTypeaheadDatasource) {
|
|
||||||
return $datasource->getPlaceholderText();
|
|
||||||
}
|
|
||||||
|
|
||||||
$matches = null;
|
|
||||||
if (!preg_match('@^/typeahead/common/(.*)/$@', $datasource, $matches)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$request = $matches[1];
|
|
||||||
|
|
||||||
$map = array(
|
|
||||||
'searchowner' => pht('Type a user name...'),
|
|
||||||
);
|
|
||||||
|
|
||||||
return idx($map, $request);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue