Implement base filter library and tests

refs 
This commit is contained in:
Jannis Moßhammer 2013-09-25 14:04:42 +02:00
parent 0ba5d96069
commit 24da98be83
28 changed files with 3909 additions and 2 deletions

@ -0,0 +1,101 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Web\Form;
use Icinga\Web\Controller\ActionController;
use Icinga\Filter\Filter;
use Icinga\Filter\FilterAttribute;
use Icinga\Filter\Type\TextFilter;
use Icinga\Application\Logger;
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
use Icinga\Web\Url;
class FilterController extends ActionController
{
/**
* @var Filter
*/
private $registry;
public function indexAction()
{
$this->registry = new Filter();
$filter = new UrlViewFilter();
$this->view->form = new Form();
$this->view->form->addElement(
'text',
'query',
array(
'name' => 'query',
'label' => 'search',
'type' => 'search',
'data-icinga-component' => 'app/semanticsearch',
'data-icinga-target' => 'host',
'helptext' => 'Filter test'
)
);
$this->view->form->addElement(
'submit',
'btn_submit',
array(
'name' => 'submit'
)
);
$this->setupQueries();
$this->view->form->setRequest($this->getRequest());
if ($this->view->form->isSubmittedAndValid()) {
$tree = $this->registry->createQueryTreeForFilter($this->view->form->getValue('query'));
$this->view->tree = new \Icinga\Web\Widget\FilterBadgeRenderer($tree);
} else if ($this->getRequest()->getHeader('accept') == 'application/json') {
$this->getResponse()->setHeader('Content-Type', 'application/json');
$this->_helper->json($this->parse($this->getRequest()->getParam('query', '')));
}
}
private function setupQueries()
{
$this->registry->addDomain(\Icinga\Module\Monitoring\Filter\MonitoringFilter::hostFilter());
}
private function parse($text)
{
try {
return $this->registry->getProposalsForQuery($text);
} catch (\Exception $exc) {
Logger::error($exc);
}
}
}

@ -0,0 +1,7 @@
<?php
echo $this->form;
if ($this->tree) {
echo $this->tree->render($this);
}

11
doc/semantic_search.md Normal file

@ -0,0 +1,11 @@
All critical hosts starting with 'MySql'
All services with status warning that have been checked in the last two days
Services with open Problems and with critical hosts
with services that are not ok
[(SUBJECT)] [(SPECIFIED)] [(OP)] [FILTER] [(ADDITIONAL)]

@ -0,0 +1,145 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter;
use Icinga\Filter\Query\Node;
/**
* A Filter domain represents an object that supports filter operations and is basically a
* container for filter attribute
*
*/
class Domain extends QueryProposer
{
/**
* The label to filter for
*
* @var string
*/
private $label;
/**
* @var array
*/
private $attributes = array();
/**
* Create a new domain identified by the given label
*
* @param $label
*/
public function __construct($label)
{
$this->label = trim($label);
}
/**
* Return true when this domain handles a given query (even if it's incomplete)
*
* @param String $query The query to test this domain with
* @return bool True if this domain can handle the query
*/
public function handlesQuery($query)
{
$query = trim($query);
return stripos($query, $this->label) === 0;
}
/**
* Register an attribute to be handled for this filter domain
*
* @param FilterAttribute $attr The attribute object to add to the filter
* @return self Fluent interface
*/
public function registerAttribute(FilterAttribute $attr)
{
$this->attributes[] = $attr;
return $this;
}
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
* @return array An array containing 0..* proposal text tokens
*/
public function getProposalsForQuery($query)
{
$query = trim($query);
if ($this->handlesQuery($query)) {
// remove domain portion of the query
$query = trim(substr($query, strlen($this->label)));
}
$proposals = array();
foreach ($this->attributes as $attributeHandler) {
$proposals = array_merge($proposals, $attributeHandler->getProposalsForQuery($query));
}
return $proposals;
}
/**
* Return the label identifying this domain
*
* @return string the label for this domain
*/
public function getLabel()
{
return $this->label;
}
/**
* Create a query tree node representing the given query and using the field given as
* $leftOperand as the attribute (left leaf of the tree)
*
* @param String $query The query to create the node from
* @param String $leftOperand The attribute use for the node
* @return Node|null
*/
public function convertToTreeNode($query)
{
if ($this->handlesQuery($query)) {
// remove domain portion of the query
$query = trim(substr($query, strlen($this->label)));
}
foreach ($this->attributes as $attributeHandler) {
if ($attributeHandler->isValidQuery($query)) {
$node = $attributeHandler->convertToTreeNode($query);
if ($node) {
$node->context = $this->label;
}
return $node;
}
}
return null;
}
}

@ -0,0 +1,295 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter;
use Icinga\Filter\Query\Tree;
use Icinga\Filter\Query\Node;
/**
* Class for filter input and query parsing
*
* This class handles the top level parsing of queries, i.e.
* - Splitting queries at conjunctions and parsing them part by part
* - Delegating the query parts to specific filter domains handling this filters
* - Building a query tree that allows to convert a filter representation into others (url to string, string to url, sql..)
*
* Filters are split in Filter Domains, Attributes and Types:
*
* Attribute
* Domain | FilterType
* _|__ _|_ ______|____
* / \/ \/ \
* Host name is not 'test'
*
*/
class Filter extends QueryProposer
{
/**
* The default domain to use, if not set the first added domain
*
* @var null
*/
private $defaultDomain = null;
/**
* An array containing all query parts that couldn't be parsed
*
* @var array
*/
private $ignoredQueryParts = array();
/**
* An array containing all domains of this filter
*
* @var array
*/
private $domains = array();
/**
* Create a new domain and return it
*
* @param String $name The field to be handled by this domain
*
* @return Domain The created domain object
*/
public function createFilterDomain($name)
{
$domain = new Domain(trim($name));
$this->domains[] = $domain;
return $domain;
}
/**
* Set the default domain (used if no domain identifier is given to the query) to the given one
*
* @param Domain $domain The domain to use as the default. Will be added to the domain list if not present yet
*/
public function setDefaultDomain(Domain $domain)
{
if (!in_array($domain, $this->domains)) {
$this->domains[] = $domain;
}
$this->defaultDomain = $domain;
}
/**
* Return the default domaon
*
* @return Domain Return either the domain that has been explicitly set as the default domain or the first
* added. If no domain has been added yet null is returned
*/
public function getDefaultDomain()
{
if ($this->defaultDomain !== null) {
return $this->defaultDomain;
} else if (count($this->domains) > 0) {
return $this->domains[0];
}
return null;
}
/**
* Add a domain to this filter
*
* @param Domain $domain The domain to add
* @return self Fluent interface
*/
public function addDomain(Domain $domain)
{
$this->domains[] = $domain;
return $this;
}
/**
* Return all domains that could match the given query
*
* @param String $query The query to search matching domains for
*
* @return array An array containing 0..* domains that could handle the query
*/
public function getDomainsForQuery($query)
{
$domains = array();
foreach ($this->domains as $domain) {
if ($domain->handlesQuery($query)) {
$domains[] = $domain;
}
}
return $domains;
}
/**
* Return the first domain matching for this query (or the default domain)
*
* @param String $query The query to search for a domain
* @return Domain A matching domain or the default domain if no domain is matching
*/
public function getFirstDomainForQuery($query)
{
$domains = $this->getDomainsForQuery($query);
if (empty($domains)) {
$domain = $this->getDefaultDomain();
} else {
$domain = $domains[0];
}
return $domain;
}
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
*
* @return array An array containing 0..* proposal text tokens
*/
public function getProposalsForQuery($query)
{
$query = $this->getLastQueryPart($query);
$proposals = array();
$domains = $this->getDomainsForQuery($query);
foreach ($domains as $domain) {
$proposals = array_merge($proposals, $domain->getProposalsForQuery($query));
}
if (empty($proposals) && $this->getDefaultDomain()) {
foreach ($this->domains as $domain) {
if (stripos($domain->getLabel(), $query) === 0 || $query == '') {
$proposals[] = self::markDifference($domain->getLabel(), $query);
}
}
$proposals = array_merge($proposals, $this->getDefaultDomain()->getProposalsForQuery($query));
}
return $proposals;
}
/**
* Split the query at the next conjunction and return a 3 element array containing (left, conjunction, right)
*
* @param $query The query to split
* @return array An three element tupel in the form array($left, $conjunction, $right)
*/
private function splitQueryAtNextConjunction($query)
{
$delimiter = array('AND', 'OR');
$inStr = false;
for ($i = 0; $i < strlen($query); $i++) {
// Skip strings
$char = $query[$i];
if ($inStr) {
if ($char == $inStr) {
$inStr = false;
}
continue;
}
if ($char === '\'' || $char === '"') {
$inStr = $char;
continue;
}
foreach ($delimiter as $delimiterString) {
$delimiterLength = strlen($delimiterString);
if (strtoupper(substr($query, $i, $delimiterLength)) === $delimiterString) {
// Delimiter, split into left, middle, right part
$nextPartOffset = $i + $delimiterLength;
$left = substr($query, 0, $i);
$conjunction = $delimiterString;
$right = substr($query, $nextPartOffset);
return array(trim($left), $conjunction, trim($right));
}
}
}
return array($query, null, null);
}
/**
* Return the last part of the query
*
* Mostly required for generating query proposals
*
* @param $query The query to scan for the last part
* @return mixed An string containing the rightmost query
*/
private function getLastQueryPart($query)
{
$right = $query;
do {
list($left, $conjuction, $right) = $this->splitQueryAtNextConjunction($right);
} while($conjuction !== null);
return $left;
}
/**
* Create a query tree containing this filter
*
* Query parts that couldn't be parsed can be retrieved with Filter::getIgnoredQueryParts
*
* @param String $query The query string to parse into a query tree
* @return Tree The resulting query tree (empty for invalid queries)
*/
public function createQueryTreeForFilter($query)
{
$this->ignoredQueryParts = array();
$right = $query;
$domain = null;
$tree = new Tree();
do {
list($left, $conjunction, $right) = $this->splitQueryAtNextConjunction($right);
$domain = $this->getFirstDomainForQuery($left);
if ($domain === null) {
$this->ignoredQueryParts[] = $left;
continue;
}
$node = $domain->convertToTreeNode($left);
if (!$node) {
$this->ignoredQueryParts[] = $left;
continue;
}
$tree->insert($node);
if ($conjunction === 'AND') {
$tree->insert(Node::createAndNode());
} elseif($conjunction === 'OR') {
$tree->insert(Node::createOrNode());
}
} while ($right !== null);
return $tree;
}
/**
* Return all parts that couldn't be parsed in the last createQueryTreeForFilter run
*
* @return array An array containing invalid/non-parseable query strings
*/
public function getIgnoredQueryParts()
{
return $this->ignoredQueryParts;
}
}

@ -0,0 +1,235 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter;
use Icinga\Filter\Query\Node;
use Icinga\Filter\Type\FilterType;
/**
* Filter attribute class representing one possible filter for a specific domain
*
* These classes contain a Filter Type to determine possible operators/values etc.
* Often the filter class directly contains the attribute and handles field => attribute mapping,
* but one exception is the BooleanFilter, which overwrites the attribute to use for a more convenient.
*
* Basically, this component maps multiple attributes to one specific field.
*/
class FilterAttribute extends QueryProposer
{
/**
* The FilterType object that handles operations on this attribute
*
* @var Type\FilterType
*/
private $type;
/**
* An array of attribute tokens to map, or empty to let the filter type choose it's own attribute
* and skip this class
*
* @var array
*/
private $attributes = array();
/**
* The field that is being represented by the given attributes
*
* @var String
*/
private $field;
/**
* Create a new FilterAttribute using the given type as the filter Type
*
* @param FilterType $type The type of this filter
*/
public function __construct(FilterType $type)
{
$this->type = $type;
}
/**
* Set a list of attributes to be mapped to this filter
*
* @param String $attr An attribute to be recognized by this filter
* @param String ...
*
* @return self Fluent interface
*/
public function setHandledAttributes($attr)
{
if (!$this->field) {
$this->field = $attr;
}
foreach(func_get_args() as $arg) {
$this->attributes[] = trim($arg);
}
return $this;
}
/**
* Set the field to be represented by this FilterAttribute
*
* The field is always unique while the attributes are ambiguous.
*
* @param String $field The field this Attribute collection maps to
*
* @return self Fluent Interface
*/
public function setField($field)
{
$this->field = $field;
return $this;
}
/**
* Return the largest attribute that matches this query or null if none matches
*
* @param String $query The query to search for containing an attribute
*
* @return String The attribute to be used or null
*/
private function getMatchingAttribute($query)
{
$query = trim($query);
foreach ($this->attributes as $attribute) {
if (stripos($query, $attribute) === 0) {
return $attribute;
}
}
return null;
}
/**
* Return true if this query contains an attribute mapped by this object
*
* @param String $query The query to search for the attribute
*
* @return bool True when this query contains an attribute mapped by this filter
*/
public function queryHasSupportedAttribute($query) {
return $this->getMatchingAttribute($query) !== null;
}
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
*
* @return array An array containing 0..* proposal text tokens
*/
public function getProposalsForQuery($query)
{
$query = trim($query);
$attribute = $this->getMatchingAttribute($query);
if ($attribute !== null || count($this->attributes) == 0) {
$subQuery = trim(substr($query, strlen($attribute)));
return $this->type->getProposalsForQuery($subQuery);
} else {
return $this->getAttributeProposalsForQuery($query);
}
}
/**
* Return an array of possible attributes that can be used for completing the query
*
* @param String $query The query to fetch completion proposals for
*
* @return array An array containing 0..* strings with possible completions
*/
public function getAttributeProposalsForQuery($query)
{
if ($query === '') {
if (count($this->attributes)) {
return array($this->attributes[0]);
} else {
return $this->type->getProposalsForQuery($query);
}
}
$proposals = array();
foreach ($this->attributes as $attribute) {
if (stripos($attribute, $query) === 0) {
$proposals[] = self::markDifference($attribute, $query);
break;
}
}
return $proposals;
}
/**
* Return true if $query is a valid query for this filter, otherwise false
*
* @param String $query The query to validate
*
* @return bool True if $query represents a valid filter for this object, otherwise false
*/
public function isValidQuery($query)
{
$attribute = $this->getMatchingAttribute($query);
if ($attribute === null && count($this->attributes) > 0) {
return false;
}
$subQuery = trim(substr($query, strlen($attribute)));
return $this->type->isValidQuery($subQuery);
}
/**
* Convert the given query to a tree node
*
* @param String $query The query to convert to a tree node
*
* @return Node The tree node representing this query or null if the query is not valid
*/
public function convertToTreeNode($query)
{
if (!$this->isValidQuery($query)) {
return null;
}
$lValue = $this->getMatchingAttribute($query);
$subQuery = trim(substr($query, strlen($lValue)));
return $this->type->createTreeNode($subQuery, $this->field);
}
/**
* Factory method to make filter creation more convenient, same as the constructor
*
* @param FilterType $type The filtertype to use for this attribute
*
* @return FilterAttribute An instance of FilterAttribute
*/
public static function create(FilterType $type)
{
return new FilterAttribute($type);
}
}

@ -0,0 +1,135 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter\Query;
/**
* Container class for the Node of a query tree
*/
class Node
{
const TYPE_AND = 'AND';
const TYPE_OR = 'OR';
const TYPE_OPERATOR = 'OPERATOR';
const OPERATOR_EQUALS = '=';
const OPERATOR_EQUALS_NOT = '!=';
const OPERATOR_GREATER = '>';
const OPERATOR_LESS = '<';
const OPERATOR_GREATER_EQ = '>=';
const OPERATOR_LESS_EQ = '<=';
/**
* Array containing all possible operators
*
* @var array
*/
static public $operatorList = array(
self::OPERATOR_EQUALS, self::OPERATOR_EQUALS_NOT, self::OPERATOR_GREATER,
self::OPERATOR_LESS, self::OPERATOR_GREATER_EQ, self::OPERATOR_LESS_EQ
);
/**
* The type of this node
*
* @var string
*/
public $type = self::TYPE_OPERATOR;
/**
* The operator of this node, if type is TYPE_OPERATOR
*
* @var string
*/
public $operator = '';
/**
* The parent of this node or null if no parent exists
*
* @var Node
*/
public $parent;
/**
* The left element of this Node
*
* @var String|Node
*/
public $left;
/**
* The right element of this Node
*
* @var String|Node
*/
public $right;
/**
* Factory method for creating operator nodes
*
* @param String $operator The operator to use
* @param String $left The left side of the node, i.e. target (mostly attribute) to query for with this node
* @param String $right The right side of the node, i.e. the value to use for querying
*
* @return Node An operator Node instance
*/
public static function createOperatorNode($operator, $left, $right)
{
$node = new Node();
$node->type = self::TYPE_OPERATOR;
$node->operator = $operator;
$node->left = $left;
$node->right = $right;
return $node;
}
/**
* Factory method for creating an AND conjunction node
*
* @return Node An AND Node instance
*/
public static function createAndNode()
{
$node = new Node();
$node->type = self::TYPE_AND;
return $node;
}
/**
* Factory method for creating an OR conjunction node
*
* @return Node An OR Node instance
*/
public static function createOrNode()
{
$node = new Node();
$node->type = self::TYPE_OR;
return $node;
}
}

@ -0,0 +1,171 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter\Query;
/**
* A binary tree representing queries in an interchangeable way
*
* This tree should always be created from queries and used to create queries
* or convert query formats. Currently it doesn't support grouped expressions,
* although this can be implemented rather easily in the tree (the problem is more or less
* how to implement it in query languages)
*/
class Tree
{
/**
* The curretnt root node of the Tree
*
* @var Node|null
*/
public $root;
/**
* The last inserted node of the Tree
*
* @var Node|null
*/
private $lastNode;
/**
* Insert a node into this tree, recognizing type and insert position
*
* @param Node $node The node to insert into the tree
*/
public function insert(Node $node)
{
if ($this->root === null) {
$this->root = $node;
} else {
switch ($node->type) {
case Node::TYPE_AND:
$this->insertAndNode($node, $this->root);
break;
case Node::TYPE_OR:
$this->insertOrNode($node, $this->root);
break;
case Node::TYPE_OPERATOR:
$node->parent = $this->lastNode;
if ($this->lastNode->left == null) {
$this->lastNode->left = $node;
} else if($this->lastNode->right == null) {
$this->lastNode->right = $node;
}
break;
}
}
$this->lastNode = $node;
}
/**
* Determine the insert position of an AND node, using $currentNode as the parent node
* and insert the tree
*
* And nodes are always with a higher priority than other nodes and only traverse down the tree
* when encountering another AND tree on the way
*
* @param Node $node The node to insert
* @param Node $currentNode The current node context
*/
private function insertAndNode(Node $node, Node $currentNode)
{
if ($currentNode->type != Node::TYPE_AND) {
// No AND node, insert into tree
if($currentNode->parent !== null) {
$node->parent = $currentNode->parent;
if ($currentNode->parent->left === $currentNode) {
$currentNode->parent->left = $node;
} else {
$currentNode->parent->right = $node;
}
} else {
$this->root = $node;
}
$currentNode->parent = $node;
if ($node->left) {
$currentNode->right = $node->left;
}
$node->left = $currentNode;
$node->parent = null;
return;
} elseif ($currentNode->left == null) {
// Insert right if there's place
$currentNode->left = $node;
$node->parent = $currentNode;
} elseif ($currentNode->right == null) {
// Insert right if there's place
$currentNode->right = $node;
$node->parent = $currentNode;
} else {
// traverse down the tree if free insertion point is found
$this->insertAndNode($node, $currentNode->right);
}
}
/**
* Insert an OR node
*
* OR nodes are always inserted over operator nodes but below AND nodes
*
* @param Node $node The OR node to insert
* @param Node $currentNode The current context to use for insertion
*/
private function insertOrNode(Node $node, Node $currentNode)
{
if ($currentNode->type === Node::TYPE_OPERATOR) {
// Always insert when encountering an operator node
if($currentNode->parent !== null) {
$node->parent = $currentNode->parent;
if ($currentNode->parent->left === $currentNode) {
$currentNode->parent->left = $node;
} else {
$currentNode->parent->right = $node;
}
} else {
$this->root = $node;
}
$currentNode->parent = $node;
$node->left = $currentNode;
} elseif ($currentNode->left === null) {
$currentNode->left = $node;
$node->parent = $currentNode;
return;
} elseif ($currentNode->right === null) {
$currentNode->right = $node;
$node->parent = $currentNode;
return;
} else {
$this->insertOrNode($node, $currentNode->right);
}
}
}

@ -0,0 +1,65 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter;
/**
* Base class for Query proposers
*
* Query Proposer accept an query string in their getProposalsForQuery method and return
* possible parts to complete this query
*/
abstract class QueryProposer
{
/**
* Static helper function to encapsulate similar string parts with an {}
*
* @param $attribute The attribute to mark differences in
* @param $query The query to use for determining similarities
*
* @return string The attribute string with similar parts encapsulated in curly braces
*/
public static function markDifference($attribute, $query)
{
if (strlen($query) === 0) {
return $attribute;
}
return '{' . substr($attribute, 0, strlen($query)) . '}' . substr($attribute, strlen($query));
}
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
*
* @return array An array containing 0..* proposal text tokens
*/
abstract public function getProposalsForQuery($query);
}

@ -0,0 +1,236 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter\Type;
use Icinga\Filter\Query\Node;
/**
* Boolean filter for setting flag filters (host is in problem state)
*
*/
class BooleanFilter extends FilterType
{
/**
* The operqator map to use
*
* @var array
*/
private $operators = array(
Node::OPERATOR_EQUALS => 'Is',
Node::OPERATOR_EQUALS_NOT => 'Is Not'
);
/**
* The fields that are supported by this filter
*
* These fields somehow break the mechanismn as they overwrite the field given in the
* Attribute
*
* @var array
*/
private $fields = array();
/**
* An TimeRangeSpecifier if a field is given
*
* @var TimeRangeSpecifier
*/
private $subFilter;
/**
* An optional field to use for time information (no time filters are possible if this is not given)
*
* @var string
*/
private $timeField;
/**
* Create a new Boolean Filter handling the given field mapping
*
* @param array $fields The fields to use, in a internal_key => Text token mapping
* @param String $timeField An optional time field, allows time specifiers to be appended to the query if given
*/
public function __construct(array $fields, $timeField = false)
{
$this->fields = $fields;
if (is_string($timeField)) {
$this->subFilter = new TimeRangeSpecifier();
$this->timeField = $timeField;
}
}
/**
* Overwrite the text to use for operators
*
* @param String $positive The 'set flag' operator (default: 'is')
* @param String $negative The 'unset flag' operator (default: 'is not')
*/
public function setOperators($positive, $negative)
{
$this->operators = array(
Node::OPERATOR_EQUALS => $positive,
Node::OPERATOR_EQUALS_NOT => $negative
);
}
/**
* Return a proposal for completing a field given the $query string
*
* @param String $query The query to get the proposal from
* @return array An array containing text tokens that could be used for completing the query
*/
private function getFieldProposals($query)
{
$proposals = array();
foreach ($this->fields as $key => $field) {
$match = null;
if (self::startsWith($field, $query)) {
$match = $field;
} elseif (self::startsWith($key, $query)) {
$match = $key;
} else {
continue;
}
if (self::startsWith($query, $match) && $this->subFilter) {
$subQuery = trim(substr($query, strlen($match)));
$proposals = $proposals + $this->subFilter->getProposalsForQuery($subQuery);
} else if (strtolower($query) !== strtolower($match)) {
$proposals[] = self::markDifference($match, $query);
}
}
return $proposals;
}
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
*
* @return array An array containing 0..* proposal text tokens
*/
public function getProposalsForQuery($query)
{
$proposals = array();
$operators = $this->getOperators();
if ($query === '') {
return $this->getOperators();
}
foreach ($operators as $operator) {
if (strtolower($operator) === strtolower($query)) {
$proposals += array_values($this->fields);
} else if (self::startsWith($operator, $query)) {
$proposals[] = self::markDifference($operator, $query);
} else if (self::startsWith($query, $operator)) {
$fieldPart = trim(substr($query, strlen($operator)));
$proposals = $proposals + $this->getFieldProposals($fieldPart);
}
}
return $proposals;
}
/**
* Return every possible operator of this Filter type
*
* @return array An array
*/
public function getOperators()
{
return $this->operators;
}
/**
* Return true when the given query is valid for this type
*
* @param String $query The query to test for this filter type
* @return bool True if the query can be parsed by this filter type
*/
public function isValidQuery($query)
{
list($field, $operator, $subQuery) = $this->getFieldValueForQuery($query);
$valid = ($field !== null && $operator !== null);
if ($valid && $subQuery && $this->subFilter !== null) {
$valid = $this->subFilter->isValidQuery($subQuery);
}
return $valid;
}
/**
* Return a 3 element tupel with array(field, value, right) from the given query part
*
* @param String $query The query string to use
* @return array An 3 element tupel containing the field, value and optionally the right
* side of the query
*/
public function getFieldValueForQuery($query)
{
$operator = $this->getMatchingOperatorForQuery($query);
if (!$operator) {
return array(null, null, null);
}
$operatorList = array_flip($this->operators);
$query = trim(substr($query, strlen($operator)));
$operator = $operatorList[$operator];
foreach ($this->fields as $key => $field) {
if (self::startsWith($query, $field)) {
$subQuery = trim(substr($query, strlen($field)));
return array($key, $operator === Node::OPERATOR_EQUALS ? 1 : 0, $subQuery);
}
}
return array(null, null, null);
}
/**
* Create a query tree node representing the given query and using the field given as
* $leftOperand as the attribute (left leaf of the tree)
*
* @param String $query The query to create the node from
* @param String $leftOperand The attribute use for the node
* @return Node|null
*/
public function createTreeNode($query, $leftOperand)
{
list($field, $value, $subQuery) = $this->getFieldValueForQuery($query);
if ($field === null || $value === null) {
return null;
}
$node = Node::createOperatorNode(Node::OPERATOR_EQUALS, $field, $value);
if ($this->subFilter && $subQuery && $this->subFilter->isValidQuery($subQuery)) {
$subNode = $this->subFilter->createTreeNode($subQuery, $this->timeField);
$conjunctionNode = Node::createAndNode();
$conjunctionNode->left = $subNode;
$conjunctionNode->right = $node;
$node = $conjunctionNode;
}
return $node;
}
}

@ -0,0 +1,100 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter\Type;
use Icinga\Filter\QueryProposer;
/**
* A specific type of filter
*
* Implementations represent specific filters like text, monitoringstatus, time, flags, etc
*
*/
abstract class FilterType extends QueryProposer
{
/**
* Return a list containing all operators that can appear in this filter type
*
* @return array An array of strings
*/
abstract public function getOperators();
/**
* Return true if the given query is valid for this type
*
* @param String $query The query string to validate
*
* @return boolean True when the query can be converted to a tree node, otherwise false
*/
abstract public function isValidQuery($query);
/**
* Return a tree node representing the given query that can be inserted into a query tree
*
* @param String $query The query to parse into a Node
* @param String $leftOperand The field to use for the left (target) side of the node
*
* @return Node A tree node
*/
abstract public function createTreeNode($query, $leftOperand);
/**
* More verbose helper method for testing whether a string starts with the second one
*
* @param String $string The string to use as the haystack
* @param String $substring The string to use as the needle
*
* @return bool True when $string starts with $substring
*/
static public function startsWith($string, $substring)
{
return stripos($string, $substring) === 0;
}
/**
* Get the operator that matches the given query best (i.e. the one with longest matching string)
*
* @param String $query The query to extract the operator from
*
* @return string The operator contained in this query or an empty string if no operator matches
*/
protected function getMatchingOperatorForQuery($query)
{
$matchingOperator = '';
foreach ($this->getOperators() as $operator) {
if (stripos($query, $operator) === 0) {
if (strlen($matchingOperator) < strlen($operator) ){
$matchingOperator = $operator;
}
}
}
return $matchingOperator;
}
}

@ -0,0 +1,211 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter\Type;
use Icinga\Filter\Query\Node;
class TextFilter extends FilterType
{
/**
* Mapping of possible text tokens to normalized operators
*
* @var array
*/
private $operators = array(
'Is' => Node::OPERATOR_EQUALS,
'Is Not' => Node::OPERATOR_EQUALS_NOT,
'Starts With' => Node::OPERATOR_EQUALS,
'Ends With' => Node::OPERATOR_EQUALS,
'Contains' => Node::OPERATOR_EQUALS,
'=' => Node::OPERATOR_EQUALS,
'!=' => Node::OPERATOR_EQUALS_NOT,
'Like' => Node::OPERATOR_EQUALS,
'Matches' => Node::OPERATOR_EQUALS
);
/**
* Return all possible operator tokens for this filter
*
* @return array
*/
public function getOperators()
{
return array_keys($this->operators);
}
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
*
* @return array An array containing 0..* proposal text tokens
*/
public function getProposalsForQuery($query)
{
$proposals = array();
$operators = $this->getOperators();
if ($query === '') {
return $this->getOperators();
}
foreach ($operators as $operator) {
if (strtolower($operator) === strtolower($query)) {
$proposals += array('\'' . $this->getProposalsForValues($operator) . '\'');
} else if (self::startsWith($operator, $query)) {
$proposals[] = self::markDifference($operator, $query);
}
}
return $proposals;
}
/**
* Return a (operator, value) tupel representing the given query or (null, null) if
* the input is not valid
*
* @param String $query The query part to extract the operator and value from
* @return array An array containg the operator as the first item and the value as the second
* or (null, null) if parsing is not possible for this query
*/
public function getOperatorAndValueFromQuery($query)
{
$matchingOperator = $this->getMatchingOperatorForQuery($query);
if (!$matchingOperator) {
return array(null, null);
}
$valuePart = trim(substr($query, strlen($matchingOperator)));
if ($valuePart == '') {
return array($matchingOperator, null);
}
$this->normalizeQuery($matchingOperator, $valuePart);
return array($matchingOperator, $valuePart);
}
/**
* Return true when the given query is valid for this type
*
* @param String $query The query to test for this filter type
* @return bool True if the query can be parsed by this filter type
*/
public function isValidQuery($query)
{
list ($operator, $value) = $this->getOperatorAndValueFromQuery($query);
return $operator !== null && $value !== null;
}
/**
* Create a query tree node representing the given query and using the field given as
* $leftOperand as the attribute (left leaf of the tree)
*
* @param String $query The query to create the node from
* @param String $leftOperand The attribute use for the node
* @return Node|null
*/
public function createTreeNode($query, $leftOperand)
{
list ($operator, $value) = $this->getOperatorAndValueFromQuery($query);
if ($operator === null || $value === null) {
return null;
}
$node = new Node();
$node->type = Node::TYPE_OPERATOR;
$node->operator = $operator;
$node->left = $leftOperand;
$node->right = $value;
return $node;
}
/**
* Normalize the operator and value for the given query
*
* This removes quotes and adds wildcards for specific operators.
* The operator and value will be modified in this method and can be
* added to a QueryNode afterwards
*
* @param String $operator A reference to the operator string
* @param String $value A reference to the value string
*/
private function normalizeQuery(&$operator, &$value)
{
$value = trim($value);
if ($value[0] == '\'' || $value[0] == '"') {
$value = substr($value, 1);
}
$lastPos = strlen($value) - 1;
if ($value[$lastPos] == '"' || $value[$lastPos] == '\'') {
$value = substr($value, 0, -1);
}
switch (strtolower($operator)) {
case 'starts with':
$value = '*' . $value;
break;
case 'ends with':
$value = $value . '*';
break;
case 'matches':
case 'contains':
$value = '*' . $value . '*';
break;
}
foreach ($this->operators as $operatorType => $type) {
if (strtolower($operatorType) === strtolower($operator)) {
$operator = $type;
}
}
}
/**
* Return generic value proposals for the given operator
*
* @param String $operator The operator string to create a proposal for
* @return string The created proposals
*/
public function getProposalsForValues($operator)
{
switch (strtolower($operator)) {
case 'starts with':
return 'value...';
case 'ends with':
return '...value';
case 'is':
case 'is not':
case '=':
case '!=':
return 'value';
case 'matches':
case 'contains':
case 'like':
return '...value...';
}
}
}

@ -0,0 +1,215 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Filter\Type;
use Icinga\Filter\Query\Node;
/**
* Filter Type for specifying time points. Uses valid inputs for strtotime as the
* Filter value
*
*/
class TimeRangeSpecifier extends FilterType
{
private $forcedPrefix = null;
/**
* Default operator to use
*
* @var array A Text Token => Operator mapping for every supported operator
*/
private $operator = array(
'Since' => Node::OPERATOR_GREATER_EQ,
'Before' => Node::OPERATOR_LESS_EQ
);
/**
* Example values that will be displayed to the user
*
* @var array
*/
public $timeExamples = array(
'"5 minutes"',
'"30 minutes"',
'"1 hour"',
'"6 hours"',
'"1 day"',
'"yesterday"',
'"last Monday"'
);
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
* @return array An array containing 0..* proposal text tokens
*/
public function getProposalsForQuery($query)
{
if ($query === '') {
return $this->getOperators();
}
$proposals = array();
foreach ($this->getOperators() as $operator) {
if (self::startsWith($query, $operator)) {
if (!trim(substr($query, strlen($operator)))) {
$proposals = array_merge($proposals, $this->timeExamples);
}
} elseif (self::startsWith($operator, $query)) {
$proposals[] = self::markDifference($operator, $query);
}
}
return $proposals;
}
/**
* Return an array containing the textual representation of all operators represented by this filter
*
* @return array An array of operator string
*/
public function getOperators()
{
return array_keys($this->operator);
}
/**
* Return a two element array with the operator and the timestring parsed from the given query part
*
* @param String $query The query to extract the operator and time value from
* @return array An array containing the operator as the first and the string for strotime as the second
* value or (null,null) if the query is invalid
*/
private function getOperatorAndTimeStringFromQuery($query)
{
$currentOperator = null;
foreach ($this->operator as $operator => $type) {
if (self::startsWith($query, $operator)) {
$currentOperator = $type;
$query = trim(substr($query, strlen($operator)));
break;
}
}
$query = trim($query, '\'"');
if (!$query || $currentOperator === null) {
return array(null, null);
}
if (is_numeric($query[0])) {
if($this->forcedPrefix) {
$prefix = $this->forcedPrefix;
} elseif($currentOperator === Node::OPERATOR_GREATER_EQ) {
$prefix = '-';
} else {
$prefix = '+';
}
$query = $prefix . $query;
}
if (!strtotime($query)) {
return array(null, null);
}
return array($currentOperator, $query);
}
/**
* Return true if the query is valid, otherwise false
*
* @param String $query The query string to validate
* @return bool True if the query is valid, otherwise false
*/
public function isValidQuery($query)
{
list($operator, $timeQuery) = $this->getOperatorAndTimeStringFromQuery($query);
return $timeQuery !== null;
}
/**
* Create a query tree node representing the given query and using the field given as
* $leftOperand as the attribute (left leaf of the tree)
*
* @param String $query The query to create the node from
* @param String $leftOperand The attribute use for the node
* @return Node|null
*/
public function createTreeNode($query, $leftOperand)
{
list($operator, $timeQuery) = $this->getOperatorAndTimeStringFromQuery($query);
if ($operator === null || $timeQuery === null) {
return null;
}
return Node::createOperatorNode($operator, $leftOperand, $timeQuery);
}
/**
* Set possible operators for this query, in a 'stringtoken' => NodeOperatorConstant map
*
* @param array $operator The operator map to use
* @return $this Fluent interface
*/
public function setOperator(array $operator)
{
$this->operator = $operator;
return $this;
}
/**
* Set all implicit values ('after 30 minutes') to be in the past ('after -30 minutes')
*
* @param True $bool True to set all timestring in the past
* @return $this Fluent interface
*/
public function setForcePastValue($bool = true)
{
if ($bool) {
$this->forcedPrefix = '-';
} else {
$this->forcedPrefix = null;
}
return $this;
}
/**
* Set all implicit values ('after 30 minutes') to be in the future ('after +30 minutes')
*
* @param True $bool True to set all timestring in the future
* @return $this Fluent interface
*/
public function setForceFutureValue($bool = true)
{
if ($bool) {
$this->forcedPrefix = '+';
} else {
$this->forcedPrefix = null;
}
return $this;
}
}

@ -57,6 +57,7 @@ class Monitoring_ListController extends MonitoringController
* @var Backend
*/
protected $backend;
/**
* Compact layout name
*
@ -432,7 +433,6 @@ class Monitoring_ListController extends MonitoringController
*/
private function createTabs()
{
$tabs = $this->getTabs();
$tabs->extend(new OutputFormat())
->extend(new DashboardAction());

@ -223,7 +223,6 @@ class StatusQuery extends AbstractQuery
protected function joinBaseTables()
{
// TODO: Shall we always add hostobject?
$this->baseQuery = $this->db->select()->from(
array('ho' => $this->prefix . 'objects'),
array()

@ -0,0 +1,45 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Filter\Backend;
use Icinga\Filter\Query\Tree;
class IdoQueryConverter
{
public function treeToSql(Tree $tree)
{
if ($tree->root = null) {
return '';
}
}
}

@ -0,0 +1,108 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Filter;
use Icinga\Filter\Domain;
use Icinga\Filter\FilterAttribute;
use Icinga\Filter\Query\Node;
use Icinga\Filter\Type\BooleanFilter;
use Icinga\Filter\Type\TextFilter;
use Icinga\Filter\Type\TimeRangeSpecifier;
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
/**
* Factory class to create filter for different monitoring objects
*
*/
class MonitoringFilter
{
private static function getNextCheckFilterType()
{
$type = new TimeRangeSpecifier();
$type->setOperator(
array(
'Until' => Node::OPERATOR_LESS_EQ,
'After' => Node::OPERATOR_GREATER_EQ
)
)->setForceFutureValue(true);
return $type;
}
private static function getLastCheckFilterType()
{
$type = new TimeRangeSpecifier();
$type->setOperator(
array(
'Older Than' => Node::OPERATOR_LESS_EQ,
'Is Older Than' => Node::OPERATOR_LESS_EQ,
'Newer Than' => Node::OPERATOR_GREATER_EQ,
'Is Newer Than' => Node::OPERATOR_GREATER_EQ,
)
)->setForcePastValue(true);
return $type;
}
public static function hostFilter()
{
$domain = new Domain('Host');
$domain->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('Name', 'Hostname')
->setField('host_name')
)->registerAttribute(
FilterAttribute::create(StatusFilter::createForHost())
->setHandledAttributes('State', 'Status', 'Current Status')
->setField('host_state')
)->registerAttribute(
FilterAttribute::create(new BooleanFilter(array(
'host_is_flapping' => 'Flapping',
'host_problem' => 'In Problem State',
'host_notifications_enabled' => 'Sending Notifications',
'host_active_checks_enabled' => 'Active',
'host_passive_checks_enabled' => 'Accepting Passive Checks',
'host_handled' => 'Handled',
'host_in_downtime' => 'In Downtime',
)))
)->registerAttribute(
FilterAttribute::create(self::getLastCheckFilterType())
->setHandledAttributes('Last Check', 'Check')
->setField('host_last_check')
)->registerAttribute(
FilterAttribute::create(self::getNextCheckFilterType())
->setHandledAttributes('Next Check')
->setField('host_next_check')
);
return $domain;
}
}

@ -0,0 +1,321 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Filter\Type;
use Icinga\Filter\Query\Node;
use Icinga\Filter\Type\FilterType;
use Icinga\Filter\Type\TextFilter;
use Icinga\Filter\Type\TimeRangeSpecifier;
/**
* Filter type for monitoring states
*
* It's best to use the StatusFilter::createForHost and StatusFilter::createForService
* factory methods as those correctly initialize possible states
*
*/
class StatusFilter extends FilterType
{
/**
* An array containing a mapping of the textual state representation ('Ok', 'Down', etc.)
* as the keys and the numeric value mapped by this state as the value
*
* @var array
*/
private $baseStates = array();
/**
* An array containing all possible textual operator tokens mapped to the
* normalized query operator
*
* @var array
*/
private $operators = array(
'Is' => Node::OPERATOR_EQUALS,
'=' => Node::OPERATOR_EQUALS,
'!=' => Node::OPERATOR_EQUALS_NOT,
'Is not' => Node::OPERATOR_EQUALS_NOT
);
/**
* The type of this filter ('host' or 'service')
*
* @var string
*/
private $type = '';
/**
* The timerange subfilter that can be appended to this filter
*
* @var TimeRangeSpecifier
*/
private $subFilter;
/**
* Create a new StatusFilter and initialize the internal state correctly.
*
* It's best to use the factory methods instead of new as a call to
* setBaseStates is necessary on direct creation
*
*/
public function __construct()
{
$this->subFilter = new TimeRangeSpecifier();
}
/**
* Set the type for this filter (host or service)
*
* @param String $type Either 'host' or 'service'
*/
public function setType($type)
{
$this->type = $type;
}
/**
* Create a StatusFilter instance that has been initialized for host status filters
*
* @return StatusFilter The ready-to-use host status filter
*/
public static function createForHost()
{
$status = new StatusFilter();
$status->setBaseStates(
array(
'Up' => 0,
'Down' => 1,
'Unreachable' => 2,
'Pending' => 99
)
);
$status->setType('host');
return $status;
}
/**
* Create a StatusFilter instance that has been initialized for service status filters
*
* @return StatusFilter The ready-to-use service status filter
*/
public static function createForService()
{
$status = new StatusFilter();
$status->setType(self::TYPE_SERVICE);
$status->setBaseStates(
array(
'Ok' => 0,
'Warning' => 1,
'Critical' => 2,
'Unknown' => 3,
'Pending' => 99
)
);
$status->setType('service');
return $status;
}
/**
* Return proposals for the given query part
*
* @param String $query The part of the query that this specifier should parse
*
* @return array An array containing 0..* proposal text tokens
*/
public function getProposalsForQuery($query)
{
if ($query == '') {
return $this->getOperators();
}
$proposals = array();
foreach ($this->getOperators() as $operator) {
if (stripos($operator, $query) === 0 && strlen($operator) < strlen($query)) {
$proposals[] = self::markDifference($operator, $query);
} elseif (stripos($query, $operator) === 0) {
$subQuery = trim(substr($query, strlen($operator)));
$proposals = $this->getValueProposalsForQuery($subQuery);
}
}
return $proposals;
}
/**
* Return an array containing all possible states
*
* @return array An array containing all states mapped by this filter
*/
private function getAllStates()
{
return array_keys($this->baseStates);
}
/**
* Return possible tokens for completing a partial query that already contains an operator
*
* @param String $query The partial query containing the operator
*
* @return array An array of strings that reflect possible query completions
*/
private function getValueProposalsForQuery($query)
{
if ($query == '') {
return $this->getAllStates();
}
$proposals = array();
foreach ($this->getAllStates() as $state) {
if (self::startsWith($query, $state)) {
$subQuery = trim(substr($query, strlen($state)));
$proposals = array_merge($proposals, $this->subFilter->getProposalsForQuery($subQuery));
} elseif (self::startsWith($state, $query)) {
$proposals[] = self::markDifference($state, $query);
}
}
return $proposals;
}
/**
* Return an tuple containing the operator as the first, the value as the second and a possible subquery as the
* third element by parsing the given query
*
* The subquery contains the time information for this status if given
*
* @param String $query The Query to parse with this filter
*
* @return array An array with three elements: array(operator, value, subQuery) or filled with nulls
* if the query is not valid
*/
private function getOperatorValueArray($query)
{
$result = array(null, null, null);
foreach ($this->getOperators() as $operator) {
if (stripos($query, $operator) === 0) {
$result[0] = $operator;
break;
}
}
if ($result[0] === null) {
return $result;
}
$subQuery = trim(substr($query, strlen($result[0])));
foreach ($this->getAllStates() as $state) {
if (self::startsWith($subQuery, $state)) {
$result[1] = $state;
}
}
$result[2] = trim(substr($subQuery, strlen($result[1])));
if ($result[2] && !$this->subFilter->isValidQuery($result[2])) {
return array(null, null, null);
}
return $result;
}
/**
* Return an array containing the textual presentation of all possible operators
*
* @return array
*/
public function getOperators()
{
return array_keys($this->operators);
}
/**
* Return true if the given query is a valid, complete query
*
* @param String $query The query to test for being valid and complete
*
* @return bool True when this query is valid, otherwise false
*/
public function isValidQuery($query)
{
$result = $this->getOperatorValueArray($query);
return $result[0] !== null && $result[1] !== null;
}
/**
* Create a Tree Node from this filter query
*
* @param String $query The query to parse and turn into a Node
* @param String $leftOperand The field to use for the status
*
* @return Node A node object to be added to a query tree
*/
public function createTreeNode($query, $leftOperand)
{
list($operator, $valueSymbol, $timeSpec) = $this->getOperatorValueArray($query);
if ($operator === null || $valueSymbol === null) {
return null;
}
$node = Node::createOperatorNode(
$this->operators[$operator],
$leftOperand,
$this->resolveValue($valueSymbol)
);
if ($timeSpec) {
$left = $node;
$node = Node::createAndNode();
$node->left = $left;
$node->right = $this->subFilter->createTreeNode($timeSpec, $this->type . '_last_state_change');
$node->right->parent = $node;
$node->left->parent = $node;
}
return $node;
}
/**
* Return the numeric representation of state given to this filter
*
* @param String $valueSymbol The state string from the query
*
* @return int The numeric state mapped by $valueSymbol or null if it's an invalid state
*/
private function resolveValue($valueSymbol)
{
if (isset($this->baseStates[$valueSymbol])) {
return $this->baseStates[$valueSymbol];
}
return null;
}
/**
* Set possible states for this filter
*
* Only required when this filter isn't created by one of it's factory methods
*
* @param array $states The states in an associative statename => numeric representation array
*/
public function setBaseStates(array $states)
{
$this->baseStates = $states;
}
}

@ -0,0 +1,213 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Filter;
use Icinga\Filter\Query\Tree;
use Icinga\Filter\Query\Node;
use Icinga\Web\Url;
use Icinga\Application\Logger;
class UrlViewFilter
{
const FILTER_TARGET = 'target';
const FILTER_OPERATOR = 'operator';
const FILTER_VALUE = 'value';
const FILTER_ERROR = 'error';
private function evaluateNode(Node $node)
{
switch($node->type) {
case Node::TYPE_OPERATOR:
return urlencode($node->left) . $node->operator . urlencode($node->right);
case Node::TYPE_AND:
return $this->evaluateNode($node->left) . '&' . $this->evaluateNode($node->right);
case Node::TYPE_OR:
return $this->evaluateNode($node->left) . '|' . $this->evaluateNode($node->right);
}
}
public function fromTree(Tree $filter)
{
return $this->evaluateNode($filter->root);
}
public function parseUrl($query = "")
{
$query = $query ? $query : $_SERVER['QUERY_STRING'];
$tokens = $this->tokenizeQuery($query);
$tree = new Tree();
foreach ($tokens as $token) {
if ($token === '&') {
$tree->insert(Node::createAndNode());
} elseif ($token === '|') {
$tree->insert(Node::createOrNode());
} elseif (is_array($token)) {
$tree->insert(
Node::createOperatorNode(
$token[self::FILTER_OPERATOR],
$token[self::FILTER_TARGET],
$token[self::FILTER_VALUE]
)
);
}
}
return $tree;
}
private function tokenizeQuery($query)
{
$tokens = array();
$state = self::FILTER_TARGET;
for ($i = 0;$i <= strlen($query); $i++) {
switch ($state) {
case self::FILTER_TARGET:
list($i, $state) = $this->parseTarget($query, $i, $tokens);
break;
case self::FILTER_VALUE:
list($i, $state) = $this->parseValue($query, $i, $tokens);
break;
case self::FILTER_ERROR:
list($i, $state) = $this->skip($query, $i, $tokens);
break;
}
}
return $tokens;
}
private function getMatchingOperator($query, $i)
{
$operatorToUse = '';
foreach (Node::$operatorList as $operator) {
if (substr($query, $i, strlen($operator)) === $operator) {
if (strlen($operatorToUse) < strlen($operator)) {
$operatorToUse = $operator;
}
}
}
return $operatorToUse;
}
private function parseTarget($query, $currentPos, array &$tokenList)
{
$conjunctions = array('&', '|');
$i = $currentPos;
for ($i; $i < strlen($query); $i++) {
$currentChar = $query[$i];
// test if operator matches
$operator = $this->getMatchingOperator($query, $i);
// Test if we're at an operator field right now, then add the current token
// without value to the tokenlist
if($operator !== '') {
$tokenList[] = array(
self::FILTER_TARGET => urldecode(substr($query, $currentPos, $i - $currentPos)),
self::FILTER_OPERATOR => $operator
);
// -1 because we're currently pointing at the first character of the operator
$newOffset = $i + strlen($operator) - 1;
return array($newOffset, self::FILTER_VALUE);
}
// Implicit value token (test=1|2)
if (in_array($currentChar, $conjunctions) || $i + 1 == strlen($query)) {
$nrOfSymbols = count($tokenList);
if ($nrOfSymbols <= 2) {
return array($i, self::FILTER_TARGET);
}
$lastState = &$tokenList[$nrOfSymbols-2];
if (is_array($lastState)) {
$tokenList[] = array(
self::FILTER_TARGET => urldecode($lastState[self::FILTER_TARGET]),
self::FILTER_OPERATOR => $lastState[self::FILTER_OPERATOR],
);
return $this->parseValue($query, $currentPos, $tokenList);
}
return array($i, self::FILTER_TARGET);
}
}
return array($i, self::FILTER_TARGET);
}
private function parseValue($query, $currentPos, array &$tokenList)
{
$i = $currentPos;
$conjunctions = array('&', '|');
$nrOfSymbols = count($tokenList);
if ($nrOfSymbols == 0) {
return array($i, self::FILTER_TARGET);
}
$lastState = &$tokenList[$nrOfSymbols-1];
for ($i; $i < strlen($query); $i++) {
$currentChar = $query[$i];
if (in_array($currentChar, $conjunctions)) {
break;
}
}
$length = $i - $currentPos;
// No value given
if ($length === 0) {
array_pop($tokenList);
array_pop($tokenList);
return array($currentPos, self::FILTER_TARGET);
}
$lastState[self::FILTER_VALUE] = urldecode(substr($query, $currentPos, $length));
if (in_array($currentChar, $conjunctions)) {
$tokenList[] = $currentChar;
}
return array($i, self::FILTER_TARGET);
}
private function skip($query, $currentPos, array &$tokenList)
{
$conjunctions = array('&', '|');
for ($i = $currentPos; strlen($query); $i++) {
$currentChar = $query[$i];
if (in_array($currentChar, $conjunctions)) {
return array($i, self::FILTER_TARGET);
}
}
}
}

@ -0,0 +1,142 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Test\Modules\Monitoring\Library\Filter\Type;
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
use Icinga\Filter\Type\TimeRangeSpecifier;
use Icinga\Filter\Query\Node;
use Icinga\Test\BaseTestCase;
// @codingStandardsIgnoreStart
require_once realpath(__DIR__ . '/../../../../../../../library/Icinga/Test/BaseTestCase.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Query/Node.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/QueryProposer.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/FilterType.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/TimeRangeSpecifier.php');
require_once realpath(BaseTestCase::$moduleDir .'/monitoring/library/Monitoring/Filter/Type/StatusFilter.php');
// @codingStandardsIgnoreEnd
class StatusFilterTest extends BaseTestCase
{
public function testOperatorProposal()
{
$searchType = StatusFilter::createForHost();
$this->assertEquals(
$searchType->getOperators(),
$searchType->getProposalsForQuery(''),
'Assert all possible operators to be returned when monitoring status has no further query input'
);
}
public function testStateTypeProposal()
{
$searchType = StatusFilter::createForHost();
$this->assertEquals(
array('{Pen}ding'),
$searchType->getProposalsForQuery('is Pen'),
'Assert StatusFilter to complete partial queries'
);
}
public function testTimeRangeProposal()
{
$subFilter = new TimeRangeSpecifier();
$searchType = StatusFilter::createForHost();
$this->assertEquals(
$subFilter->getOperators(),
$searchType->getProposalsForQuery('is Pending'),
'Assert StatusFilter to chain TimeRangeSpecifier at the end'
);
$this->assertEquals(
$subFilter->timeExamples,
$searchType->getProposalsForQuery('is Pending Since'),
'Assert TimeRange time examples to be proposed'
);
}
public function testQueryNodeCreation()
{
$searchType = StatusFilter::createForHost();
$treeNode = $searchType->createTreeNode('is down', 'host_current_state');
$this->assertEquals(
'host_current_state',
$treeNode->left,
'Assert the left treenode to represent the state field given to the StatusFilter'
);
$this->assertEquals(
1,
$treeNode->right,
'Assert the right treenode to contain the numeric status for "Down"'
);
$this->assertEquals(
Node::TYPE_OPERATOR,
$treeNode->type,
'Assert the treenode to be an operator node'
);
$this->assertEquals(
Node::OPERATOR_EQUALS,
$treeNode->operator,
'Assert the treenode operator to be "Equals"'
);
}
public function testQueryNodeCreationWithTime()
{
$searchType = StatusFilter::createForHost();
$treeNode = $searchType->createTreeNode('is down since yesterday', 'host_current_state');
$this->assertEquals(
Node::TYPE_AND,
$treeNode->type,
'Assert and and node to be returned when an additional time specifier is appended'
);
$this->assertEquals(
Node::TYPE_OPERATOR,
$treeNode->left->type,
'Assert the left node to be the original query (operator)'
);
$this->assertEquals(
'host_current_state',
$treeNode->left->left,
'Assert the left node to be the original query (field)'
);
$this->assertEquals(
Node::TYPE_OPERATOR,
$treeNode->right->type,
'Assert the right node to be the time specifier query (operator)'
);
$this->assertEquals(
'host_last_state_change',
$treeNode->right->left,
'Assert the right node to be the time specifier query (field)'
);
}
}

@ -0,0 +1,179 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Test\Modules\Monitoring\Library\Filter;
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
use Icinga\Filter\Type\TimeRangeSpecifier;
use Icinga\Filter\Query\Node;
use Icinga\Filter\Filter;
use Icinga\Filter\Type\TextFilter;
use Icinga\Filter\FilterAttribute;
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
use Icinga\Protocol\Ldap\Exception;
use Icinga\Test\BaseTestCase;
// @codingStandardsIgnoreStart
require_once realpath(__DIR__ . '/../../../../../../library/Icinga/Test/BaseTestCase.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/QueryProposer.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Filter.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/FilterAttribute.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Domain.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Query/Node.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Query/Tree.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Type/FilterType.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Type/TextFilter.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/TimeRangeSpecifier.php');
require_once realpath(BaseTestCase::$moduleDir .'/monitoring/library/Monitoring/Filter/Type/StatusFilter.php');
require_once realpath(BaseTestCase::$moduleDir .'/monitoring/library/Monitoring/Filter/UrlViewFilter.php');
class UrlViewFilterTest extends BaseTestCase
{
public function testUrlParamCreation()
{
$searchEngine = new Filter();
$searchEngine->createFilterDomain('host')
->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr1')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr2')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr3')
)->registerAttribute(
FilterAttribute::create(StatusFilter::createForHost())
->setHandledAttributes('attr4')
)->registerAttribute(
FilterAttribute::create(StatusFilter::createForHost())
->setHandledAttributes('attr5')
);
$query = 'attr1 is not \'Hans wurst\''
. ' or attr2 contains something '
. ' and attr3 starts with bla'
. ' or attr4 is DOWN since "yesterday"'
. ' and attr5 is UP';
$tree = $searchEngine->createQueryTreeForFilter($query);
$filterFactory = new UrlViewFilter();
$uri = $filterFactory->fromTree($tree);
$this->assertEquals(
'attr1!=Hans+wurst|attr2=%2Asomething%2A&attr3=%2Abla|attr4=1&host_last_state_change>=yesterday&attr5=0',
$uri,
'Assert a correct query to be returned when parsing a more complex query ("'. $query .'")'
);
}
public function testTreeFromSimpleKeyValueUrlCreation()
{
$filterFactory = new UrlViewFilter();
$tree = $filterFactory->parseUrl('attr1!=Hans+Wurst');
$this->assertEquals(
$tree->root->type,
Node::TYPE_OPERATOR,
'Assert one operator node to exist for a simple filter'
);
$this->assertEquals(
$tree->root->operator,
Node::OPERATOR_EQUALS_NOT,
'Assert the operator to be !='
);
$this->assertEquals(
$tree->root->left,
'attr1',
'Assert the field to be set correctly'
);
$this->assertEquals(
$tree->root->right,
'Hans Wurst',
'Assert the value to be set correctly'
);
}
public function testConjunctionFilterInUrl()
{
$filterFactory = new UrlViewFilter();
$query = 'attr1!=Hans+Wurst&test=test123|bla=1';
$tree = $filterFactory->parseUrl($query);
$this->assertEquals($tree->root->type, Node::TYPE_AND, 'Assert the root of the filter tree to be an AND node');
$this->assertEquals($filterFactory->fromTree($tree), $query, 'Assert the tree to map back to the query');
}
public function testImplicitConjunctionInUrl()
{
$filterFactory = new UrlViewFilter();
$query = 'attr1!=Hans+Wurst&test=test123|bla=1|2|3';
$tree = $filterFactory->parseUrl($query);
$this->assertEquals($tree->root->type, Node::TYPE_AND, 'Assert the root of the filter tree to be an AND node');
$this->assertEquals(
'attr1!=Hans+Wurst&test=test123|bla=1|bla=2|bla=3',
$filterFactory->fromTree($tree),
'Assert the tree to map back to the query in an explicit form'
);
}
public function testMissingValuesInQueries()
{
$filterFactory = new UrlViewFilter();
$queryStr = 'attr1!=Hans+Wurst&test=';
$tree = $filterFactory->parseUrl($queryStr);
$query = $filterFactory->fromTree($tree);
$this->assertEquals('attr1!=Hans+Wurst', $query, 'Assert the valid part of a query to be used');
}
public function testErrorInQueries()
{
$filterFactory = new UrlViewFilter();
$queryStr = 'test=&attr1!=Hans+Wurst';
$tree = $filterFactory->parseUrl($queryStr);
$query = $filterFactory->fromTree($tree);
$this->assertEquals('attr1!=Hans+Wurst', $query, 'Assert the valid part of a query to be used');
}
public function testSenselessConjunctions()
{
$filterFactory = new UrlViewFilter();
$queryStr = 'test=&|/5/|&attr1!=Hans+Wurst';
$tree = $filterFactory->parseUrl($queryStr);
$query = $filterFactory->fromTree($tree);
$this->assertEquals('attr1!=Hans+Wurst', $query, 'Assert the valid part of a query to be used');
}
public function testRandomString()
{
$filter = '';
$filterFactory = new UrlViewFilter();
for ($i=0; $i<10;$i++) {
$filter .= str_shuffle('&|ds& wra =!<>|dsgs=,-G');
$tree = $filterFactory->parseUrl($filter);
}
}
}

@ -0,0 +1,136 @@
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
/*global Icinga:false, document: false, define:false require:false base_url:false console:false */
/**
* Ensures that our date/time controls will work on every browser (natively or javascript based)
*/
define(['jquery', 'logging', 'URIjs/URI'], function($, log, URI) {
'use strict';
return function(inputDOM) {
this.inputDom = $(inputDOM);
this.form = this.inputDom.parents('form').first();
this.formUrl = URI(this.form.attr('action'));
this.lastTokens = [];
this.lastQueuedEvent = null;
this.pendingRequest = null;
this.construct = function() {
this.registerControlListener();
};
this.getProposal = function() {
var text = this.inputDom.val().trim();
try {
if (this.pendingRequest) {
this.pendingRequest.abort();
}
this.pendingRequest = $.ajax({
data: {
'cache' : (new Date()).getTime(),
'query' : text
},
headers: {
'Accept': 'application/json'
},
url: this.formUrl
}).done(this.showProposals.bind(this)).fail(function() {});
} catch(exception) {
console.log(exception);
}
};
this.applySelectedProposal = function(token) {
var currentText = $.trim(this.inputDom.val());
var substr = token.match(/^(\{.*\})/);
if (substr !== null) {
token = token.substr(substr[0].length);
} else {
token = ' ' + token;
}
currentText += token;
this.inputDom.val(currentText);
this.inputDom.popover('hide');
this.inputDom.focus();
};
this.showProposals = function(tokens, state, args) {
var jsonRep = args.responseText;
if (tokens.length === 0) {
return this.inputDom.popover('destroy');
}
this.lastTokens = jsonRep;
var list = $('<ul>').addClass('nav nav-stacked nav-pills');
$.each(tokens, (function(idx, token) {
var displayToken = token.replace(/(\{|\})/g, '');
var proposal = $('<li>').
append($('<a href="#">').
text(displayToken)
).appendTo(list);
proposal.on('click', (function(ev) {
ev.preventDefault();
ev.stopPropagation();
this.applySelectedProposal(token);
return false;
}).bind(this));
}).bind(this));
this.inputDom.popover('destroy').popover({
content: list,
placement : 'bottom',
html: true,
trigger: 'manual'
}).popover('show');
};
this.registerControlListener = function() {
this.inputDom.on('blur', (function() {
$(this).popover('hide');
}));
this.inputDom.on('focus', updateProposalList.bind(this));
this.inputDom.on('keyup', updateProposalList.bind(this));
};
var updateProposalList = function() {
if (this.lastQueuedEvent) {
window.clearTimeout(this.lastQueuedEvent);
}
this.lastQueuedEvent = window.setTimeout(this.getProposal.bind(this), 200);
};
this.construct();
};
});

@ -0,0 +1,102 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Filter;
use Icinga\Filter\FilterAttribute;
use Icinga\Filter\Query\Node;
use Icinga\Filter\Type\TextFilter;
use Icinga\Test\BaseTestCase;
use Icinga\Filter\Domain;
// @codingStandardsIgnoreStart
require_once realpath(__DIR__ . '/../../../../../library/Icinga/Test/BaseTestCase.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/QueryProposer.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/FilterAttribute.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Domain.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Query/Node.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Type/FilterType.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Type/TextFilter.php');
// @codingStandardsIgnoreEnd
class DomainTest extends BaseTestCase
{
public function testDomainRecognitionInQueryString()
{
$domain = new Domain('host');
$queryWithWhitespace = ' host is up';
$camelCaseQuery = 'HOsT is down';
$invalidQuery = 'Horst host Host';
$this->assertTrue($domain->handlesQuery($queryWithWhitespace), 'Assert the domain to ignore starting whitespaces');
$this->assertTrue($domain->handlesQuery($camelCaseQuery), 'Assert the domain to be case insensitive');
$this->assertFalse($domain->handlesQuery($invalidQuery), 'Assert wrong domains to be recognized');
}
public function testQueryProposal()
{
$domain = new Domain('host');
$attr = new TextFilter();
$queryHandler = new FilterAttribute($attr);
$domain->registerAttribute($queryHandler->setHandledAttributes('name', 'description'));
$this->assertEquals(
array('name'),
$domain->getProposalsForQuery(''),
'Assert the name being returned when empty query is provided to domain'
);
$this->assertEquals(
array('\'value\'', '{Is} Not'),
$domain->getProposalsForQuery('host name is'),
'Assert mixed operator extension and value proposal being returned when provided a partial query'
);
$this->assertEquals(
array('\'value\''),
$domain->getProposalsForQuery('name is not'),
'Assert only the value to be returned when operator is fully given'
);
$this->assertEquals(
array(),
$domain->getProposalsForQuery('sagsdgsdgdgds')
);
}
public function testGetQueryTree()
{
$domain = new Domain('host');
$attr = new TextFilter();
$queryHandler = new FilterAttribute($attr);
$domain->registerAttribute($queryHandler->setField('host_name')->setHandledAttributes('name', 'description'));
$node = $domain->convertToTreeNode('Host name is \'my host\'');
$this->assertEquals($node->type, Node::TYPE_OPERATOR, 'Assert a domain to produce operator query nodes');
$this->assertEquals($node->left, 'host_name', 'Assert a domain to insert the field as the left side of a treenode');
$this->assertEquals($node->right, 'my host', 'Assert a domain to insert the value as the right side of a treenode');
$this->assertEquals($node->operator, Node::OPERATOR_EQUALS, 'Assert the correct operator to be set in a single query');
}
}

@ -0,0 +1,297 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Filter;
use Icinga\Test\BaseTestCase;
use Icinga\Filter\FilterAttribute;
use Icinga\Filter\Filter;
use Icinga\Filter\Type\TextFilter;
use Icinga\Filter\Query\Node;
// @codingStandardsIgnoreStart
require_once realpath(__DIR__ . '/../../../../../library/Icinga/Test/BaseTestCase.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/QueryProposer.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Filter.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/FilterAttribute.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Domain.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Query/Node.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Query/Tree.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Type/FilterType.php');
require_once realpath(BaseTestCase::$libDir . '/Filter/Type/TextFilter.php');
// @codingStandardsIgnoreEnd
class FilterTest extends BaseTestCase
{
public function testFilterProposals()
{
$searchEngine = new Filter();
$searchEngine->createFilterDomain('host')
->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('name')
);
$this->assertEquals(
array(),
$searchEngine->getProposalsForQuery('Host name Is something'),
'Assert empty array being returned if no proposal is sensible'
);
$this->assertEquals(
array('{Starts} With'),
$searchEngine->getProposalsForQuery('Host Name Starts'),
'Assert operator proposal to occur when entering an attribute'
);
$this->assertEquals(
array('\'...value...\''),
$searchEngine->getProposalsForQuery('Host name Is test and Hostname contains'),
'Assert only proposals for the last query part being made'
);
}
public function testSingleQueryTreeCreation()
{
$searchEngine = new Filter();
$searchEngine->createFilterDomain('host')
->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('name')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('test')
);
$tree = $searchEngine->createQueryTreeForFilter('Host name is not \'Hans wurst\'');
$this->assertEquals(
$tree->root->type,
Node::TYPE_OPERATOR,
'Assert a single operator node as the query tree\'s root on a simple query'
);
}
public function testSingleAndQueryTreeCreation()
{
$searchEngine = new Filter();
$searchEngine->createFilterDomain('host')
->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('name')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('test')
);
$tree = $searchEngine->createQueryTreeForFilter(
'Host name is not \'Hans wurst\' and Host test contains something'
);
$this->assertEquals(
$tree->root->type,
Node::TYPE_AND,
'Assert an AND node as the query tree\'s root on a simple "and" query'
);
$this->assertEquals(array(), $searchEngine->getIgnoredQueryParts(), 'Assert no errors occuring');
$this->assertEquals(
$tree->root->left->type, Node::TYPE_OPERATOR, 'Assert a left operator below the root on a single "and" query'
);
$this->assertEquals(
$tree->root->left->left, 'name', 'Assert "name" underneath as the leftmost node on an "and" query'
);
$this->assertEquals(
$tree->root->right->type, Node::TYPE_OPERATOR, 'Assert a left operator below the root on a single "and" query'
);
$this->assertEquals(
$tree->root->right->left, 'test', 'Assert "test" underneath as the leftmost node on an "and" query'
);
}
public function testSingleOrQueryTreeCreation()
{
$searchEngine = new Filter();
$searchEngine->createFilterDomain('host')
->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('name')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('test')
);
$tree = $searchEngine->createQueryTreeForFilter(
'Host name is not \'Hans wurst\' or Host test contains something'
);
$this->assertEquals(
Node::TYPE_OR,
$tree->root->type,
'Assert an OR node as the query tree\'s root on a simple "or" query'
);
$this->assertEquals(array(), $searchEngine->getIgnoredQueryParts(), 'Assert no errors occuring');
$this->assertEquals(
$tree->root->left->type, Node::TYPE_OPERATOR, 'Assert a left operator below the root on a single "or" query'
);
$this->assertEquals(
$tree->root->left->left, 'name', 'Assert "name" underneath as the leftmost node on an "or" query'
);
$this->assertEquals(
$tree->root->right->type, Node::TYPE_OPERATOR, 'Assert a left operator below the root on a single "or" query'
);
$this->assertEquals(
$tree->root->right->left, 'test', 'Assert "test" underneath as the leftmost node on an "or" query'
);
}
public function testMultipleOrQueries()
{
$searchEngine = new Filter();
$searchEngine->createFilterDomain('host')
->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr1')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr2')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr3')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr4')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr5')
);
$query = 'attr1 is not "test" or attr2 is not "test2" or attr3 is 0';
$tree = $searchEngine->createQueryTreeForFilter($query);
$this->assertEquals(
$tree->root->type,
Node::TYPE_OR,
'Assert the root node to be or on a multi-or query'
);
$this->assertEquals(
$tree->root->left->type,
Node::TYPE_OPERATOR,
'Assert the left node to be an operator on a multi-or query'
);
$this->assertEquals(
$tree->root->right->type,
Node::TYPE_OR,
'Assert the right node to be an operator on a multi-or query'
);
$this->assertEquals(
$tree->root->right->right->type,
Node::TYPE_OPERATOR,
'Assert the right node to be an operator on a multi-or query'
);
$this->assertEquals(
$tree->root->right->left->type,
Node::TYPE_OPERATOR,
'Assert the right node to be an operator on a multi-or query'
);
}
public function testComplexQueryTreeCreation()
{
$searchEngine = new Filter();
$searchEngine->createFilterDomain('host')
->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr1')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr2')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr3')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr4')
)->registerAttribute(
FilterAttribute::create(new TextFilter())
->setHandledAttributes('attr5')
);
$query = 'attr1 is not \'Hans wurst\''
. ' or attr2 contains something '
. ' and attr3 starts with bla'
. ' or attr4 contains \'more\''
. ' and attr5 is test2';
$tree = $searchEngine->createQueryTreeForFilter($query);
$this->assertEquals(
$tree->root->type,
Node::TYPE_AND,
'Assert the root node to be an AND (query :"' . $query . '")'
);
$this->assertEquals(
$tree->root->left->type,
Node::TYPE_OR,
'Assert the root->left node to be an OR (query :"' . $query . '")'
);
$this->assertEquals(
$tree->root->left->left->type,
Node::TYPE_OPERATOR,
'Assert the root->left->left node to be an Operator (query :"' . $query . '")'
);
$this->assertEquals(
$tree->root->left->right->type,
Node::TYPE_OPERATOR,
'Assert the root->left->left node to be an Operator (query :"' . $query . '")'
);
$this->assertEquals(
$tree->root->right->type,
Node::TYPE_AND,
'Assert the root->right node to be an AND (query :"' . $query . '")'
);
$this->assertEquals(
$tree->root->right->left->type,
Node::TYPE_OR,
'Assert the root->right->left node to be an OR (query :"' . $query . '")'
);
$this->assertEquals(
$tree->root->right->left->left->type,
Node::TYPE_OPERATOR,
'Assert the root->right->left->left node to be an OPERATOR (query :"' . $query . '")'
);
$this->assertEquals(
$tree->root->right->left->right->type,
Node::TYPE_OPERATOR,
'Assert the root->right->left->right node to be an OPERATOR (query :"' . $query . '")'
);
$this->assertEquals(
$tree->root->right->right->type,
Node::TYPE_OPERATOR,
'Assert the root->right->right->type node to be an OPERATOR (query :"' . $query . '")'
);
}
}

@ -0,0 +1,141 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Filter;
use Icinga\Filter\Query\Node;
use Icinga\Filter\FilterAttribute;
use Icinga\Filter\Type\FilterType;
use Icinga\Test\BaseTestCase;
// @codingStandardsIgnoreStart
require_once realpath(__DIR__ . '/../../../../../library/Icinga/Test/BaseTestCase.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Query/Node.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/QueryProposer.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Domain.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/FilterAttribute.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/FilterType.php');
class TypeMock extends FilterType
{
public function isValidQuery($query)
{
return true;
}
public function createTreeNode($query, $leftOperand)
{
$node = new Node();
$node->left = $leftOperand;
return $node;
}
public function getProposalsForQuery($query)
{
return $this->getOperators();
}
public function getOperators()
{
return array('op1', 'is better than', 'is worse than');
}
}
class QueryHandlerTest extends BaseTestCase
{
public function testQueryHandlerSetup()
{
$handler = new FilterAttribute(new TypeMock());
$handler->setField('current_status');
$handler->setHandledAttributes('State', 'Status', 'Current State');
$this->assertTrue(
$handler->queryHasSupportedAttribute('state is down'),
'Assert attributes to be correctly recognized'
);
$this->assertTrue(
$handler->queryHasSupportedAttribute('current state is down'),
'Assert more than one attribute to be possible, also with whitespaces'
);
$this->assertFalse(
$handler->queryHasSupportedAttribute('bla status has blah'),
'Assert invalid attributes to be returned as not supported'
);
}
public function testQueryProposal()
{
$handler = new FilterAttribute(new TypeMock());
$handler->setField('current_status');
$handler->setHandledAttributes('Status', 'State', 'Current State');
$this->assertEquals(
array('Status'),
$handler->getProposalsForQuery(''),
'Assert the queryHandler to propose the first attribute if empty string is given'
);
$this->assertEquals(
array('{Current} State'),
$handler->getProposalsForQuery('current'),
'Assert the queryHandler to propose sensible attributes if a partial string is given'
);
$this->assertEquals(
array(),
$handler->getProposalsForQuery('abc'),
'Assert the queryHandler to return null if no propsal can be made'
);
}
public function testOperatorProposal()
{
$handler = new FilterAttribute(new TypeMock());
$handler->setField('current_status')
->setHandledAttributes('status', 'state', 'current state');
$this->assertEquals(
array('op1', 'is better than', 'is worse than'),
$handler->getProposalsForQuery('current state'),
'Assert all operators being proposed when having a distinguishable attribute'
);
}
public function testAttributeRecognition()
{
$handler = new FilterAttribute(new TypeMock());
$handler->setField('current_status')
->setHandledAttributes('status', 'state', 'current state');
$node = $handler->convertToTreeNode('status is not \some kind of magic\'');
$this->assertEquals($node->left, 'current_status', 'Assert status to be set to the field');
}
}

@ -0,0 +1,153 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Filter;
use Icinga\Filter\Type\BooleanFilter;
use Icinga\Filter\Type\TimeRangeSpecifier;
use Icinga\Filter\Query\Node;
use Icinga\Test\BaseTestCase;
// @codingStandardsIgnoreStart
require_once realpath(__DIR__ . '/../../../../../../library/Icinga/Test/BaseTestCase.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Query/Node.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/QueryProposer.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/FilterType.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/BooleanFilter.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/TimeRangeSpecifier.php');
// @codingStandardsIgnoreEnd
class BooleanFilterTest extends BaseTestCase
{
public function testOperatorProposal()
{
$filter = new BooleanFilter(array());
$this->assertEquals(
$filter->getOperators(),
$filter->getProposalsForQuery(''),
'Assert all operators to be returned for an empty query'
);
}
public function testFieldProposal()
{
$filter = new BooleanFilter(array(
'host_problem' => 'With Problem',
'host_is_flapping' => 'Flapping',
));
$this->assertEquals(
array('With Problem', 'Flapping', '{Is} Not'),
$filter->getProposalsForQuery('is'),
'Assert fields to be proposed when an operator is given in boolean fields'
);
$this->assertEquals(
array('{With} Problem'),
$filter->getProposalsForQuery('is with'),
'Assert partial fields being recognized in boolean filter queries'
);
}
public function testKeyProposal()
{
$filter = new BooleanFilter(array(
'host_problem' => 'With Problem',
'host_is_flapping' => 'Flapping',
));
$this->assertEquals(
array('{host_pr}oblem'),
$filter->getProposalsForQuery('is host_pr'),
'Assert keys being used when they match instead of the values'
);
}
public function testTimeRangeProposal()
{
$filter = new BooleanFilter(array(
'host_problem' => 'With Problem',
'host_is_flapping' => 'Flapping',
), 'time_field');
$this->assertEquals(
array('Since', 'Before'),
$filter->getProposalsForQuery('is with problem'),
'Assert timerange proposals being made if "noTime" is not set on creation'
);
}
public function testQueryValidation()
{
$filter = new BooleanFilter(array(
'host_problem' => 'With Problem',
'host_is_flapping' => 'Flapping',
));
$this->assertTrue($filter->isValidQuery('is with problem'), 'Assert valid queries to be recognized');
$this->assertFalse($filter->isValidQuery('is problem'), 'Assert invalid queries to be recognized');
}
public function testQueryNodeCreation()
{
$filter = new BooleanFilter(array(
'host_problem' => 'With Problem',
'host_is_flapping' => 'Flapping'
));
$node = $filter->createTreeNode('is with problem', 'host_status');
$this->assertEquals('host_problem', $node->left, 'Assert the left part of the node to be host_problem');
$this->assertEquals(Node::OPERATOR_EQUALS, $node->operator, 'Assert the operator to be equals');
$this->assertEquals(1, $node->right, 'Assert the value to be 1');
$node = $filter->createTreeNode('is not with problem', 'host_status');
$this->assertEquals('host_problem', $node->left, 'Assert the left part of the node to be host_problem');
$this->assertEquals(Node::OPERATOR_EQUALS, $node->operator, 'Assert the operator to be equals');
$this->assertEquals(0, $node->right, 'Assert the value to be 0 for not equals');
}
public function testTimeQueryNodeCreation()
{
$filter = new BooleanFilter(array(
'host_problem' => 'With Problem',
'host_is_flapping' => 'Flapping'
), 'time_node');
$node = $filter->createTreeNode('is with problem since 1 hour', 'host_status');
$this->assertEquals(Node::TYPE_AND, $node->type, 'Assert the node to be an AND node');
$this->assertEquals('time_node', $node->left->left, 'Assert the left part of the node to be time filter');
$this->assertEquals(Node::OPERATOR_GREATER_EQ, $node->left->operator, 'Assert the operator to be greater eq');
$this->assertEquals('-1 hour', $node->left->right, 'Assert the value to be the strotime info');
$this->assertEquals('host_problem', $node->right->left, 'Assert the right part of the node to be host_problem');
$this->assertEquals(Node::OPERATOR_EQUALS, $node->right->operator, 'Assert the operator to be equals');
$this->assertEquals(1, $node->right->right, 'Assert the value to be 1');
}
}

@ -0,0 +1,76 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Filter;
use Icinga\Filter\Type\TextFilter;
use Icinga\Filter\Query\Node;
use Icinga\Test\BaseTestCase;
// @codingStandardsIgnoreStart
require_once realpath(__DIR__ . '/../../../../../../library/Icinga/Test/BaseTestCase.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Query/Node.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/QueryProposer.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/FilterType.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/TextFilter.php');
// @codingStandardsIgnoreEnd
class TextFilterTest extends BaseTestCase
{
public function testOperatorProposal()
{
$textFilter = new TextFilter();
$this->assertEquals(
$textFilter->getOperators(),
$textFilter->getProposalsForQuery(''),
'Assert all operators being proposed when having an empty operator substring'
);
$this->assertEquals(
array('{Con}tains'),
$textFilter->getProposalsForQuery('con'),
'Assert one operator being proposed when having a distinguishable operator substring'
);
$this->assertEquals(
array('\'value\'', '{Is} Not'),
$textFilter->getProposalsForQuery('is'),
'Assert all operators being proposed when having an ambiguous operator substring'
);
}
public function testGetOperatorAndValueFromQuery()
{
$textFilter = new TextFilter();
list($operator, $value) = $textFilter->getOperatorAndValueFromQuery('is not \'something\'');
$this->assertEquals(Node::OPERATOR_EQUALS_NOT, $operator, 'Asserting text operators to be split via TextFilter');
$this->assertEquals('something', $value, 'Asserting quoted values to be recognized in TextFilter');
}
}

@ -0,0 +1,68 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Filter;
use Icinga\Filter\Type\TimeRangeSpecifier;
use Icinga\Filter\Query\Node;
use Icinga\Test\BaseTestCase;
// @codingStandardsIgnoreStart
require_once realpath(__DIR__ . '/../../../../../../library/Icinga/Test/BaseTestCase.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Query/Node.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/QueryProposer.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/FilterType.php');
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/TimeRangeSpecifier.php');
// @codingStandardsIgnoreEnd
class TimeRangeSpecifierTest extends BaseTestCase
{
public function testIsValid()
{
$tRange = new TimeRangeSpecifier();
$this->assertTrue(
$tRange->isValidQuery('since yesterday'),
'Assert "since yesterday" being a valid time range'
);
$this->assertTrue(
$tRange->isValidQuery('since 2 days'),
'Assert "since 2 days" being a valid time range'
);
$this->assertTrue(
$tRange->isValidQuery('before tomorrow'),
'Assert "before tomorrow" being a valid time range'
);
$this->assertTrue(
$tRange->isValidQuery('since "2 hours"'),
'Assert quotes being recognized'
);
}
}