@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+149 -3
+91
src/applications/maniphest/query/ManiphestTaskQuery.php
··· 26 26 private $closedEpochMin; 27 27 private $closedEpochMax; 28 28 private $closerPHIDs; 29 + private $columnPHIDs; 29 30 30 31 private $status = 'status-any'; 31 32 const STATUS_ANY = 'status-any'; ··· 210 211 211 212 public function withSubtypes(array $subtypes) { 212 213 $this->subtypes = $subtypes; 214 + return $this; 215 + } 216 + 217 + public function withColumnPHIDs(array $column_phids) { 218 + $this->columnPHIDs = $column_phids; 213 219 return $this; 214 220 } 215 221 ··· 440 446 $conn, 441 447 'task.subtype IN (%Ls)', 442 448 $this->subtypes); 449 + } 450 + 451 + 452 + if ($this->columnPHIDs !== null) { 453 + $viewer = $this->getViewer(); 454 + 455 + $columns = id(new PhabricatorProjectColumnQuery()) 456 + ->setParentQuery($this) 457 + ->setViewer($viewer) 458 + ->withPHIDs($this->columnPHIDs) 459 + ->execute(); 460 + if (!$columns) { 461 + throw new PhabricatorEmptyQueryException(); 462 + } 463 + 464 + // We must do board layout before we move forward because the column 465 + // positions may not yet exist otherwise. An example is that newly 466 + // created tasks may not yet be positioned in the backlog column. 467 + 468 + $projects = mpull($columns, 'getProject'); 469 + $projects = mpull($projects, null, 'getPHID'); 470 + 471 + // The board layout engine needs to know about every object that it's 472 + // going to be asked to do layout for. For now, we're just doing layout 473 + // on every object on the boards. In the future, we could do layout on a 474 + // smaller set of objects by using the constraints on this Query. For 475 + // example, if the caller is only asking for open tasks, we only need 476 + // to do layout on open tasks. 477 + 478 + // This fetches too many objects (every type of object tagged with the 479 + // project, not just tasks). We could narrow it by querying the edge 480 + // table on the Maniphest side, but there's currently no way to build 481 + // that query with EdgeQuery. 482 + $edge_query = id(new PhabricatorEdgeQuery()) 483 + ->withSourcePHIDs(array_keys($projects)) 484 + ->withEdgeTypes( 485 + array( 486 + PhabricatorProjectProjectHasObjectEdgeType::EDGECONST, 487 + )); 488 + 489 + $edge_query->execute(); 490 + $all_phids = $edge_query->getDestinationPHIDs(); 491 + 492 + // Since we overfetched PHIDs, filter out any non-tasks we got back. 493 + foreach ($all_phids as $key => $phid) { 494 + if (phid_get_type($phid) !== ManiphestTaskPHIDType::TYPECONST) { 495 + unset($all_phids[$key]); 496 + } 497 + } 498 + 499 + // If there are no tasks on the relevant boards, this query can't 500 + // possibly hit anything so we're all done. 501 + $task_phids = array_fuse($all_phids); 502 + if (!$task_phids) { 503 + throw new PhabricatorEmptyQueryException(); 504 + } 505 + 506 + // We know everything we need to know, so perform board layout. 507 + $engine = id(new PhabricatorBoardLayoutEngine()) 508 + ->setViewer($viewer) 509 + ->setFetchAllBoards(true) 510 + ->setBoardPHIDs(array_keys($projects)) 511 + ->setObjectPHIDs($task_phids) 512 + ->executeLayout(); 513 + 514 + // Find the tasks that are in the constraint columns after board layout 515 + // completes. 516 + $select_phids = array(); 517 + foreach ($columns as $column) { 518 + $in_column = $engine->getColumnObjectPHIDs( 519 + $column->getProjectPHID(), 520 + $column->getPHID()); 521 + foreach ($in_column as $phid) { 522 + $select_phids[$phid] = $phid; 523 + } 524 + } 525 + 526 + if (!$select_phids) { 527 + throw new PhabricatorEmptyQueryException(); 528 + } 529 + 530 + $where[] = qsprintf( 531 + $conn, 532 + 'task.phid IN (%Ls)', 533 + $select_phids); 443 534 } 444 535 445 536 return $where;
+8
src/applications/maniphest/query/ManiphestTaskSearchEngine.php
··· 86 86 pht('Search for tasks with given subtypes.')) 87 87 ->setDatasource(new ManiphestTaskSubtypeDatasource()) 88 88 ->setIsHidden($hide_subtypes), 89 + id(new PhabricatorPHIDsSearchField()) 90 + ->setLabel(pht('Columns')) 91 + ->setKey('columnPHIDs') 92 + ->setAliases(array('column', 'columnPHID', 'columns')), 89 93 id(new PhabricatorSearchThreeStateField()) 90 94 ->setLabel(pht('Open Parents')) 91 95 ->setKey('hasParents') ··· 244 248 245 249 if ($map['subtaskIDs']) { 246 250 $query->withSubtaskIDs($map['subtaskIDs']); 251 + } 252 + 253 + if ($map['columnPHIDs']) { 254 + $query->withColumnPHIDs($map['columnPHIDs']); 247 255 } 248 256 249 257 $group = idx($map, 'group');
+50 -3
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 36 36 37 37 if ($request->isFormPost() 38 38 && !$request->getBool('initialize') 39 - && !$request->getStr('move')) { 39 + && !$request->getStr('move') 40 + && !$request->getStr('queryColumnID')) { 40 41 $saved = $search_engine->buildSavedQueryFromRequest($request); 41 42 $search_engine->saveQuery($saved); 42 43 $filter_form = id(new AphrontFormView()) ··· 186 187 ->setNavigation($nav) 187 188 ->setCrumbs($crumbs) 188 189 ->appendChild($content); 190 + } 191 + 192 + // If the user wants to turn a particular column into a query, build an 193 + // apropriate filter and redirect them to the query results page. 194 + $query_column_id = $request->getInt('queryColumnID'); 195 + if ($query_column_id) { 196 + $column_id_map = mpull($columns, null, 'getID'); 197 + $query_column = idx($column_id_map, $query_column_id); 198 + if (!$query_column) { 199 + return new Aphront404Response(); 200 + } 201 + 202 + // Create a saved query to combine the active filter on the workboard 203 + // with the column filter. If the user currently has constraints on the 204 + // board, we want to add a new column or project constraint, not 205 + // completely replace the constraints. 206 + $saved_query = clone $saved; 207 + 208 + if ($query_column->getProxyPHID()) { 209 + $project_phids = $saved_query->getParameter('projectPHIDs'); 210 + if (!$project_phids) { 211 + $project_phids = array(); 212 + } 213 + $project_phids[] = $query_column->getProxyPHID(); 214 + $saved_query->setParameter('projectPHIDs', $project_phids); 215 + } else { 216 + $saved_query->setParameter( 217 + 'columnPHIDs', 218 + array($query_column->getPHID())); 219 + } 220 + 221 + $search_engine = id(new ManiphestTaskSearchEngine()) 222 + ->setViewer($viewer); 223 + $search_engine->saveQuery($saved_query); 224 + 225 + $query_key = $saved_query->getQueryKey(); 226 + $query_uri = new PhutilURI("/maniphest/query/{$query_key}/#R"); 227 + 228 + return id(new AphrontRedirectResponse()) 229 + ->setURI($query_uri); 189 230 } 190 231 191 232 $task_can_edit_map = id(new PhabricatorPolicyFilter()) ··· 1069 1110 ->setHref($batch_move_uri) 1070 1111 ->setWorkflow(true); 1071 1112 1072 - // Column Related Actions Below 1073 - // 1113 + $query_uri = $request->getRequestURI(); 1114 + $query_uri->setQueryParam('queryColumnID', $column->getID()); 1115 + 1116 + $column_items[] = id(new PhabricatorActionView()) 1117 + ->setName(pht('View as Query')) 1118 + ->setIcon('fa-search') 1119 + ->setHref($query_uri); 1120 + 1074 1121 $edit_uri = 'board/'.$this->id.'/edit/'.$column->getID().'/'; 1075 1122 $column_items[] = id(new PhabricatorActionView()) 1076 1123 ->setName(pht('Edit Column'))