Add project and column attributes for advanced search
This commit is contained in:
@@ -31,6 +31,8 @@ class Lexer
|
|||||||
"/^(status:)/" => 'T_STATUS',
|
"/^(status:)/" => 'T_STATUS',
|
||||||
"/^(description:)/" => 'T_DESCRIPTION',
|
"/^(description:)/" => 'T_DESCRIPTION',
|
||||||
"/^(category:)/" => 'T_CATEGORY',
|
"/^(category:)/" => 'T_CATEGORY',
|
||||||
|
"/^(column:)/" => 'T_COLUMN',
|
||||||
|
"/^(project:)/" => 'T_PROJECT',
|
||||||
"/^(\s+)/" => 'T_WHITESPACE',
|
"/^(\s+)/" => 'T_WHITESPACE',
|
||||||
'/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/' => 'T_DATE',
|
'/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/' => 'T_DATE',
|
||||||
'/^(yesterday|tomorrow|today)/' => 'T_DATE',
|
'/^(yesterday|tomorrow|today)/' => 'T_DATE',
|
||||||
@@ -109,6 +111,8 @@ class Lexer
|
|||||||
case 'T_ASSIGNEE':
|
case 'T_ASSIGNEE':
|
||||||
case 'T_COLOR':
|
case 'T_COLOR':
|
||||||
case 'T_CATEGORY':
|
case 'T_CATEGORY':
|
||||||
|
case 'T_COLUMN':
|
||||||
|
case 'T_PROJECT':
|
||||||
$next = next($tokens);
|
$next = next($tokens);
|
||||||
|
|
||||||
if ($next !== false && $next['token'] === 'T_STRING') {
|
if ($next !== false && $next['token'] === 'T_STRING') {
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ class TaskFilter extends Base
|
|||||||
case 'T_CATEGORY':
|
case 'T_CATEGORY':
|
||||||
$this->filterByCategoryName($value);
|
$this->filterByCategoryName($value);
|
||||||
break;
|
break;
|
||||||
|
case 'T_PROJECT':
|
||||||
|
$this->filterByProjectName($value);
|
||||||
|
break;
|
||||||
|
case 'T_COLUMN':
|
||||||
|
$this->filterByColumnName($value);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +195,29 @@ class TaskFilter extends Base
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter by project name
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param array $values List of project name
|
||||||
|
* @return TaskFilter
|
||||||
|
*/
|
||||||
|
public function filterByProjectName(array $values)
|
||||||
|
{
|
||||||
|
$this->query->beginOr();
|
||||||
|
|
||||||
|
foreach ($values as $project) {
|
||||||
|
if (ctype_digit($project)) {
|
||||||
|
$this->query->eq(Task::TABLE.'.project_id', $project);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->query->ilike(Project::TABLE.'.name', $project);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->query->closeOr();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by category id
|
* Filter by category id
|
||||||
*
|
*
|
||||||
@@ -325,6 +354,24 @@ class TaskFilter extends Base
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter by column name
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param array $values List of column name
|
||||||
|
* @return TaskFilter
|
||||||
|
*/
|
||||||
|
public function filterByColumnName(array $values)
|
||||||
|
{
|
||||||
|
$this->query->beginOr();
|
||||||
|
|
||||||
|
foreach ($values as $project) {
|
||||||
|
$this->query->ilike(Board::TABLE.'.title', $project);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->query->closeOr();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by swimlane
|
* Filter by swimlane
|
||||||
*
|
*
|
||||||
@@ -382,7 +429,6 @@ class TaskFilter extends Base
|
|||||||
*/
|
*/
|
||||||
public function filterByDueDate($date)
|
public function filterByDueDate($date)
|
||||||
{
|
{
|
||||||
$this->query->neq(Task::TABLE.'.date_due', '');
|
|
||||||
$this->query->neq(Task::TABLE.'.date_due', 0);
|
$this->query->neq(Task::TABLE.'.date_due', 0);
|
||||||
$this->query->notNull(Task::TABLE.'.date_due');
|
$this->query->notNull(Task::TABLE.'.date_due');
|
||||||
return $this->filterWithOperator(Task::TABLE.'.date_due', $date, true);
|
return $this->filterWithOperator(Task::TABLE.'.date_due', $date, true);
|
||||||
@@ -452,7 +498,7 @@ class TaskFilter extends Base
|
|||||||
*/
|
*/
|
||||||
public function findAll()
|
public function findAll()
|
||||||
{
|
{
|
||||||
return $this->query->findAll();
|
return $this->query->asc(Task::TABLE.'.id')->findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,7 +15,22 @@
|
|||||||
<input type="submit" value="<?= t('Search') ?>" class="btn btn-blue"/>
|
<input type="submit" value="<?= t('Search') ?>" class="btn btn-blue"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<?php if (! empty($values['search']) && $paginator->isEmpty()): ?>
|
<?php if (empty($values['search'])): ?>
|
||||||
|
<div class="listing">
|
||||||
|
<h3><?= t('Advanced search') ?></h3>
|
||||||
|
<p><?= t('Example of query: ') ?><strong>project:"My project" assignee:me due:tomorrow</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><?= t('Search by project: ') ?><strong>project:"My project"</strong></li>
|
||||||
|
<li><?= t('Search by column: ') ?><strong>column:"Work in progress"</strong></li>
|
||||||
|
<li><?= t('Search by assignee: ') ?><strong>assignee:nobody</strong></li>
|
||||||
|
<li><?= t('Search by color: ') ?><strong>color:Blue</strong></li>
|
||||||
|
<li><?= t('Search by category: ') ?><strong>category:"Feature Request"</strong></li>
|
||||||
|
<li><?= t('Search by description: ') ?><strong>description:"Something to find"</strong></li>
|
||||||
|
<li><?= t('Search by due date: ') ?><strong>due:2015-07-01</strong></li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="http://kanboard.net/documentation/search" target="_blank"><?= t('More examples in the documentation') ?></a></p>
|
||||||
|
</div>
|
||||||
|
<?php elseif (! empty($values['search']) && $paginator->isEmpty()): ?>
|
||||||
<p class="alert"><?= t('Nothing found.') ?></p>
|
<p class="alert"><?= t('Nothing found.') ?></p>
|
||||||
<?php elseif (! $paginator->isEmpty()): ?>
|
<?php elseif (! $paginator->isEmpty()): ?>
|
||||||
<?= $this->render('search/results', array(
|
<?= $this->render('search/results', array(
|
||||||
|
|||||||
@@ -127,3 +127,20 @@ Attribute: **category**
|
|||||||
- Find tasks with a specific category: `category:"Feature Request"`
|
- Find tasks with a specific category: `category:"Feature Request"`
|
||||||
- Find all tasks that have those categories: `category:"Bug" category:"Improvements"`
|
- Find all tasks that have those categories: `category:"Bug" category:"Improvements"`
|
||||||
- Find tasks with no category assigned: `category:none`
|
- Find tasks with no category assigned: `category:none`
|
||||||
|
|
||||||
|
Search by project
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Attribute: **project**
|
||||||
|
|
||||||
|
- Find tasks by project name: `project:"My project name"`
|
||||||
|
- Find tasks by project id: `project:23`
|
||||||
|
- Find tasks for several projects: `project:"My project A" project:"My project B"`
|
||||||
|
|
||||||
|
Search by column
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Attribute: **column**
|
||||||
|
|
||||||
|
- Find tasks by column name: `column:"Work in progress"`
|
||||||
|
- Find tasks for several columns: `column:"Backlog" column:ready`
|
||||||
|
|||||||
@@ -91,6 +91,56 @@ class LexerTest extends Base
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testColumnQuery()
|
||||||
|
{
|
||||||
|
$lexer = new Lexer;
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array(array('match' => 'column:', 'token' => 'T_COLUMN'), array('match' => 'Feature Request', 'token' => 'T_STRING')),
|
||||||
|
$lexer->tokenize('column:"Feature Request"')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array('T_COLUMN' => array('Feature Request')),
|
||||||
|
$lexer->map($lexer->tokenize('column:"Feature Request"'))
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array('T_COLUMN' => array('Feature Request', 'Bug')),
|
||||||
|
$lexer->map($lexer->tokenize('column:"Feature Request" column:Bug'))
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array(),
|
||||||
|
$lexer->map($lexer->tokenize('column: '))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProjectQuery()
|
||||||
|
{
|
||||||
|
$lexer = new Lexer;
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array(array('match' => 'project:', 'token' => 'T_PROJECT'), array('match' => 'My project', 'token' => 'T_STRING')),
|
||||||
|
$lexer->tokenize('project:"My project"')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array('T_PROJECT' => array('My project')),
|
||||||
|
$lexer->map($lexer->tokenize('project:"My project"'))
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array('T_PROJECT' => array('My project', 'plop')),
|
||||||
|
$lexer->map($lexer->tokenize('project:"My project" project:plop'))
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array(),
|
||||||
|
$lexer->map($lexer->tokenize('project: '))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testStatusQuery()
|
public function testStatusQuery()
|
||||||
{
|
{
|
||||||
$lexer = new Lexer;
|
$lexer = new Lexer;
|
||||||
|
|||||||
@@ -124,6 +124,76 @@ class TaskFilterTest extends Base
|
|||||||
$this->assertEmpty($tasks);
|
$this->assertEmpty($tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSearchWithProject()
|
||||||
|
{
|
||||||
|
$p = new Project($this->container);
|
||||||
|
$tc = new TaskCreation($this->container);
|
||||||
|
$tf = new TaskFilter($this->container);
|
||||||
|
|
||||||
|
$this->assertEquals(1, $p->create(array('name' => 'My project A')));
|
||||||
|
$this->assertEquals(2, $p->create(array('name' => 'My project B')));
|
||||||
|
$this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task1')));
|
||||||
|
$this->assertNotFalse($tc->create(array('project_id' => 2, 'title' => 'task2')));
|
||||||
|
|
||||||
|
$tf->search('project:"My project A"');
|
||||||
|
$tasks = $tf->findAll();
|
||||||
|
$this->assertNotEmpty($tasks);
|
||||||
|
$this->assertCount(1, $tasks);
|
||||||
|
$this->assertEquals('task1', $tasks[0]['title']);
|
||||||
|
$this->assertEquals('My project A', $tasks[0]['project_name']);
|
||||||
|
|
||||||
|
$tf->search('project:2');
|
||||||
|
$tasks = $tf->findAll();
|
||||||
|
$this->assertNotEmpty($tasks);
|
||||||
|
$this->assertCount(1, $tasks);
|
||||||
|
$this->assertEquals('task2', $tasks[0]['title']);
|
||||||
|
$this->assertEquals('My project B', $tasks[0]['project_name']);
|
||||||
|
|
||||||
|
$tf->search('project:"My project A" project:"my project b"');
|
||||||
|
$tasks = $tf->findAll();
|
||||||
|
$this->assertNotEmpty($tasks);
|
||||||
|
$this->assertCount(2, $tasks);
|
||||||
|
$this->assertEquals('task1', $tasks[0]['title']);
|
||||||
|
$this->assertEquals('My project A', $tasks[0]['project_name']);
|
||||||
|
$this->assertEquals('task2', $tasks[1]['title']);
|
||||||
|
$this->assertEquals('My project B', $tasks[1]['project_name']);
|
||||||
|
|
||||||
|
$tf->search('project:"not found"');
|
||||||
|
$tasks = $tf->findAll();
|
||||||
|
$this->assertEmpty($tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSearchWithColumn()
|
||||||
|
{
|
||||||
|
$p = new Project($this->container);
|
||||||
|
$tc = new TaskCreation($this->container);
|
||||||
|
$tf = new TaskFilter($this->container);
|
||||||
|
|
||||||
|
$this->assertEquals(1, $p->create(array('name' => 'My project A')));
|
||||||
|
$this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task1')));
|
||||||
|
$this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task2', 'column_id' => 3)));
|
||||||
|
|
||||||
|
$tf->search('column:Backlog');
|
||||||
|
$tasks = $tf->findAll();
|
||||||
|
$this->assertNotEmpty($tasks);
|
||||||
|
$this->assertCount(1, $tasks);
|
||||||
|
$this->assertEquals('task1', $tasks[0]['title']);
|
||||||
|
$this->assertEquals('Backlog', $tasks[0]['column_name']);
|
||||||
|
|
||||||
|
$tf->search('column:backlog column:"Work in progress"');
|
||||||
|
$tasks = $tf->findAll();
|
||||||
|
$this->assertNotEmpty($tasks);
|
||||||
|
$this->assertCount(2, $tasks);
|
||||||
|
$this->assertEquals('task1', $tasks[0]['title']);
|
||||||
|
$this->assertEquals('Backlog', $tasks[0]['column_name']);
|
||||||
|
$this->assertEquals('task2', $tasks[1]['title']);
|
||||||
|
$this->assertEquals('Work in progress', $tasks[1]['column_name']);
|
||||||
|
|
||||||
|
$tf->search('column:"not found"');
|
||||||
|
$tasks = $tf->findAll();
|
||||||
|
$this->assertEmpty($tasks);
|
||||||
|
}
|
||||||
|
|
||||||
public function testSearchWithDueDate()
|
public function testSearchWithDueDate()
|
||||||
{
|
{
|
||||||
$dp = new DateParser($this->container);
|
$dp = new DateParser($this->container);
|
||||||
|
|||||||
Reference in New Issue
Block a user