Indefero

Indefero Commit Details


Date:2011-10-02 18:54:01 (13 years 2 months ago)
Author:Thomas Keller
Branch:develop, feature.diff-whitespace, feature.search-filter, feature.wiki-default-page, release-1.2, release-1.3
Commit:85978a4d18c0d86a62022d28858ae361f7d33bd6
Parents: e1e7696d538185184d876b425579560912d2866c
Message:Implement basic filtering capabilities in the issue search view.

Since IDF's text search component does not allow further restrictions
on the result set, we make a second, filtered query to restrict to
the item state ('open' or 'closed') and optionally a label. All in
all this is all harder than it could be, especially the tag cloud
is very monolithic and should be replaced by a data-driven component
that is less dependent on a single data / query and link usage, but
this would for now require too many changes.

Similar questionable is the code duplication for the index, listStatus
and listLabel view implementations that all do more or less the same.
The search implementation now only uses one implementation for a very
similar use case. It also removes the artificial restriction to 100
results we had previously there and does not query a record for each
single result (as was done with Pluf_Search_ResultSet previously).

On my way through this I tried to generalize a couple of i18n texts
and removed smaller issues like the "trailing comma" in label lists.

This partially fixes issue 548.
Changes:

File differences

src/IDF/Project.php
132132
133133
134134
135
135
136136
137137
138138
......
167167
168168
169169
170
170
171171
172172
173173
......
178178
179179
180180
181
181182
182183
183
184
184185
185186
186187
......
203204
204205
205206
207
208
209
210
206211
207212
208213
......
210215
211216
212217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
213250
214251
215252
......
415452
416453
417454
418
455
456
457
458
459
419460
420461
421462
}
return $projects[0];
}
/**
* Returns the number of open/closed issues.
*
$key = ($v['id'] === '-1') ? null : $v['id'];
$ownerStatistics[$key] = (int)$v['nb'];
}
arsort($ownerStatistics);
return $ownerStatistics;
*
* @param string Status ('open'), 'closed'
* @param IDF_Tag Subfilter with a label (null)
* @param array Restrict further to a list of ids
* @return int Count
*/
public function getIssueCountByStatus($status='open', $label=null)
public function getIssueCountByStatus($status='open', $label=null, $ids=array())
{
switch ($status) {
case 'open':
$sql2 = new Pluf_SQL('idf_tag_id=%s', array($label->id));
$sql->SAnd($sql2);
}
if (count($ids) > 0) {
$sql2 = new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids)));
$sql->SAnd($sql2);
}
$params = array('filter' => $sql->gen());
if (!is_null($label)) { $params['view'] = 'join_tags'; }
$gissue = new IDF_Issue();
}
/**
* Get the tags for a specific list of issues.
*
* @param string Status ('open') or 'closed'
* @param array A list of issue ids
* @return array An array of tag objects
*/
public function getTagsByIssues($issue_ids=array())
{
// make the below query always a valid one
if (count($issue_ids) == 0) $issue_ids[] = 0;
$assocTable = $this->_con->pfx.'idf_issue_idf_tag_assoc';
$query = sprintf(
'SELECT DISTINCT idf_tag_id FROM %s '.
'WHERE idf_issue_id IN (%s) '.
'GROUP BY idf_tag_id',
$assocTable, implode(',', $issue_ids)
);
$db = Pluf::db();
$dbData = $db->select($query);
$ids = array(0);
foreach ($dbData as $data) {
$ids[] = $data['idf_tag_id'];
}
$sql = new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids)));
$model = new IDF_Tag();
return $model->getList(array('filter' => $sql->gen()));
}
/**
* Get the open/closed tag ids as they are often used when doing
* listings.
*
foreach ($this->_con->select($sql) as $idc) {
$tag = new IDF_Tag($idc['id']);
$tag->nb_use = $idc['nb_use'];
$tags[] = $tag;
// group by class
if (!array_key_exists($tag->class, $tags)) {
$tags[$tag->class] = array();
}
$tags[$tag->class][] = $tag;
}
return new Pluf_Template_ContextVars($tags);
}
src/IDF/Views/Issue.php
432432
433433
434434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
435458
436
437
438
459
460
439461
440462
441
442
443
444
445
446
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
447508
509
448510
449
450511
451
452
453
512
513
514
515
516
517
454518
455
456519
457
458
459
460
461
462
463
464
520
521
522
523
524
525
526
527
465528
466529
467
468
469
470
471
472530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
473570
474571
475572
public $search_precond = array('IDF_Precondition::accessIssues');
public function search($request, $match)
{
$query = !isset($request->REQUEST['q']) ? '' : $request->REQUEST['q'];
return $this->doSearch($request, $query, 'open');
}
public $searchStatus_precond = array('IDF_Precondition::accessIssues');
public function searchStatus($request, $match)
{
$query = !isset($request->REQUEST['q']) ? '' : $request->REQUEST['q'];
$status = in_array($match[2], array('open', 'closed')) ? $match[2] : 'open';
return $this->doSearch($request, $query, $status);
}
public $searchLabel_precond = array('IDF_Precondition::accessIssues');
public function searchLabel($request, $match)
{
$query = !isset($request->REQUEST['q']) ? '' : $request->REQUEST['q'];
$tag_id = intval($match[2]);
$status = in_array($match[3], array('open', 'closed')) ? $match[3] : 'open';
return $this->doSearch($request, $query, $status, $tag_id);
}
private function doSearch($request, $query, $status, $tag_id=null)
{
$prj = $request->project;
if (!isset($request->REQUEST['q']) or trim($request->REQUEST['q']) == '') {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
array($prj->shortname));
if (trim($query) == '') {
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index', array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
$q = $request->REQUEST['q'];
$title = sprintf(__('Search Issues - %s'), $q);
$issues = new Pluf_Search_ResultSet(IDF_Search::mySearch($q, $prj, 'IDF_Issue'));
if (count($issues) > 100) {
// no more than 100 results as we do not care
$issues->results = array_slice($issues->results, 0, 100);
$tag = null;
if ($tag_id !== null) {
$tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $tag_id);
}
$title = sprintf(__('Search issues - %s'), $query);
if ($status === 'closed') {
$title = sprintf(__('Search closed issues - %s'), $query);
}
// using Plufs ResultSet implementation here is inefficient, because
// it makes a SELECT for each item and does not allow for further
// filtering neither, so we just return the ids and filter by them
// and other things in the next round
$results = IDF_Search::mySearch($query, $prj, 'IDF_Issue');
$issue_ids = array();
foreach ($results as $result) {
$issue_ids[] = $result['model_id'];
}
$otags = $prj->getTagIdsByStatus($status);
if (count($otags) == 0) $otags[] = 0;
$sql = new Pluf_SQL(
'id IN ('.implode(',', $issue_ids).') '.
'AND status IN ('.implode(', ', $otags).') '.
($tag_id !== null ? 'AND idf_tag_id='.$tag_id.' ' : '')
);
$model = new IDF_Issue();
$issues = $model->getList(array('filter' => $sql->gen(), 'view' => 'join_tags'));
// we unfortunately loose the original sort order,
// so we manually have to apply it here again
$sorted_issues = new ArrayObject();
$filtered_issue_ids = array();
foreach ($issue_ids as $issue_id) {
foreach ($issues as $issue) {
if ($issue->id != $issue_id)
continue;
if (array_key_exists($issue_id, $sorted_issues))
continue;
$sorted_issues[$issue_id] = $issue;
$filtered_issue_ids[] = $issue_id;
}
}
$pag = new Pluf_Paginator();
$pag->items = $issues;
$pag->class = 'recent-issues';
$pag->item_extra_props = array('project_m' => $prj,
'shortname' => $prj->shortname,
'current_user' => $request->user);
$pag->items = $sorted_issues;
$pag->item_extra_props = array(
'project_m' => $prj,
'shortname' => $prj->shortname,
'current_user' => $request->user
);
$pag->summary = __('This table shows the found issues.');
$pag->action = array('IDF_Views_Issue::search', array($prj->shortname), array('q'=> $q));
$pag->extra_classes = array('a-c', '', 'a-c', '');
$list_display = array(
'id' => __('Id'),
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
);
$pag->configure($list_display);
$pag->items_per_page = 100;
$pag->configure(array(
'id' => __('Id'),
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
));
// disable paginating
$pag->items_per_page = PHP_INT_MAX;
$pag->no_results_text = __('No issues were found.');
$pag->setFromRequest($request);
$params = array('page_title' => $title,
'issues' => $pag,
'q' => $q,
);
return Pluf_Shortcuts_RenderToResponse('idf/issues/search.html', $params, $request);
if ($tag_id === null) {
$pag->action = array('IDF_Views_Issue::searchStatus',
array($prj->shortname, $status),
array('q'=> $query),
);
} else {
$pag->action = array('IDF_Views_Issue::searchLabel',
array($prj->shortname, $tag_id, $status),
array('q'=> $query),
);
}
// get stats about the issues
$open = $prj->getIssueCountByStatus('open', $tag, $issue_ids);
$closed = $prj->getIssueCountByStatus('closed', $tag, $issue_ids);
// query the available tags for this search result
$all_tags = $prj->getTagsByIssues($filtered_issue_ids);
$grouped_tags = array();
foreach ($all_tags as $atag) {
// group by class
if (!array_key_exists($atag->class, $grouped_tags)) {
$grouped_tags[$atag->class] = array();
}
$grouped_tags[$atag->class][] = $atag;
}
$params = array(
'page_title' => $title,
'issues' => $pag,
'query' => $query,
'status' => $status,
'open' => $open,
'closed' => $closed,
'tag' => $tag,
'all_tags' => $grouped_tags,
);
return Pluf_Shortcuts_RenderToResponse('idf/issues/search.html', $params, $request);
}
public $view_precond = array('IDF_Precondition::accessIssues');
src/IDF/conf/urls.php
117117
118118
119119
120
120
121121
122122
123123
......
128128
129129
130130
131
132
133
134
135
136
137
138
139
140
131141
132142
133143
'base' => $base,
'model' => 'IDF_Views_Issue',
'method' => 'index');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/summary/$#',
'base' => $base,
'model' => 'IDF_Views_Issue',
'model' => 'IDF_Views_Issue',
'method' => 'search');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/status/(\w+)/$#',
'base' => $base,
'model' => 'IDF_Views_Issue',
'method' => 'searchStatus');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/label/(\d+)/(\w+)/$#',
'base' => $base,
'model' => 'IDF_Views_Issue',
'method' => 'searchLabel');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/(\d+)/$#',
'base' => $base,
'model' => 'IDF_Views_Issue',
src/IDF/templates/idf/issues/base.html
77
88
99
10
10
1111
1212
1313
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a {if $inMyIssues}class="active" {/if}href="{url 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}">{trans 'My Issues'}</a>
| <a {if $inWatchList}class="active" {/if}href="{url 'IDF_Views_Issue::watchList', array($project.shortname, 'open')}">{trans 'My watch list'}</a>{/if} |
<form class="star" action="{url 'IDF_Views_Issue::search', array($project.shortname)}" method="get">
<input accesskey="4" type="text" value="{$q}" name="q" size="20" />
<input accesskey="4" type="text" value="{$query}" name="q" size="20" />
<input type="submit" name="s" value="{trans 'Search'}" />
</form>
{if $inIssue} |
src/IDF/templates/idf/issues/by-label.html
88
99
1010
11
12
13
1411
1512
1613
1714
18
15
16
17
18
19
1920
2021
21
22
2322
{/block}
{block context}
<p><strong>{trans 'Label:'}</strong>
{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')}
<a href="{$url}" class="label"><strong>{$label.class}:</strong>{$label.name}</a></p>
{aurl 'open_url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')}
{aurl 'closed_url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'closed')}
{blocktrans}<p><strong>Open issues:</strong> <a href="{$open_url}">{$open}</a></p>
<p><strong>Closed issues:</strong> <a href="{$closed_url}">{$closed}</a></p>
{/blocktrans}{if $completion}
{/blocktrans}
<p><strong>{trans 'Label:'}</strong>
{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')}
<a href="{$url}" class="label"><strong>{$label.class}:</strong>{$label.name}</a></p>
{if $completion}
<p><strong>{trans 'Completion:'}</strong> {$completion}</p>
{/if}
{/block}
src/IDF/templates/idf/issues/search.html
88
99
1010
11
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1232
{/block}
{block context}
<p><strong>{trans 'Found issues:'}</strong> {$issues.nb_items}</p>
{aurl 'open_url', 'IDF_Views_Issue::searchStatus', array($project.shortname, 'open'), array('q' => $query)}
{aurl 'closed_url', 'IDF_Views_Issue::searchStatus', array($project.shortname, 'closed'), array('q' => $query)}
{if $tag != null}
{aurl 'open_url', 'IDF_Views_Issue::searchLabel', array($project.shortname, $tag.id, 'open'), array('q' => $query)}
{aurl 'closed_url', 'IDF_Views_Issue::searchLabel', array($project.shortname, $tag.id, 'closed'), array('q' => $query)}
{/if}
{blocktrans}
<p><strong>Found open issues:</strong> <a href="{$open_url}">{$open}</a></p>
<p><strong>Found closed issues:</strong> <a href="{$closed_url}">{$closed}</a></p>{/blocktrans}
{if $tag !== null}
{blocktrans}<p><strong>Label:</strong>
<a href="{$open_url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></p>{/blocktrans}
{else}
{* yes, this is duplicated from tags-cloud.html, but the code there cannot be easily overridden *}
<div id="tagscloud" class="smaller"><dl>{foreach $all_tags as $class => $labels}
<dt class="label">{$class}</dt>
{foreach $labels as $idx => $label}
{aurl 'url', 'IDF_Views_Issue::searchLabel', array($project.shortname, $label.id, $status), array('q'=> $query)}
<dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd>
{/foreach}{/foreach}</dl></p>
{/if}
{/block}
src/IDF/templates/idf/tags-cloud.html
1
2
1
2
3
34
4
5
6
7
8
5
6
{assign $class = ''}{assign $i = 0}
<div id="tagscloud" class="smaller"><dl>{foreach $project.getTagCloud($cloud) as $label}
<div id="tagscloud" class="smaller"><dl>{foreach $project.getTagCloud($cloud) as $class => $labels}
<dt class="label">{$class}</dt>
{foreach $labels as $idx => $label}
{aurl 'url', $cloud_url, array($project.shortname, $label.id, 'open')}
{if $class != $label.class}<dt class="label">{$label.class}</dt>{assign $i = 0}{/if}
<dd><a href="{$url}" class="label">{$label.name},</a></dd>
{assign $class = $label.class}
{assign $i = $i + 1}
{/foreach}</dl></p>
<dd><a href="{$url}" class="label">{$label.name}{if $idx != count($labels) - 1},{/if}</a></dd>
{/foreach}{/foreach}</dl></p>

Archive Download the corresponding diff file

Page rendered in 0.10195s using 14 queries.