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

Allow Maniphest tasks to be filtered by Project

Summary:
Major things taking place here:

  - A new table for storing <task, project> relationships.
  - Moved all task query logic into a dedicated class.
  - Added a "projects" filter to the UI.

I was originally going to try to drive this off the main search index but the
perf benefits of a custom schema make an overwhelming argument in favor of doing
it this way.

Test Plan:
Filtered tasks by author and owner and zero, one, and more than one project.
Exercised all the group/sort options. Ran the index script over my 100k task
corpus. Edited task-project membership and verified the index updated.

Reviewed By: cadamo
Reviewers: gc3, jungejason, cadamo, tuomaspelkonen, aran
CC: aran, cadamo, epriestley
Differential Revision: 556
This commit is contained in:
epriestley 2011-06-29 16:16:33 -07:00
parent 6a3eb19876
commit de0c89261e
12 changed files with 610 additions and 133 deletions

View file

@ -2,6 +2,11 @@ This is not a complete list of changes, just of API or workflow changes that may
break existing installs. Newer changes are listed at the top. If you pull new break existing installs. Newer changes are listed at the top. If you pull new
changes and things stop working, check here first! changes and things stop working, check here first!
June 29 2011 - Maniphest project indexes
Old Maniphest tasks will not appear in project filter views until you run
"scripts/search/reindex_maniphest.php" to build indexes. New tasks will have
their indexes built automatically.
May 31 2011 - Javelin submodule moved May 31 2011 - Javelin submodule moved
The externals/javelin submodule location has moved. If you have an older The externals/javelin submodule location has moved. If you have an older
checkout of Phabricator, you may need to edit .git/config to point at checkout of Phabricator, you may need to edit .git/config to point at

View file

@ -0,0 +1,6 @@
CREATE TABLE phabricator_maniphest.maniphest_taskproject (
taskPHID varchar(64) BINARY NOT NULL,
projectPHID varchar(64) BINARY NOT NULL,
PRIMARY KEY (projectPHID, taskPHID),
UNIQUE KEY (taskPHID, projectPHID)
);

View file

@ -0,0 +1,32 @@
#!/usr/bin/env php
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
require_once $root.'/scripts/__init_env__.php';
ini_set('memory_limit', -1);
$tasks = id(new ManiphestTask())->loadAll();
echo "Updating relationships for ".count($tasks)." tasks";
foreach ($tasks as $task) {
ManiphestTaskProject::updateTaskProjects($task);
echo '.';
}
echo "\nDone.\n";

View file

@ -272,6 +272,8 @@ phutil_register_library_map(array(
'ManiphestTaskListView' => 'applications/maniphest/view/tasklist', 'ManiphestTaskListView' => 'applications/maniphest/view/tasklist',
'ManiphestTaskOwner' => 'applications/maniphest/constants/owner', 'ManiphestTaskOwner' => 'applications/maniphest/constants/owner',
'ManiphestTaskPriority' => 'applications/maniphest/constants/priority', 'ManiphestTaskPriority' => 'applications/maniphest/constants/priority',
'ManiphestTaskProject' => 'applications/maniphest/storage/taskproject',
'ManiphestTaskQuery' => 'applications/maniphest/query',
'ManiphestTaskStatus' => 'applications/maniphest/constants/status', 'ManiphestTaskStatus' => 'applications/maniphest/constants/status',
'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary', 'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary',
'ManiphestTransaction' => 'applications/maniphest/storage/transaction', 'ManiphestTransaction' => 'applications/maniphest/storage/transaction',
@ -765,6 +767,7 @@ phutil_register_library_map(array(
'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskEditController' => 'ManiphestController',
'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListController' => 'ManiphestController',
'ManiphestTaskListView' => 'AphrontView', 'ManiphestTaskListView' => 'AphrontView',
'ManiphestTaskProject' => 'ManiphestDAO',
'ManiphestTaskSummaryView' => 'AphrontView', 'ManiphestTaskSummaryView' => 'AphrontView',
'ManiphestTransaction' => 'ManiphestDAO', 'ManiphestTransaction' => 'ManiphestDAO',
'ManiphestTransactionDetailView' => 'AphrontView', 'ManiphestTransactionDetailView' => 'AphrontView',

View file

@ -33,23 +33,30 @@ class ManiphestTaskListController extends ManiphestController {
$uri = $request->getRequestURI(); $uri = $request->getRequestURI();
if ($request->isFormPost()) { if ($request->isFormPost()) {
$phid_arr = $request->getArr('view_user'); // Redirect to GET so URIs can be copy/pasted.
$view_target = head($phid_arr);
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('phid', $view_target));
}
$user_phids = $request->getArr('set_users');
$proj_phids = $request->getArr('set_projects');
$user_phids = implode(',', $user_phids);
$proj_phids = implode(',', $proj_phids);
$user_phids = nonempty($user_phids, null);
$proj_phids = nonempty($proj_phids, null);
$uri = $request->getRequestURI()
->alter('users', $user_phids)
->alter('projects', $proj_phids);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$views = array( $views = array(
'User Tasks', 'User Tasks',
'action' => 'Assigned', 'action' => 'Assigned',
'created' => 'Created', 'created' => 'Created',
'triage' => 'Need Triage', 'triage' => 'Need Triage',
// 'touched' => 'Touched',
'<hr />', '<hr />',
'All Tasks', 'All Tasks',
'alltriage' => 'Need Triage', 'alltriage' => 'Need Triage',
'unassigned' => 'Unassigned',
'all' => 'All Tasks', 'all' => 'All Tasks',
); );
@ -77,7 +84,7 @@ class ManiphestTaskListController extends ManiphestController {
phutil_render_tag( phutil_render_tag(
'a', 'a',
array( array(
'href' => $uri, 'href' => $uri->alter('page', null),
'class' => ($this->view == $view) 'class' => ($this->view == $view)
? 'aphront-side-nav-selected' ? 'aphront-side-nav-selected'
: null, : null,
@ -90,13 +97,26 @@ class ManiphestTaskListController extends ManiphestController {
list($grouping, $group_links) = $this->renderGroupLinks(); list($grouping, $group_links) = $this->renderGroupLinks();
list($order, $order_links) = $this->renderOrderLinks(); list($order, $order_links) = $this->renderOrderLinks();
$view_phid = nonempty($request->getStr('phid'), $user->getPHID()); $user_phids = $request->getStr('users');
if (strlen($user_phids)) {
$user_phids = explode(',', $user_phids);
} else {
$user_phids = array($user->getPHID());
}
$project_phids = $request->getStr('projects');
if (strlen($project_phids)) {
$project_phids = explode(',', $project_phids);
} else {
$project_phids = array();
}
$page = $request->getInt('page'); $page = $request->getInt('page');
$page_size = self::DEFAULT_PAGE_SIZE; $page_size = self::DEFAULT_PAGE_SIZE;
list($tasks, $handles, $total_count) = $this->loadTasks( list($tasks, $handles, $total_count) = $this->loadTasks(
$view_phid, $user_phids,
$project_phids,
array( array(
'status' => $status_map, 'status' => $status_map,
'group' => $grouping, 'group' => $grouping,
@ -105,24 +125,34 @@ class ManiphestTaskListController extends ManiphestController {
'limit' => $page_size, 'limit' => $page_size,
)); ));
$form = id(new AphrontFormView()) $form = id(new AphrontFormView())
->setUser($user); ->setUser($user)
->setAction($request->getRequestURI());
if (isset($has_filter[$this->view])) { if (isset($has_filter[$this->view])) {
$tokens = array();
foreach ($user_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild( $form->appendChild(
id(new AphrontFormTokenizerControl()) id(new AphrontFormTokenizerControl())
->setLimit(1)
->setDatasource('/typeahead/common/searchowner/') ->setDatasource('/typeahead/common/searchowner/')
->setName('view_user') ->setName('set_users')
->setLabel('View User') ->setLabel('Users')
->setCaption('Use "upforgrabs" to find unassigned tasks.') ->setValue($tokens));
->setValue(
array(
$view_phid => $handles[$view_phid]->getFullName(),
)));
} }
$tokens = array();
foreach ($project_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/projects/')
->setName('set_projects')
->setLabel('Projects')
->setValue($tokens));
$form $form
->appendChild( ->appendChild(
id(new AphrontFormToggleButtonsControl()) id(new AphrontFormToggleButtonsControl())
@ -137,6 +167,10 @@ class ManiphestTaskListController extends ManiphestController {
->setLabel('Order') ->setLabel('Order')
->setValue($order_links)); ->setValue($order_links));
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Filter Tasks'));
$filter = new AphrontListFilterView(); $filter = new AphrontListFilterView();
$filter->addButton( $filter->addButton(
phutil_render_tag( phutil_render_tag(
@ -209,141 +243,71 @@ class ManiphestTaskListController extends ManiphestController {
)); ));
} }
private function loadTasks($view_phid, array $dict) { private function loadTasks(
$phids = array($view_phid); array $user_phids,
array $project_phids,
array $dict) {
$include_upforgrabs = false; $query = new ManiphestTaskQuery();
foreach ($phids as $key => $phid) { $query->withProjects($project_phids);
if ($phid == ManiphestTaskOwner::OWNER_UP_FOR_GRABS) {
unset($phids[$key]);
$include_upforgrabs = true;
}
}
$task = new ManiphestTask();
$argv = array();
$status = $dict['status']; $status = $dict['status'];
if (!empty($status['open']) && !empty($status['closed'])) { if (!empty($status['open']) && !empty($status['closed'])) {
$status_clause = '1 = 1'; $query->withStatus(ManiphestTaskQuery::STATUS_ANY);
} else if (!empty($status['open'])) { } else if (!empty($status['open'])) {
$status_clause = 'status = %d'; $query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$argv[] = 0;
} else { } else {
$status_clause = 'status > %d'; $query->withStatus(ManiphestTaskQuery::STATUS_CLOSED);
$argv[] = 0;
} }
$extra_clause = '1 = 1';
switch ($this->view) { switch ($this->view) {
case 'action': case 'action':
$parts = array(); $query->withOwners($user_phids);
if ($phids) {
$parts[] = 'ownerPHID in (%Ls)';
$argv[] = $phids;
}
if ($include_upforgrabs) {
$parts[] = 'ownerPHID IS NULL';
}
$extra_clause = '('.implode(' OR ', $parts).')';
break; break;
case 'created': case 'created':
$parts = array(); $query->withAuthors($user_phids);
if ($phids) {
$parts[] = 'authorPHID in (%Ls)';
$argv[] = $phids;
}
if ($include_upforgrabs) {
// This should be impossible since every task is supposed to have a
// valid author, but we might as well run the query.
$parts[] = 'authorPHID IS NULL';
}
$extra_clause = '('.implode(' OR ', $parts).')';
break; break;
case 'triage': case 'triage':
$parts = array(); $query->withOwners($user_phids);
if ($phids) { $query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$parts[] = 'ownerPHID in (%Ls)';
$argv[] = $phids;
}
if ($include_upforgrabs) {
$parts[] = 'ownerPHID IS NULL';
}
$extra_clause = '('.implode(' OR ', $parts).') AND priority = %d';
$argv[] = ManiphestTaskPriority::PRIORITY_TRIAGE;
break; break;
case 'alltriage': case 'alltriage':
$extra_clause = 'priority = %d'; $query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$argv[] = ManiphestTaskPriority::PRIORITY_TRIAGE;
break;
case 'unassigned':
$extra_clause = 'ownerPHID is NULL';
break; break;
case 'all': case 'all':
break; break;
} }
$order = array(); $order_map = array(
switch ($dict['group']) { 'priority' => ManiphestTaskQuery::ORDER_PRIORITY,
case 'priority': 'created' => ManiphestTaskQuery::ORDER_CREATED,
$order[] = 'priority'; );
break; $query->setOrderBy(
case 'owner': idx(
$order[] = 'ownerOrdering'; $order_map,
break; $dict['order'],
case 'status': ManiphestTaskQuery::ORDER_MODIFIED));
$order[] = 'status';
break;
}
switch ($dict['order']) { $group_map = array(
case 'priority': 'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
$order[] = 'priority'; 'owner' => ManiphestTaskQuery::GROUP_OWNER,
$order[] = 'dateModified'; 'status' => ManiphestTaskQuery::GROUP_STATUS,
break; );
case 'created': $query->setGroupBy(
$order[] = 'id'; idx(
break; $group_map,
default: $dict['group'],
$order[] = 'dateModified'; ManiphestTaskQuery::GROUP_NONE));
break;
}
$order = array_unique($order); $query->setCalculateRows(true);
$query->setLimit($dict['limit']);
$query->setOffset($dict['offset']);
foreach ($order as $k => $column) { $data = $query->execute();
switch ($column) { $total_row_count = $query->getRowCount();
case 'ownerOrdering':
$order[$k] = "{$column} ASC";
break;
default:
$order[$k] = "{$column} DESC";
break;
}
}
$order = implode(', ', $order);
$offset = (int)idx($dict, 'offset', 0);
$limit = (int)idx($dict, 'limit', self::DEFAULT_PAGE_SIZE);
$sql = "SELECT SQL_CALC_FOUND_ROWS * FROM %T WHERE ".
"({$status_clause}) AND ({$extra_clause}) ORDER BY {$order} ".
"LIMIT {$offset}, {$limit}";
array_unshift($argv, $task->getTableName());
$conn = $task->establishConnection('r');
$data = vqueryfx_all($conn, $sql, $argv);
$total_row_count = queryfx_one($conn, 'SELECT FOUND_ROWS() N');
$total_row_count = $total_row_count['N'];
$data = $task->loadAllFromArray($data);
$handle_phids = mpull($data, 'getOwnerPHID'); $handle_phids = mpull($data, 'getOwnerPHID');
$handle_phids[] = $view_phid; $handle_phids = array_merge($handle_phids, $project_phids, $user_phids);
$handles = id(new PhabricatorObjectHandleData($handle_phids)) $handles = id(new PhabricatorObjectHandleData($handle_phids))
->loadHandles(); ->loadHandles();

View file

@ -7,17 +7,16 @@
phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/maniphest/constants/owner');
phutil_require_module('phabricator', 'applications/maniphest/constants/priority'); phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
phutil_require_module('phabricator', 'applications/maniphest/constants/status'); phutil_require_module('phabricator', 'applications/maniphest/constants/status');
phutil_require_module('phabricator', 'applications/maniphest/controller/base'); phutil_require_module('phabricator', 'applications/maniphest/controller/base');
phutil_require_module('phabricator', 'applications/maniphest/storage/task'); phutil_require_module('phabricator', 'applications/maniphest/query');
phutil_require_module('phabricator', 'applications/maniphest/view/tasklist'); phutil_require_module('phabricator', 'applications/maniphest/view/tasklist');
phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phabricator', 'view/control/pager'); phutil_require_module('phabricator', 'view/control/pager');
phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/togglebuttons'); phutil_require_module('phabricator', 'view/form/control/togglebuttons');
phutil_require_module('phabricator', 'view/form/control/tokenizer'); phutil_require_module('phabricator', 'view/form/control/tokenizer');
phutil_require_module('phabricator', 'view/layout/listfilter'); phutil_require_module('phabricator', 'view/layout/listfilter');

View file

@ -0,0 +1,351 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Query tasks by specific criteria. This class uses the higher-performance
* but less-general Maniphest indexes to satisfy queries.
*/
final class ManiphestTaskQuery {
private $authorPHIDs = array();
private $ownerPHIDs = array();
private $includeUnowned = null;
private $projectPHIDs = array();
private $status = 'status-any';
const STATUS_ANY = 'status-any';
const STATUS_OPEN = 'status-open';
const STATUS_CLOSED = 'status-closed';
private $priority = null;
private $groupBy = 'group-none';
const GROUP_NONE = 'group-none';
const GROUP_PRIORITY = 'group-priority';
const GROUP_OWNER = 'group-owner';
const GROUP_STATUS = 'group-status';
private $orderBy = 'order-modified';
const ORDER_PRIORITY = 'order-priority';
const ORDER_CREATED = 'order-created';
const ORDER_MODIFIED = 'order-modified';
private $limit = null;
const DEFAULT_PAGE_SIZE = 1000;
private $offset = 0;
private $calculateRows = false;
private $rowCount = null;
public function withAuthors(array $authors) {
$this->authorPHIDs = $authors;
return $this;
}
public function withOwners(array $owners) {
$this->includeUnowned = false;
foreach ($owners as $k => $phid) {
if ($phid == ManiphestTaskOwner::OWNER_UP_FOR_GRABS) {
$this->includeUnowned = true;
unset($owners[$k]);
break;
}
}
$this->ownerPHIDs = $owners;
return $this;
}
public function withProjects(array $projects) {
$this->projectPHIDs = $projects;
return $this;
}
public function withStatus($status) {
$this->status = $status;
return $this;
}
public function withPriority($priority) {
$this->priority = $priority;
return $this;
}
public function setGroupBy($group) {
$this->groupBy = $group;
return $this;
}
public function setOrderBy($order) {
$this->orderBy = $order;
return $this;
}
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function setOffset($offset) {
$this->offset = $offset;
return $this;
}
public function setCalculateRows($calculate_rows) {
$this->calculateRows = $calculate_rows;
return $this;
}
public function getRowCount() {
if ($this->rowCount === null) {
throw new Exception(
"You must execute a query with setCalculateRows() before you can ".
"retrieve a row count.");
}
return $this->rowCount;
}
public function execute() {
$task_dao = new ManiphestTask();
$conn = $task_dao->establishConnection('r');
if ($this->calculateRows) {
$calc = 'SQL_CALC_FOUND_ROWS';
} else {
$calc = '';
}
$where = array();
$where[] = $this->buildStatusWhereClause($conn);
$where[] = $this->buildPriorityWhereClause($conn);
$where[] = $this->buildAuthorWhereClause($conn);
$where[] = $this->buildOwnerWhereClause($conn);
$where[] = $this->buildProjectWhereClause($conn);
$where = array_filter($where);
if ($where) {
$where = 'WHERE ('.implode(') AND (', $where).')';
} else {
$where = '';
}
$join = array();
$join[] = $this->buildProjectJoinClause($conn);
$join = array_filter($join);
if ($join) {
$join = implode(' ', $join);
} else {
$join = '';
}
$having = '';
$count = '';
$group = '';
if (count($this->projectPHIDs) > 1) {
// If we're searching for more than one project:
// - We'll get multiple rows for tasks when they join the project table
// multiple times. We use GROUP BY to make them distinct again.
// - We want to treat the query as an intersection query, not a union
// query. We sum the project count and require it be the same as the
// number of projects we're searching for.
$group = 'GROUP BY task.id';
$count = ', COUNT(1) projectCount';
$having = qsprintf(
$conn,
'HAVING projectCount = %d',
count($this->projectPHIDs));
}
$order = $this->buildOrderClause($conn);
$offset = (int)nonempty($this->offset, 0);
$limit = (int)nonempty($this->limit, self::DEFAULT_PAGE_SIZE);
$data = queryfx_all(
$conn,
'SELECT %Q * %Q FROM %T task %Q %Q %Q %Q %Q LIMIT %d, %d',
$calc,
$count,
$task_dao->getTableName(),
$join,
$where,
$group,
$having,
$order,
$offset,
$limit);
if ($this->calculateRows) {
$count = queryfx_one(
$conn,
'SELECT FOUND_ROWS() N');
$this->rowCount = $count['N'];
} else {
$this->rowCount = null;
}
return $task_dao->loadAllFromArray($data);
}
private function buildStatusWhereClause($conn) {
switch ($this->status) {
case self::STATUS_ANY:
return null;
case self::STATUS_OPEN:
return 'status = 0';
case self::STATUS_CLOSED:
return 'status > 0';
default:
throw new Exception("Unknown status query '{$this->status}'!");
}
}
private function buildPriorityWhereClause($conn) {
if ($this->priority === null) {
return null;
}
return qsprintf(
$conn,
'priority = %d',
$this->priority);
}
private function buildAuthorWhereClause($conn) {
if (!$this->authorPHIDs) {
return null;
}
return qsprintf(
$conn,
'authorPHID in (%Ls)',
$this->authorPHIDs);
}
private function buildOwnerWhereClause($conn) {
if (!$this->ownerPHIDs) {
if ($this->includeUnowned === null) {
return null;
} else if ($this->includeUnowned) {
return qsprintf(
$conn,
'ownerPHID IS NULL');
} else {
return qsprintf(
$conn,
'ownerPHID IS NOT NULL');
}
}
if ($this->includeUnowned) {
return qsprintf(
$conn,
'ownerPHID IN (%Ls) OR ownerPHID IS NULL',
$this->ownerPHIDs);
} else {
return qsprintf(
$conn,
'ownerPHID IN (%Ls)',
$this->ownerPHIDs);
}
}
private function buildProjectWhereClause($conn) {
if (!$this->projectPHIDs) {
return null;
}
return qsprintf(
$conn,
'project.projectPHID IN (%Ls)',
$this->projectPHIDs);
}
private function buildProjectJoinClause($conn) {
if (!$this->projectPHIDs) {
return null;
}
$project_dao = new ManiphestTaskProject();
return qsprintf(
$conn,
'JOIN %T project ON project.taskPHID = task.phid',
$project_dao->getTableName());
}
private function buildOrderClause($conn) {
$order = array();
switch ($this->groupBy) {
case self::GROUP_NONE:
break;
case self::GROUP_PRIORITY:
$order[] = 'priority';
break;
case self::GROUP_OWNER:
$order[] = 'ownerOrdering';
break;
case self::GROUP_STATUS:
$order[] = 'status';
break;
default:
throw new Exception("Unknown group query '{$this->groupBy}'!");
}
switch ($this->orderBy) {
case self::ORDER_PRIORITY:
$order[] = 'priority';
$order[] = 'dateModified';
break;
case self::ORDER_CREATED:
$order[] = 'id';
break;
case self::ORDER_MODIFIED:
$order[] = 'dateModified';
break;
default:
throw new Exception("Unknown order query '{$this->orderBy}'!");
}
$order = array_unique($order);
if (empty($order)) {
return null;
}
foreach ($order as $k => $column) {
switch ($column) {
case 'ownerOrdering':
$order[$k] = "task.{$column} ASC";
break;
default:
$order[$k] = "task.{$column} DESC";
break;
}
}
return 'ORDER BY '.implode(', ', $order);
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/maniphest/constants/owner');
phutil_require_module('phabricator', 'applications/maniphest/storage/task');
phutil_require_module('phabricator', 'applications/maniphest/storage/taskproject');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'utils');
phutil_require_source('ManiphestTaskQuery.php');

View file

@ -33,6 +33,7 @@ class ManiphestTask extends ManiphestDAO {
protected $attached = array(); protected $attached = array();
protected $projectPHIDs = array(); protected $projectPHIDs = array();
private $projectsNeedUpdate;
protected $ownerOrdering; protected $ownerOrdering;
@ -60,11 +61,27 @@ class ManiphestTask extends ManiphestDAO {
return nonempty($this->ccPHIDs, array()); return nonempty($this->ccPHIDs, array());
} }
public function setProjectPHIDs(array $phids) {
$this->projectPHIDs = $phids;
$this->projectsNeedUpdate = true;
return $this;
}
public function save() { public function save() {
if (!$this->mailKey) { if (!$this->mailKey) {
$this->mailKey = sha1(Filesystem::readRandomBytes(20)); $this->mailKey = sha1(Filesystem::readRandomBytes(20));
} }
return parent::save();
$result = parent::save();
if ($this->projectsNeedUpdate) {
// If we've changed the project PHIDs for this task, update the link
// table.
ManiphestTaskProject::updateTaskProjects($this);
$this->projectsNeedUpdate = false;
}
return $result;
} }
} }

View file

@ -7,6 +7,7 @@
phutil_require_module('phabricator', 'applications/maniphest/storage/base'); phutil_require_module('phabricator', 'applications/maniphest/storage/base');
phutil_require_module('phabricator', 'applications/maniphest/storage/taskproject');
phutil_require_module('phabricator', 'applications/phid/constants'); phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/storage/phid'); phutil_require_module('phabricator', 'applications/phid/storage/phid');

View file

@ -0,0 +1,67 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This is a DAO for the Task -> Project table, which denormalizes the
* relationship between tasks and projects into a link table so it can be
* efficiently queried. This table is not authoritative; the projectPHIDs field
* of ManiphestTask is. The rows in this table are regenerated when transactions
* are applied to tasks which affected their associated projects.
*
* @group maniphest
*/
final class ManiphestTaskProject extends ManiphestDAO {
protected $taskPHID;
protected $projectPHID;
public function getConfiguration() {
return array(
self::CONFIG_IDS => self::IDS_MANUAL,
self::CONFIG_TIMESTAMPS => false,
);
}
public static function updateTaskProjects(ManiphestTask $task) {
$dao = new ManiphestTaskProject();
$conn = $dao->establishConnection('w');
$sql = array();
foreach ($task->getProjectPHIDs() as $project_phid) {
$sql[] = qsprintf(
$conn,
'(%s, %s)',
$task->getPHID(),
$project_phid);
}
queryfx(
$conn,
'DELETE FROM %T WHERE taskPHID = %s',
$dao->getTableName(),
$task->getPHID());
if ($sql) {
queryfx(
$conn,
'INSERT INTO %T (taskPHID, projectPHID) VALUES %Q',
$dao->getTableName(),
implode(', ', $sql));
}
}
}

View file

@ -0,0 +1,14 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/maniphest/storage/base');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_source('ManiphestTaskProject.php');