Skip to content

Commit de0c892

Browse files
author
epriestley
committedJun 30, 2011
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
1 parent 6a3eb19 commit de0c892

File tree

12 files changed

+611
-134
lines changed

12 files changed

+611
-134
lines changed
 

‎CHANGELOG

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ This is not a complete list of changes, just of API or workflow changes that may
22
break existing installs. Newer changes are listed at the top. If you pull new
33
changes and things stop working, check here first!
44

5+
June 29 2011 - Maniphest project indexes
6+
Old Maniphest tasks will not appear in project filter views until you run
7+
"scripts/search/reindex_maniphest.php" to build indexes. New tasks will have
8+
their indexes built automatically.
9+
510
May 31 2011 - Javelin submodule moved
611
The externals/javelin submodule location has moved. If you have an older
712
checkout of Phabricator, you may need to edit .git/config to point at
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE TABLE phabricator_maniphest.maniphest_taskproject (
2+
taskPHID varchar(64) BINARY NOT NULL,
3+
projectPHID varchar(64) BINARY NOT NULL,
4+
PRIMARY KEY (projectPHID, taskPHID),
5+
UNIQUE KEY (taskPHID, projectPHID)
6+
);

‎scripts/search/reindex_maniphest.php

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
/*
5+
* Copyright 2011 Facebook, Inc.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
$root = dirname(dirname(dirname(__FILE__)));
21+
require_once $root.'/scripts/__init_script__.php';
22+
require_once $root.'/scripts/__init_env__.php';
23+
24+
ini_set('memory_limit', -1);
25+
$tasks = id(new ManiphestTask())->loadAll();
26+
echo "Updating relationships for ".count($tasks)." tasks";
27+
foreach ($tasks as $task) {
28+
ManiphestTaskProject::updateTaskProjects($task);
29+
echo '.';
30+
}
31+
echo "\nDone.\n";
32+

‎src/__phutil_library_map__.php

+3
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@
272272
'ManiphestTaskListView' => 'applications/maniphest/view/tasklist',
273273
'ManiphestTaskOwner' => 'applications/maniphest/constants/owner',
274274
'ManiphestTaskPriority' => 'applications/maniphest/constants/priority',
275+
'ManiphestTaskProject' => 'applications/maniphest/storage/taskproject',
276+
'ManiphestTaskQuery' => 'applications/maniphest/query',
275277
'ManiphestTaskStatus' => 'applications/maniphest/constants/status',
276278
'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary',
277279
'ManiphestTransaction' => 'applications/maniphest/storage/transaction',
@@ -765,6 +767,7 @@
765767
'ManiphestTaskEditController' => 'ManiphestController',
766768
'ManiphestTaskListController' => 'ManiphestController',
767769
'ManiphestTaskListView' => 'AphrontView',
770+
'ManiphestTaskProject' => 'ManiphestDAO',
768771
'ManiphestTaskSummaryView' => 'AphrontView',
769772
'ManiphestTransaction' => 'ManiphestDAO',
770773
'ManiphestTransactionDetailView' => 'AphrontView',

‎src/applications/maniphest/controller/tasklist/ManiphestTaskListController.php

+94-130
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,30 @@ public function processRequest() {
3333
$uri = $request->getRequestURI();
3434

3535
if ($request->isFormPost()) {
36-
$phid_arr = $request->getArr('view_user');
37-
$view_target = head($phid_arr);
38-
return id(new AphrontRedirectResponse())
39-
->setURI($request->getRequestURI()->alter('phid', $view_target));
40-
}
36+
// Redirect to GET so URIs can be copy/pasted.
37+
38+
$user_phids = $request->getArr('set_users');
39+
$proj_phids = $request->getArr('set_projects');
40+
$user_phids = implode(',', $user_phids);
41+
$proj_phids = implode(',', $proj_phids);
42+
$user_phids = nonempty($user_phids, null);
43+
$proj_phids = nonempty($proj_phids, null);
4144

45+
$uri = $request->getRequestURI()
46+
->alter('users', $user_phids)
47+
->alter('projects', $proj_phids);
48+
49+
return id(new AphrontRedirectResponse())->setURI($uri);
50+
}
4251

4352
$views = array(
4453
'User Tasks',
4554
'action' => 'Assigned',
4655
'created' => 'Created',
4756
'triage' => 'Need Triage',
48-
// 'touched' => 'Touched',
4957
'<hr />',
5058
'All Tasks',
5159
'alltriage' => 'Need Triage',
52-
'unassigned' => 'Unassigned',
5360
'all' => 'All Tasks',
5461
);
5562

@@ -77,7 +84,7 @@ public function processRequest() {
7784
phutil_render_tag(
7885
'a',
7986
array(
80-
'href' => $uri,
87+
'href' => $uri->alter('page', null),
8188
'class' => ($this->view == $view)
8289
? 'aphront-side-nav-selected'
8390
: null,
@@ -90,13 +97,26 @@ public function processRequest() {
9097
list($grouping, $group_links) = $this->renderGroupLinks();
9198
list($order, $order_links) = $this->renderOrderLinks();
9299

93-
$view_phid = nonempty($request->getStr('phid'), $user->getPHID());
100+
$user_phids = $request->getStr('users');
101+
if (strlen($user_phids)) {
102+
$user_phids = explode(',', $user_phids);
103+
} else {
104+
$user_phids = array($user->getPHID());
105+
}
106+
107+
$project_phids = $request->getStr('projects');
108+
if (strlen($project_phids)) {
109+
$project_phids = explode(',', $project_phids);
110+
} else {
111+
$project_phids = array();
112+
}
94113

95114
$page = $request->getInt('page');
96115
$page_size = self::DEFAULT_PAGE_SIZE;
97116

98117
list($tasks, $handles, $total_count) = $this->loadTasks(
99-
$view_phid,
118+
$user_phids,
119+
$project_phids,
100120
array(
101121
'status' => $status_map,
102122
'group' => $grouping,
@@ -105,23 +125,33 @@ public function processRequest() {
105125
'limit' => $page_size,
106126
));
107127

108-
109128
$form = id(new AphrontFormView())
110-
->setUser($user);
129+
->setUser($user)
130+
->setAction($request->getRequestURI());
111131

112132
if (isset($has_filter[$this->view])) {
133+
$tokens = array();
134+
foreach ($user_phids as $phid) {
135+
$tokens[$phid] = $handles[$phid]->getFullName();
136+
}
113137
$form->appendChild(
114138
id(new AphrontFormTokenizerControl())
115-
->setLimit(1)
116139
->setDatasource('/typeahead/common/searchowner/')
117-
->setName('view_user')
118-
->setLabel('View User')
119-
->setCaption('Use "upforgrabs" to find unassigned tasks.')
120-
->setValue(
121-
array(
122-
$view_phid => $handles[$view_phid]->getFullName(),
123-
)));
140+
->setName('set_users')
141+
->setLabel('Users')
142+
->setValue($tokens));
143+
}
144+
145+
$tokens = array();
146+
foreach ($project_phids as $phid) {
147+
$tokens[$phid] = $handles[$phid]->getFullName();
124148
}
149+
$form->appendChild(
150+
id(new AphrontFormTokenizerControl())
151+
->setDatasource('/typeahead/common/projects/')
152+
->setName('set_projects')
153+
->setLabel('Projects')
154+
->setValue($tokens));
125155

126156
$form
127157
->appendChild(
@@ -137,6 +167,10 @@ public function processRequest() {
137167
->setLabel('Order')
138168
->setValue($order_links));
139169

170+
$form->appendChild(
171+
id(new AphrontFormSubmitControl())
172+
->setValue('Filter Tasks'));
173+
140174
$filter = new AphrontListFilterView();
141175
$filter->addButton(
142176
phutil_render_tag(
@@ -209,141 +243,71 @@ public function processRequest() {
209243
));
210244
}
211245

212-
private function loadTasks($view_phid, array $dict) {
213-
$phids = array($view_phid);
214-
215-
$include_upforgrabs = false;
216-
foreach ($phids as $key => $phid) {
217-
if ($phid == ManiphestTaskOwner::OWNER_UP_FOR_GRABS) {
218-
unset($phids[$key]);
219-
$include_upforgrabs = true;
220-
}
221-
}
246+
private function loadTasks(
247+
array $user_phids,
248+
array $project_phids,
249+
array $dict) {
222250

223-
$task = new ManiphestTask();
224-
225-
$argv = array();
251+
$query = new ManiphestTaskQuery();
252+
$query->withProjects($project_phids);
226253

227254
$status = $dict['status'];
228255
if (!empty($status['open']) && !empty($status['closed'])) {
229-
$status_clause = '1 = 1';
256+
$query->withStatus(ManiphestTaskQuery::STATUS_ANY);
230257
} else if (!empty($status['open'])) {
231-
$status_clause = 'status = %d';
232-
$argv[] = 0;
258+
$query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
233259
} else {
234-
$status_clause = 'status > %d';
235-
$argv[] = 0;
260+
$query->withStatus(ManiphestTaskQuery::STATUS_CLOSED);
236261
}
237262

238-
$extra_clause = '1 = 1';
239263
switch ($this->view) {
240264
case 'action':
241-
$parts = array();
242-
if ($phids) {
243-
$parts[] = 'ownerPHID in (%Ls)';
244-
$argv[] = $phids;
245-
}
246-
if ($include_upforgrabs) {
247-
$parts[] = 'ownerPHID IS NULL';
248-
}
249-
$extra_clause = '('.implode(' OR ', $parts).')';
265+
$query->withOwners($user_phids);
250266
break;
251267
case 'created':
252-
$parts = array();
253-
if ($phids) {
254-
$parts[] = 'authorPHID in (%Ls)';
255-
$argv[] = $phids;
256-
}
257-
if ($include_upforgrabs) {
258-
// This should be impossible since every task is supposed to have a
259-
// valid author, but we might as well run the query.
260-
$parts[] = 'authorPHID IS NULL';
261-
}
262-
$extra_clause = '('.implode(' OR ', $parts).')';
268+
$query->withAuthors($user_phids);
263269
break;
264270
case 'triage':
265-
$parts = array();
266-
if ($phids) {
267-
$parts[] = 'ownerPHID in (%Ls)';
268-
$argv[] = $phids;
269-
}
270-
if ($include_upforgrabs) {
271-
$parts[] = 'ownerPHID IS NULL';
272-
}
273-
$extra_clause = '('.implode(' OR ', $parts).') AND priority = %d';
274-
$argv[] = ManiphestTaskPriority::PRIORITY_TRIAGE;
271+
$query->withOwners($user_phids);
272+
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
275273
break;
276274
case 'alltriage':
277-
$extra_clause = 'priority = %d';
278-
$argv[] = ManiphestTaskPriority::PRIORITY_TRIAGE;
279-
break;
280-
case 'unassigned':
281-
$extra_clause = 'ownerPHID is NULL';
275+
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
282276
break;
283277
case 'all':
284278
break;
285279
}
286280

287-
$order = array();
288-
switch ($dict['group']) {
289-
case 'priority':
290-
$order[] = 'priority';
291-
break;
292-
case 'owner':
293-
$order[] = 'ownerOrdering';
294-
break;
295-
case 'status':
296-
$order[] = 'status';
297-
break;
298-
}
299-
300-
switch ($dict['order']) {
301-
case 'priority':
302-
$order[] = 'priority';
303-
$order[] = 'dateModified';
304-
break;
305-
case 'created':
306-
$order[] = 'id';
307-
break;
308-
default:
309-
$order[] = 'dateModified';
310-
break;
311-
}
312-
313-
$order = array_unique($order);
314-
315-
foreach ($order as $k => $column) {
316-
switch ($column) {
317-
case 'ownerOrdering':
318-
$order[$k] = "{$column} ASC";
319-
break;
320-
default:
321-
$order[$k] = "{$column} DESC";
322-
break;
323-
}
324-
}
325-
326-
$order = implode(', ', $order);
327-
328-
$offset = (int)idx($dict, 'offset', 0);
329-
$limit = (int)idx($dict, 'limit', self::DEFAULT_PAGE_SIZE);
330-
331-
$sql = "SELECT SQL_CALC_FOUND_ROWS * FROM %T WHERE ".
332-
"({$status_clause}) AND ({$extra_clause}) ORDER BY {$order} ".
333-
"LIMIT {$offset}, {$limit}";
334-
335-
array_unshift($argv, $task->getTableName());
336-
337-
$conn = $task->establishConnection('r');
338-
$data = vqueryfx_all($conn, $sql, $argv);
281+
$order_map = array(
282+
'priority' => ManiphestTaskQuery::ORDER_PRIORITY,
283+
'created' => ManiphestTaskQuery::ORDER_CREATED,
284+
);
285+
$query->setOrderBy(
286+
idx(
287+
$order_map,
288+
$dict['order'],
289+
ManiphestTaskQuery::ORDER_MODIFIED));
290+
291+
$group_map = array(
292+
'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
293+
'owner' => ManiphestTaskQuery::GROUP_OWNER,
294+
'status' => ManiphestTaskQuery::GROUP_STATUS,
295+
);
296+
$query->setGroupBy(
297+
idx(
298+
$group_map,
299+
$dict['group'],
300+
ManiphestTaskQuery::GROUP_NONE));
339301

340-
$total_row_count = queryfx_one($conn, 'SELECT FOUND_ROWS() N');
341-
$total_row_count = $total_row_count['N'];
302+
$query->setCalculateRows(true);
303+
$query->setLimit($dict['limit']);
304+
$query->setOffset($dict['offset']);
342305

343-
$data = $task->loadAllFromArray($data);
306+
$data = $query->execute();
307+
$total_row_count = $query->getRowCount();
344308

345309
$handle_phids = mpull($data, 'getOwnerPHID');
346-
$handle_phids[] = $view_phid;
310+
$handle_phids = array_merge($handle_phids, $project_phids, $user_phids);
347311
$handles = id(new PhabricatorObjectHandleData($handle_phids))
348312
->loadHandles();
349313

0 commit comments

Comments
 (0)
Failed to load comments.