1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

Allow Maniphest tasks to be queried by workboard Column PHID via SearchEngine

Summary:
Ref T13120. See PHI571. Fixes T5024. This adds a "View as Query" action to workboard columns, which builds a query in Maniphest that has the current query constraints plus an additional constraint to select only tasks in the specified column.

This is a normal query and can be turned into a dashboard panel, added to a menu, edited, saved as a link, etc.

Much of the complexity here is that finding tasks in a given column isn't entirely straightforward because of how board layout works: when you create a task, it isn't immediately placed in columns. It's only actually added to the "Backlog" column on any boards when someone looks at the board.

To get the right behavior, we must do "board layout" for any queried columns before we can constrain results. This isn't enormously efficient, but should be OK for reasonable boards.

Test Plan:
  - Used "View as Query" for normal columns and milestome columns, got appropriate queries in Maniphest.
  - Applied filters to the board (e.g., "Priorities: wishlist"), then used "View As Query" and had my custom filters respected.
  - Queried some large boards/columns with more than a thousand tasks, got results back within a second or so.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13120, T5024

Differential Revision: https://secure.phabricator.com/D19366
This commit is contained in:
epriestley 2018-04-13 06:20:09 -07:00
parent ca49fffc1b
commit c46be2a70b
3 changed files with 149 additions and 3 deletions

View file

@ -26,6 +26,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $closedEpochMin;
private $closedEpochMax;
private $closerPHIDs;
private $columnPHIDs;
private $status = 'status-any';
const STATUS_ANY = 'status-any';
@ -213,6 +214,11 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this;
}
public function withColumnPHIDs(array $column_phids) {
$this->columnPHIDs = $column_phids;
return $this;
}
public function newResultObject() {
return new ManiphestTask();
}
@ -442,6 +448,91 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$this->subtypes);
}
if ($this->columnPHIDs !== null) {
$viewer = $this->getViewer();
$columns = id(new PhabricatorProjectColumnQuery())
->setParentQuery($this)
->setViewer($viewer)
->withPHIDs($this->columnPHIDs)
->execute();
if (!$columns) {
throw new PhabricatorEmptyQueryException();
}
// We must do board layout before we move forward because the column
// positions may not yet exist otherwise. An example is that newly
// created tasks may not yet be positioned in the backlog column.
$projects = mpull($columns, 'getProject');
$projects = mpull($projects, null, 'getPHID');
// The board layout engine needs to know about every object that it's
// going to be asked to do layout for. For now, we're just doing layout
// on every object on the boards. In the future, we could do layout on a
// smaller set of objects by using the constraints on this Query. For
// example, if the caller is only asking for open tasks, we only need
// to do layout on open tasks.
// This fetches too many objects (every type of object tagged with the
// project, not just tasks). We could narrow it by querying the edge
// table on the Maniphest side, but there's currently no way to build
// that query with EdgeQuery.
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array_keys($projects))
->withEdgeTypes(
array(
PhabricatorProjectProjectHasObjectEdgeType::EDGECONST,
));
$edge_query->execute();
$all_phids = $edge_query->getDestinationPHIDs();
// Since we overfetched PHIDs, filter out any non-tasks we got back.
foreach ($all_phids as $key => $phid) {
if (phid_get_type($phid) !== ManiphestTaskPHIDType::TYPECONST) {
unset($all_phids[$key]);
}
}
// If there are no tasks on the relevant boards, this query can't
// possibly hit anything so we're all done.
$task_phids = array_fuse($all_phids);
if (!$task_phids) {
throw new PhabricatorEmptyQueryException();
}
// We know everything we need to know, so perform board layout.
$engine = id(new PhabricatorBoardLayoutEngine())
->setViewer($viewer)
->setFetchAllBoards(true)
->setBoardPHIDs(array_keys($projects))
->setObjectPHIDs($task_phids)
->executeLayout();
// Find the tasks that are in the constraint columns after board layout
// completes.
$select_phids = array();
foreach ($columns as $column) {
$in_column = $engine->getColumnObjectPHIDs(
$column->getProjectPHID(),
$column->getPHID());
foreach ($in_column as $phid) {
$select_phids[$phid] = $phid;
}
}
if (!$select_phids) {
throw new PhabricatorEmptyQueryException();
}
$where[] = qsprintf(
$conn,
'task.phid IN (%Ls)',
$select_phids);
}
return $where;
}

View file

@ -86,6 +86,10 @@ final class ManiphestTaskSearchEngine
pht('Search for tasks with given subtypes.'))
->setDatasource(new ManiphestTaskSubtypeDatasource())
->setIsHidden($hide_subtypes),
id(new PhabricatorPHIDsSearchField())
->setLabel(pht('Columns'))
->setKey('columnPHIDs')
->setAliases(array('column', 'columnPHID', 'columns')),
id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Open Parents'))
->setKey('hasParents')
@ -246,6 +250,10 @@ final class ManiphestTaskSearchEngine
$query->withSubtaskIDs($map['subtaskIDs']);
}
if ($map['columnPHIDs']) {
$query->withColumnPHIDs($map['columnPHIDs']);
}
$group = idx($map, 'group');
$group = idx($this->getGroupValues(), $group);
if ($group) {

View file

@ -36,7 +36,8 @@ final class PhabricatorProjectBoardViewController
if ($request->isFormPost()
&& !$request->getBool('initialize')
&& !$request->getStr('move')) {
&& !$request->getStr('move')
&& !$request->getStr('queryColumnID')) {
$saved = $search_engine->buildSavedQueryFromRequest($request);
$search_engine->saveQuery($saved);
$filter_form = id(new AphrontFormView())
@ -188,6 +189,46 @@ final class PhabricatorProjectBoardViewController
->appendChild($content);
}
// If the user wants to turn a particular column into a query, build an
// apropriate filter and redirect them to the query results page.
$query_column_id = $request->getInt('queryColumnID');
if ($query_column_id) {
$column_id_map = mpull($columns, null, 'getID');
$query_column = idx($column_id_map, $query_column_id);
if (!$query_column) {
return new Aphront404Response();
}
// Create a saved query to combine the active filter on the workboard
// with the column filter. If the user currently has constraints on the
// board, we want to add a new column or project constraint, not
// completely replace the constraints.
$saved_query = clone $saved;
if ($query_column->getProxyPHID()) {
$project_phids = $saved_query->getParameter('projectPHIDs');
if (!$project_phids) {
$project_phids = array();
}
$project_phids[] = $query_column->getProxyPHID();
$saved_query->setParameter('projectPHIDs', $project_phids);
} else {
$saved_query->setParameter(
'columnPHIDs',
array($query_column->getPHID()));
}
$search_engine = id(new ManiphestTaskSearchEngine())
->setViewer($viewer);
$search_engine->saveQuery($saved_query);
$query_key = $saved_query->getQueryKey();
$query_uri = new PhutilURI("/maniphest/query/{$query_key}/#R");
return id(new AphrontRedirectResponse())
->setURI($query_uri);
}
$task_can_edit_map = id(new PhabricatorPolicyFilter())
->setViewer($viewer)
->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
@ -1069,8 +1110,14 @@ final class PhabricatorProjectBoardViewController
->setHref($batch_move_uri)
->setWorkflow(true);
// Column Related Actions Below
//
$query_uri = $request->getRequestURI();
$query_uri->setQueryParam('queryColumnID', $column->getID());
$column_items[] = id(new PhabricatorActionView())
->setName(pht('View as Query'))
->setIcon('fa-search')
->setHref($query_uri);
$edit_uri = 'board/'.$this->id.'/edit/'.$column->getID().'/';
$column_items[] = id(new PhabricatorActionView())
->setName(pht('Edit Column'))