Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/maniphest/controller/tasklist/ManiphestTaskListController.php b/src/applications/maniphest/controller/tasklist/ManiphestTaskListController.php
index b9889fcbd2..1cd0ccd3a3 100644
--- a/src/applications/maniphest/controller/tasklist/ManiphestTaskListController.php
+++ b/src/applications/maniphest/controller/tasklist/ManiphestTaskListController.php
@@ -1,620 +1,651 @@
<?php
/*
* Copyright 2012 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.
*/
/**
* @group maniphest
*/
final class ManiphestTaskListController extends ManiphestController {
const DEFAULT_PAGE_SIZE = 1000;
private $view;
public function willProcessRequest(array $data) {
$this->view = idx($data, 'view');
}
private function getArrToStrList($key) {
$arr = $this->getRequest()->getArr($key);
$arr = implode(',', $arr);
return nonempty($arr, null);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($request->isFormPost()) {
// Redirect to GET so URIs can be copy/pasted.
$task_ids = $request->getStr('set_tasks');
$task_ids = nonempty($task_ids, null);
$uri = $request->getRequestURI()
->alter('users', $this->getArrToStrList('set_users'))
->alter('projects', $this->getArrToStrList('set_projects'))
->alter('xprojects', $this->getArrToStrList('set_xprojects'))
->alter('owners', $this->getArrToStrList('set_owners'))
->alter('authors', $this->getArrToStrList('set_authors'))
->alter('tasks', $task_ids);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$nav = $this->buildBaseSideNav();
$this->view = $nav->selectFilter($this->view, 'action');
$has_filter = array(
'action' => true,
'created' => true,
'subscribed' => true,
'triage' => true,
'projecttriage' => true,
'projectall' => true,
);
list($status_map, $status_control) = $this->renderStatusLinks();
list($grouping, $group_control) = $this->renderGroupLinks();
list($order, $order_control) = $this->renderOrderLinks();
$user_phids = $request->getStrList(
'users',
array($user->getPHID()));
if ($this->view == 'projecttriage' || $this->view == 'projectall') {
$project_query = new PhabricatorProjectQuery();
$project_query->setMembers($user_phids);
$projects = $project_query->execute();
$project_phids = mpull($projects, 'getPHID');
} else {
$project_phids = $request->getStrList('projects');
}
$exclude_project_phids = $request->getStrList('xprojects');
$task_ids = $request->getStrList('tasks');
$owner_phids = $request->getStrList('owners');
$author_phids = $request->getStrList('authors');
$page = $request->getInt('page');
$page_size = self::DEFAULT_PAGE_SIZE;
$query = new PhabricatorSearchQuery();
$query->setQuery('<<maniphest>>');
$query->setParameters(
array(
'view' => $this->view,
'userPHIDs' => $user_phids,
'projectPHIDs' => $project_phids,
'excludeProjectPHIDs' => $exclude_project_phids,
'ownerPHIDs' => $owner_phids,
'authorPHIDs' => $author_phids,
'taskIDs' => $task_ids,
'group' => $grouping,
'order' => $order,
'offset' => $page,
'limit' => $page_size,
'status' => $status_map,
));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$query->save();
unset($unguarded);
list($tasks, $handles, $total_count) = self::loadTasks($query);
$form = id(new AphrontFormView())
->setUser($user)
->setAction($request->getRequestURI());
if (isset($has_filter[$this->view])) {
$tokens = array();
foreach ($user_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchowner/')
->setName('set_users')
->setLabel('Users')
->setValue($tokens));
}
if ($this->view == 'custom') {
$form->appendChild(
id(new AphrontFormTextControl())
->setName('set_tasks')
->setLabel('Task IDs')
->setValue(join(',', $task_ids))
);
$tokens = array();
foreach ($owner_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchowner/')
->setName('set_owners')
->setLabel('Owners')
->setValue($tokens));
$tokens = array();
foreach ($author_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setName('set_authors')
->setLabel('Authors')
->setValue($tokens));
}
$tokens = array();
foreach ($project_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
if ($this->view != 'projectall' && $this->view != 'projecttriage') {
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchproject/')
->setName('set_projects')
->setLabel('Projects')
->setValue($tokens));
}
if ($this->view == 'custom') {
$tokens = array();
foreach ($exclude_project_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/projects/')
->setName('set_xprojects')
->setLabel('Exclude Projects')
->setValue($tokens));
}
$form
->appendChild($status_control)
->appendChild($group_control)
->appendChild($order_control);
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Filter Tasks'));
$create_uri = new PhutilURI('/maniphest/task/create/');
if ($project_phids) {
// If we have project filters selected, use them as defaults for task
// creation.
$create_uri->setQueryParam('projects', implode(';', $project_phids));
}
$filter = new AphrontListFilterView();
$filter->addButton(
phutil_render_tag(
'a',
array(
'href' => (string)$create_uri,
'class' => 'green button',
),
'Create New Task'));
$filter->appendChild($form);
$nav->appendChild($filter);
$have_tasks = false;
foreach ($tasks as $group => $list) {
if (count($list)) {
$have_tasks = true;
break;
}
}
require_celerity_resource('maniphest-task-summary-css');
$list_container = new AphrontNullView();
$list_container->appendChild('<div class="maniphest-list-container">');
if (!$have_tasks) {
$list_container->appendChild(
'<h1 class="maniphest-task-group-header">'.
'No matching tasks.'.
'</h1>');
} else {
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'page');
$pager->setPageSize($page_size);
$pager->setOffset($page);
$pager->setCount($total_count);
$cur = ($pager->getOffset() + 1);
$max = min($pager->getOffset() + $page_size, $total_count);
$tot = $total_count;
$cur = number_format($cur);
$max = number_format($max);
$tot = number_format($tot);
$list_container->appendChild(
'<div class="maniphest-total-result-count">'.
"Displaying tasks {$cur} - {$max} of {$tot}.".
'</div>');
$selector = new AphrontNullView();
foreach ($tasks as $group => $list) {
$task_list = new ManiphestTaskListView();
$task_list->setShowBatchControls(true);
$task_list->setUser($user);
$task_list->setTasks($list);
$task_list->setHandles($handles);
$count = number_format(count($list));
$selector->appendChild(
'<h1 class="maniphest-task-group-header">'.
phutil_escape_html($group).' ('.$count.')'.
'</h1>');
$selector->appendChild($task_list);
}
$selector->appendChild($this->renderBatchEditor($query));
$selector = phabricator_render_form(
$user,
array(
'method' => 'POST',
'action' => '/maniphest/batch/',
),
$selector->render());
$list_container->appendChild($selector);
$list_container->appendChild($pager);
}
$list_container->appendChild('</div>');
$nav->appendChild($list_container);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Task List',
));
}
public static function loadTasks(PhabricatorSearchQuery $search_query) {
$user_phids = $search_query->getParameter('userPHIDs', array());
$project_phids = $search_query->getParameter('projectPHIDs', array());
$task_ids = $search_query->getParameter('taskIDs', array());
$xproject_phids = $search_query->getParameter(
'excludeProjectPHIDs',
array());
$owner_phids = $search_query->getParameter('ownerPHIDs', array());
$author_phids = $search_query->getParameter('authorPHIDs', array());
$query = new ManiphestTaskQuery();
$query->withProjects($project_phids);
$query->withTaskIDs($task_ids);
if ($xproject_phids) {
$query->withoutProjects($xproject_phids);
}
if ($owner_phids) {
$query->withOwners($owner_phids);
}
if ($author_phids) {
$query->withAuthors($author_phids);
}
$status = $search_query->getParameter('status', 'all');
if (!empty($status['open']) && !empty($status['closed'])) {
$query->withStatus(ManiphestTaskQuery::STATUS_ANY);
} else if (!empty($status['open'])) {
$query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
} else {
$query->withStatus(ManiphestTaskQuery::STATUS_CLOSED);
}
switch ($search_query->getParameter('view')) {
case 'action':
$query->withOwners($user_phids);
break;
case 'created':
$query->withAuthors($user_phids);
break;
case 'subscribed':
$query->withSubscribers($user_phids);
break;
case 'triage':
$query->withOwners($user_phids);
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
break;
case 'alltriage':
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
break;
case 'all':
break;
case 'projecttriage':
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$query->withAnyProject(true);
break;
case 'projectall':
$query->withAnyProject(true);
break;
}
$order_map = array(
'priority' => ManiphestTaskQuery::ORDER_PRIORITY,
'created' => ManiphestTaskQuery::ORDER_CREATED,
);
$query->setOrderBy(
idx(
$order_map,
$search_query->getParameter('order'),
ManiphestTaskQuery::ORDER_MODIFIED));
$group_map = array(
'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
'owner' => ManiphestTaskQuery::GROUP_OWNER,
'status' => ManiphestTaskQuery::GROUP_STATUS,
+ 'project' => ManiphestTaskQuery::GROUP_PROJECT,
);
$query->setGroupBy(
idx(
$group_map,
$search_query->getParameter('group'),
ManiphestTaskQuery::GROUP_NONE));
$query->setCalculateRows(true);
$query->setLimit($search_query->getParameter('limit'));
$query->setOffset($search_query->getParameter('offset'));
$data = $query->execute();
$total_row_count = $query->getRowCount();
+ $project_group_phids = array();
+ if ($search_query->getParameter('group') == 'project') {
+ foreach ($data as $task) {
+ foreach ($task->getProjectPHIDs() as $phid) {
+ $project_group_phids[] = $phid;
+ }
+ }
+ }
+
$handle_phids = mpull($data, 'getOwnerPHID');
$handle_phids = array_merge(
$handle_phids,
$project_phids,
$user_phids,
$xproject_phids,
$owner_phids,
- $author_phids);
+ $author_phids,
+ $project_group_phids);
$handles = id(new PhabricatorObjectHandleData($handle_phids))
->loadHandles();
switch ($search_query->getParameter('group')) {
case 'priority':
$data = mgroup($data, 'getPriority');
- krsort($data);
// If we have invalid priorities, they'll all map to "???". Merge
// arrays to prevent them from overwriting each other.
$out = array();
foreach ($data as $pri => $tasks) {
$out[ManiphestTaskPriority::getTaskPriorityName($pri)][] = $tasks;
}
foreach ($out as $pri => $tasks) {
$out[$pri] = array_mergev($tasks);
}
$data = $out;
break;
case 'status':
$data = mgroup($data, 'getStatus');
- ksort($data);
$out = array();
foreach ($data as $status => $tasks) {
$out[ManiphestTaskStatus::getTaskStatusFullName($status)] = $tasks;
}
$data = $out;
break;
case 'owner':
$data = mgroup($data, 'getOwnerPHID');
$out = array();
foreach ($data as $phid => $tasks) {
if ($phid) {
$out[$handles[$phid]->getFullName()] = $tasks;
} else {
$out['Unassigned'] = $tasks;
}
}
if (isset($out['Unassigned'])) {
// If any tasks are unassigned, move them to the front of the list.
$data = array('Unassigned' => $out['Unassigned']) + $out;
} else {
$data = $out;
}
ksort($data);
break;
+ case 'project':
+ $grouped = array();
+ foreach ($data as $task) {
+ $phids = $task->getProjectPHIDs();
+ if ($project_phids) {
+ // If the user is filtering on "Bugs", don't show a "Bugs" group
+ // with every result since that's silly (the query also does this
+ // on the backend).
+ $phids = array_diff($phids, $project_phids);
+ }
+ if ($phids) {
+ foreach ($phids as $phid) {
+ $grouped[$handles[$phid]->getName()][$task->getID()] = $task;
+ }
+ } else {
+ $grouped['No Project'][$task->getID()] = $task;
+ }
+ }
+ $data = $grouped;
+ break;
default:
$data = array(
'Tasks' => $data,
);
break;
}
return array($data, $handles, $total_row_count);
}
public function renderStatusLinks() {
$request = $this->getRequest();
$statuses = array(
'o' => array('open' => true),
'c' => array('closed' => true),
'oc' => array('open' => true, 'closed' => true),
);
$status = $request->getStr('s');
if (empty($statuses[$status])) {
$status = 'o';
}
$status_control = id(new AphrontFormToggleButtonsControl())
->setLabel('Status')
->setValue($status)
->setBaseURI($request->getRequestURI(), 's')
->setButtons(
array(
'o' => 'Open',
'c' => 'Closed',
'oc' => 'All',
));
return array($statuses[$status], $status_control);
}
public function renderOrderLinks() {
$request = $this->getRequest();
$order = $request->getStr('o');
$orders = array(
'u' => 'updated',
'c' => 'created',
'p' => 'priority',
);
if (empty($orders[$order])) {
$order = 'p';
}
$order_by = $orders[$order];
$order_control = id(new AphrontFormToggleButtonsControl())
->setLabel('Order')
->setValue($order)
->setBaseURI($request->getRequestURI(), 'o')
->setButtons(
array(
'p' => 'Priority',
'u' => 'Updated',
'c' => 'Created',
));
return array($order_by, $order_control);
}
public function renderGroupLinks() {
$request = $this->getRequest();
$group = $request->getStr('g');
$groups = array(
'n' => 'none',
'p' => 'priority',
's' => 'status',
'o' => 'owner',
+ 'j' => 'project',
);
if (empty($groups[$group])) {
$group = 'p';
}
$group_by = $groups[$group];
$group_control = id(new AphrontFormToggleButtonsControl())
->setLabel('Group')
->setValue($group)
->setBaseURI($request->getRequestURI(), 'g')
->setButtons(
array(
'p' => 'Priority',
'o' => 'Owner',
's' => 'Status',
+ 'j' => 'Project',
'n' => 'None',
));
return array($group_by, $group_control);
}
private function renderBatchEditor(PhabricatorSearchQuery $search_query) {
Javelin::initBehavior(
'maniphest-batch-selector',
array(
'selectAll' => 'batch-select-all',
'selectNone' => 'batch-select-none',
'submit' => 'batch-select-submit',
'status' => 'batch-select-status-cell',
));
$select_all = javelin_render_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'class' => 'grey button',
'id' => 'batch-select-all',
),
'Select All');
$select_none = javelin_render_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'class' => 'grey button',
'id' => 'batch-select-none',
),
'Clear Selection');
$submit = phutil_render_tag(
'button',
array(
'id' => 'batch-select-submit',
'disabled' => 'disabled',
'class' => 'disabled',
),
'Batch Edit Selected Tasks &raquo;');
$export = javelin_render_tag(
'a',
array(
'href' => '/maniphest/export/'.$search_query->getQueryKey().'/',
'class' => 'grey button',
),
'Export Tasks to Excel...');
return
'<div class="maniphest-batch-editor">'.
'<div class="batch-editor-header">Batch Task Editor</div>'.
'<table class="maniphest-batch-editor-layout">'.
'<tr>'.
'<td>'.
$select_all.
$select_none.
'</td>'.
'<td>'.
$export.
'</td>'.
'<td id="batch-select-status-cell">'.
'0 Selected Tasks'.
'</td>'.
'<td class="batch-select-submit-cell">'.$submit.'</td>'.
'</tr>'.
'</table>'.
'</table>';
}
}
diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php
index 861a84b194..c0edae6c0f 100644
--- a/src/applications/maniphest/query/ManiphestTaskQuery.php
+++ b/src/applications/maniphest/query/ManiphestTaskQuery.php
@@ -1,463 +1,544 @@
<?php
/*
* Copyright 2012 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.
*
* @group maniphest
*/
final class ManiphestTaskQuery {
private $taskIDs = array();
private $authorPHIDs = array();
private $ownerPHIDs = array();
private $includeUnowned = null;
private $projectPHIDs = array();
private $xprojectPHIDs = array();
private $subscriberPHIDs = array();
private $anyProject = false;
private $includeNoProject = null;
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';
+ const GROUP_PROJECT = 'group-project';
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 withTaskIDs(array $ids) {
$this->taskIDs = $ids;
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->includeNoProject = false;
foreach ($projects as $k => $phid) {
if ($phid == ManiphestTaskOwner::PROJECT_NO_PROJECT) {
$this->includeNoProject = true;
unset($projects[$k]);
}
}
$this->projectPHIDs = $projects;
return $this;
}
public function withoutProjects(array $projects) {
$this->xprojectPHIDs = $projects;
return $this;
}
public function withStatus($status) {
$this->status = $status;
return $this;
}
public function withPriority($priority) {
$this->priority = $priority;
return $this;
}
public function withSubscribers(array $subscribers) {
$this->subscriberPHIDs = $subscribers;
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 withAnyProject($any_project) {
$this->anyProject = $any_project;
return $this;
}
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->buildTaskIDsWhereClause($conn);
$where[] = $this->buildStatusWhereClause($conn);
$where[] = $this->buildPriorityWhereClause($conn);
$where[] = $this->buildAuthorWhereClause($conn);
$where[] = $this->buildOwnerWhereClause($conn);
$where[] = $this->buildSubscriberWhereClause($conn);
$where[] = $this->buildProjectWhereClause($conn);
$where[] = $this->buildXProjectWhereClause($conn);
$where = array_filter($where);
if ($where) {
$where = 'WHERE ('.implode(') AND (', $where).')';
} else {
$where = '';
}
$join = array();
$join[] = $this->buildProjectJoinClause($conn);
$join[] = $this->buildXProjectJoinClause($conn);
$join[] = $this->buildSubscriberJoinClause($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. (If 'anyProject' is set,
// we do union instead.)
$group = 'GROUP BY task.id';
if (!$this->anyProject) {
$count = ', COUNT(project.projectPHID) 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);
+ if ($this->groupBy == self::GROUP_PROJECT) {
+ $limit = PHP_INT_MAX;
+ $offset = 0;
+ }
+
$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);
+ $tasks = $task_dao->loadAllFromArray($data);
+
+ if ($this->groupBy == self::GROUP_PROJECT) {
+ $tasks = $this->applyGroupByProject($tasks);
+ }
+
+ return $tasks;
}
private function buildTaskIDsWhereClause($conn) {
if (!$this->taskIDs) {
return null;
}
return qsprintf(
$conn,
'id in (%Ld)',
$this->taskIDs);
}
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 buildSubscriberWhereClause($conn) {
if (!$this->subscriberPHIDs) {
return null;
}
return qsprintf(
$conn,
'subscriber.subscriberPHID IN (%Ls)',
$this->subscriberPHIDs);
}
private function buildProjectWhereClause($conn) {
if (!$this->projectPHIDs && !$this->includeNoProject) {
return null;
}
$parts = array();
if ($this->projectPHIDs) {
$parts[] = qsprintf(
$conn,
'project.projectPHID in (%Ls)',
$this->projectPHIDs);
}
if ($this->includeNoProject) {
$parts[] = qsprintf(
$conn,
'project.projectPHID IS NULL');
}
return '('.implode(') OR (', $parts).')';
}
private function buildProjectJoinClause($conn) {
if (!$this->projectPHIDs && !$this->includeNoProject) {
return null;
}
$project_dao = new ManiphestTaskProject();
return qsprintf(
$conn,
'%Q JOIN %T project ON project.taskPHID = task.phid',
($this->includeNoProject ? 'LEFT' : ''),
$project_dao->getTableName());
}
private function buildXProjectWhereClause($conn) {
if (!$this->xprojectPHIDs) {
return null;
}
return qsprintf(
$conn,
'xproject.projectPHID IS NULL');
}
private function buildXProjectJoinClause($conn) {
if (!$this->xprojectPHIDs) {
return null;
}
$project_dao = new ManiphestTaskProject();
return qsprintf(
$conn,
'LEFT JOIN %T xproject ON xproject.taskPHID = task.phid
AND xproject.projectPHID IN (%Ls)',
$project_dao->getTableName(),
$this->xprojectPHIDs);
}
private function buildSubscriberJoinClause($conn) {
if (!$this->subscriberPHIDs) {
return null;
}
$subscriber_dao = new ManiphestTaskSubscriber();
return qsprintf(
$conn,
'JOIN %T subscriber ON subscriber.taskPHID = task.phid',
$subscriber_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;
+ case self::GROUP_PROJECT:
+ // NOTE: We have to load the entire result set and apply this grouping
+ // in the PHP process for now.
+ 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);
}
+ /**
+ * To get paging to work for "group by project", we need to do a bunch of
+ * server-side magic since there's currently no way to sort by project name on
+ * the database.
+ *
+ * TODO: Move this all to the database.
+ */
+ private function applyGroupByProject(array $tasks) {
+
+ $project_phids = array();
+ foreach ($tasks as $task) {
+ foreach ($task->getProjectPHIDs() as $phid) {
+ $project_phids[$phid] = true;
+ }
+ }
+
+ $handles = id(new PhabricatorObjectHandleData(array_keys($project_phids)))
+ ->loadHandles();
+
+ $max = 1;
+ foreach ($handles as $handle) {
+ $max = max($max, strlen($handle->getName()));
+ }
+
+ $items = array();
+ $ii = 0;
+ foreach ($tasks as $key => $task) {
+ $phids = $task->getProjectPHIDs();
+ if ($this->projectPHIDs) {
+ $phids = array_diff($phids, $this->projectPHIDs);
+ }
+ if ($phids) {
+ foreach ($phids as $phid) {
+ $items[] = array(
+ 'key' => $key,
+ 'seq' => sprintf(
+ '%'.$max.'s%d',
+ $handles[$phid]->getName(),
+ $ii),
+ );
+ }
+ } else {
+ // Sort "no project" tasks first.
+ $items[] = array(
+ 'key' => $key,
+ 'seq' => '',
+ );
+ }
+ ++$ii;
+ }
+
+ $items = isort($items, 'seq');
+ $items = array_slice(
+ $items,
+ nonempty($this->offset),
+ nonempty($this->limit, self::DEFAULT_PAGE_SIZE));
+
+ $result = array();
+ foreach ($items as $item) {
+ $result[] = $tasks[$item['key']];
+ }
+
+ return $result;
+ }
+
}
diff --git a/src/applications/maniphest/query/__init__.php b/src/applications/maniphest/query/__init__.php
index 86a637a392..bbd8f47297 100644
--- a/src/applications/maniphest/query/__init__.php
+++ b/src/applications/maniphest/query/__init__.php
@@ -1,19 +1,20 @@
<?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/subscriber');
phutil_require_module('phabricator', 'applications/maniphest/storage/task');
phutil_require_module('phabricator', 'applications/maniphest/storage/taskproject');
+phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'utils');
phutil_require_source('ManiphestTaskQuery.php');

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 20, 6:26 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
241493
Default Alt Text
(34 KB)

Event Timeline