diff options
| -rw-r--r-- | app/Core/Lexer.php | 4 | ||||
| -rw-r--r-- | app/Model/TaskFilter.php | 50 | ||||
| -rw-r--r-- | app/Template/search/index.php | 17 | ||||
| -rw-r--r-- | docs/search.markdown | 17 | ||||
| -rw-r--r-- | tests/units/LexerTest.php | 50 | ||||
| -rw-r--r-- | tests/units/TaskFilterTest.php | 70 | 
6 files changed, 205 insertions, 3 deletions
diff --git a/app/Core/Lexer.php b/app/Core/Lexer.php index ad0631d5..d277f998 100644 --- a/app/Core/Lexer.php +++ b/app/Core/Lexer.php @@ -31,6 +31,8 @@ class Lexer          "/^(status:)/"                                   => 'T_STATUS',          "/^(description:)/"                              => 'T_DESCRIPTION',          "/^(category:)/"                                 => 'T_CATEGORY', +        "/^(column:)/"                                   => 'T_COLUMN', +        "/^(project:)/"                                  => 'T_PROJECT',          "/^(\s+)/"                                       => 'T_WHITESPACE',          '/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/'      => 'T_DATE',          '/^(yesterday|tomorrow|today)/'                  => 'T_DATE', @@ -109,6 +111,8 @@ class Lexer                  case 'T_ASSIGNEE':                  case 'T_COLOR':                  case 'T_CATEGORY': +                case 'T_COLUMN': +                case 'T_PROJECT':                      $next = next($tokens);                      if ($next !== false && $next['token'] === 'T_STRING') { diff --git a/app/Model/TaskFilter.php b/app/Model/TaskFilter.php index 4a086078..31080cb5 100644 --- a/app/Model/TaskFilter.php +++ b/app/Model/TaskFilter.php @@ -62,6 +62,12 @@ class TaskFilter extends Base                  case 'T_CATEGORY':                      $this->filterByCategoryName($value);                      break; +                case 'T_PROJECT': +                    $this->filterByProjectName($value); +                    break; +                case 'T_COLUMN': +                    $this->filterByColumnName($value); +                    break;              }          } @@ -190,6 +196,29 @@ class TaskFilter extends Base      }      /** +     * 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       *       * @access public @@ -326,6 +355,24 @@ class TaskFilter extends Base      }      /** +     * 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       *       * @access public @@ -382,7 +429,6 @@ class TaskFilter extends Base       */      public function filterByDueDate($date)      { -        $this->query->neq(Task::TABLE.'.date_due', '');          $this->query->neq(Task::TABLE.'.date_due', 0);          $this->query->notNull(Task::TABLE.'.date_due');          return $this->filterWithOperator(Task::TABLE.'.date_due', $date, true); @@ -452,7 +498,7 @@ class TaskFilter extends Base       */      public function findAll()      { -        return $this->query->findAll(); +        return $this->query->asc(Task::TABLE.'.id')->findAll();      }      /** diff --git a/app/Template/search/index.php b/app/Template/search/index.php index 058f428d..47a926f4 100644 --- a/app/Template/search/index.php +++ b/app/Template/search/index.php @@ -15,7 +15,22 @@          <input type="submit" value="<?= t('Search') ?>" class="btn btn-blue"/>      </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>      <?php elseif (! $paginator->isEmpty()): ?>          <?= $this->render('search/results', array( diff --git a/docs/search.markdown b/docs/search.markdown index 6e40874a..1d254b29 100644 --- a/docs/search.markdown +++ b/docs/search.markdown @@ -127,3 +127,20 @@ Attribute: **category**  - Find tasks with a specific category: `category:"Feature Request"`  - Find all tasks that have those categories: `category:"Bug" category:"Improvements"`  - 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` diff --git a/tests/units/LexerTest.php b/tests/units/LexerTest.php index 4d0d67ad..38974357 100644 --- a/tests/units/LexerTest.php +++ b/tests/units/LexerTest.php @@ -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()      {          $lexer = new Lexer; diff --git a/tests/units/TaskFilterTest.php b/tests/units/TaskFilterTest.php index 33e1792e..c68e8880 100644 --- a/tests/units/TaskFilterTest.php +++ b/tests/units/TaskFilterTest.php @@ -124,6 +124,76 @@ class TaskFilterTest extends Base          $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()      {          $dp = new DateParser($this->container);  | 
