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.
This commit is contained in:
parent
e1e7696d53
commit
85978a4d18
@ -178,9 +178,10 @@ GROUP BY uid";
|
|||||||
*
|
*
|
||||||
* @param string Status ('open'), 'closed'
|
* @param string Status ('open'), 'closed'
|
||||||
* @param IDF_Tag Subfilter with a label (null)
|
* @param IDF_Tag Subfilter with a label (null)
|
||||||
|
* @param array Restrict further to a list of ids
|
||||||
* @return int Count
|
* @return int Count
|
||||||
*/
|
*/
|
||||||
public function getIssueCountByStatus($status='open', $label=null)
|
public function getIssueCountByStatus($status='open', $label=null, $ids=array())
|
||||||
{
|
{
|
||||||
switch ($status) {
|
switch ($status) {
|
||||||
case 'open':
|
case 'open':
|
||||||
@ -203,12 +204,48 @@ GROUP BY uid";
|
|||||||
$sql2 = new Pluf_SQL('idf_tag_id=%s', array($label->id));
|
$sql2 = new Pluf_SQL('idf_tag_id=%s', array($label->id));
|
||||||
$sql->SAnd($sql2);
|
$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());
|
$params = array('filter' => $sql->gen());
|
||||||
if (!is_null($label)) { $params['view'] = 'join_tags'; }
|
if (!is_null($label)) { $params['view'] = 'join_tags'; }
|
||||||
$gissue = new IDF_Issue();
|
$gissue = new IDF_Issue();
|
||||||
return $gissue->getCount($params);
|
return $gissue->getCount($params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Get the open/closed tag ids as they are often used when doing
|
||||||
* listings.
|
* listings.
|
||||||
@ -415,7 +452,11 @@ GROUP BY uid";
|
|||||||
foreach ($this->_con->select($sql) as $idc) {
|
foreach ($this->_con->select($sql) as $idc) {
|
||||||
$tag = new IDF_Tag($idc['id']);
|
$tag = new IDF_Tag($idc['id']);
|
||||||
$tag->nb_use = $idc['nb_use'];
|
$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);
|
return new Pluf_Template_ContextVars($tags);
|
||||||
}
|
}
|
||||||
|
@ -431,45 +431,142 @@ class IDF_Views_Issue
|
|||||||
|
|
||||||
public $search_precond = array('IDF_Precondition::accessIssues');
|
public $search_precond = array('IDF_Precondition::accessIssues');
|
||||||
public function search($request, $match)
|
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;
|
$prj = $request->project;
|
||||||
if (!isset($request->REQUEST['q']) or trim($request->REQUEST['q']) == '') {
|
if (trim($query) == '') {
|
||||||
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index', array($prj->shortname));
|
||||||
array($prj->shortname));
|
|
||||||
return new Pluf_HTTP_Response_Redirect($url);
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
}
|
}
|
||||||
$q = $request->REQUEST['q'];
|
|
||||||
$title = sprintf(__('Search Issues - %s'), $q);
|
$tag = null;
|
||||||
$issues = new Pluf_Search_ResultSet(IDF_Search::mySearch($q, $prj, 'IDF_Issue'));
|
if ($tag_id !== null) {
|
||||||
if (count($issues) > 100) {
|
$tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $tag_id);
|
||||||
// no more than 100 results as we do not care
|
|
||||||
$issues->results = array_slice($issues->results, 0, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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 = new Pluf_Paginator();
|
||||||
$pag->items = $issues;
|
|
||||||
$pag->class = 'recent-issues';
|
$pag->class = 'recent-issues';
|
||||||
$pag->item_extra_props = array('project_m' => $prj,
|
$pag->items = $sorted_issues;
|
||||||
'shortname' => $prj->shortname,
|
$pag->item_extra_props = array(
|
||||||
'current_user' => $request->user);
|
'project_m' => $prj,
|
||||||
|
'shortname' => $prj->shortname,
|
||||||
|
'current_user' => $request->user
|
||||||
|
);
|
||||||
$pag->summary = __('This table shows the found issues.');
|
$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', '');
|
$pag->extra_classes = array('a-c', '', 'a-c', '');
|
||||||
$list_display = array(
|
$pag->configure(array(
|
||||||
'id' => __('Id'),
|
'id' => __('Id'),
|
||||||
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||||
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||||
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||||
);
|
));
|
||||||
$pag->configure($list_display);
|
// disable paginating
|
||||||
$pag->items_per_page = 100;
|
$pag->items_per_page = PHP_INT_MAX;
|
||||||
$pag->no_results_text = __('No issues were found.');
|
$pag->no_results_text = __('No issues were found.');
|
||||||
$pag->setFromRequest($request);
|
$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');
|
public $view_precond = array('IDF_Precondition::accessIssues');
|
||||||
|
@ -128,6 +128,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/$#',
|
|||||||
'model' => 'IDF_Views_Issue',
|
'model' => 'IDF_Views_Issue',
|
||||||
'method' => 'search');
|
'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+)/$#',
|
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/(\d+)/$#',
|
||||||
'base' => $base,
|
'base' => $base,
|
||||||
'model' => 'IDF_Views_Issue',
|
'model' => 'IDF_Views_Issue',
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
{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>
|
{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} |
|
| <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">
|
<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'}" />
|
<input type="submit" name="s" value="{trans 'Search'}" />
|
||||||
</form>
|
</form>
|
||||||
{if $inIssue} |
|
{if $inIssue} |
|
||||||
|
@ -8,16 +8,15 @@
|
|||||||
|
|
||||||
{/block}
|
{/block}
|
||||||
{block context}
|
{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 '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')}
|
{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>
|
{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>
|
<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>
|
<p><strong>{trans 'Completion:'}</strong> {$completion}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
{/block}
|
{/block}
|
||||||
|
@ -8,5 +8,25 @@
|
|||||||
|
|
||||||
{/block}
|
{/block}
|
||||||
{block context}
|
{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}
|
{/block}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
{assign $class = ''}{assign $i = 0}
|
<div id="tagscloud" class="smaller"><dl>{foreach $project.getTagCloud($cloud) as $class => $labels}
|
||||||
<div id="tagscloud" class="smaller"><dl>{foreach $project.getTagCloud($cloud) as $label}
|
<dt class="label">{$class}</dt>
|
||||||
|
{foreach $labels as $idx => $label}
|
||||||
{aurl 'url', $cloud_url, array($project.shortname, $label.id, 'open')}
|
{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}{if $idx != count($labels) - 1},{/if}</a></dd>
|
||||||
<dd><a href="{$url}" class="label">{$label.name},</a></dd>
|
{/foreach}{/foreach}</dl></p>
|
||||||
{assign $class = $label.class}
|
|
||||||
{assign $i = $i + 1}
|
|
||||||
{/foreach}</dl></p>
|
|
||||||
|
Loading…
Reference in New Issue
Block a user