Skip to content

Commit

Permalink
FEATURE: Add Node-Type Filter
Browse files Browse the repository at this point in the history
- Creates a method which allows to create a nodetype filter to
   include and exclude specific nodetypes
- Adds a test for the new filter function
- Adjusts existing tests to match the new circumstances
  • Loading branch information
erkenes committed Feb 17, 2023
1 parent 2a7de69 commit 2b66844
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 21 deletions.
50 changes: 50 additions & 0 deletions Classes/Eel/ElasticSearchQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,56 @@ public function nodeType(string $nodeType): QueryBuilderInterface
return $this->queryFilter('term', ['neos_type_and_supertypes' => $nodeType]);
}

/**
* Filter multiple node types
*
* @param array $expectedNodeTypes NodeTypes that should be expected
* @param array $excludedNodeTypes NodeTypes that should be excluded
* @return ElasticSearchQueryBuilder
* @throws QueryBuildingException
* @api
*/
public function nodeTypeFilter(array $expectedNodeTypes, array $excludedNodeTypes = []): QueryBuilderInterface
{
$excludeTerms = [];
foreach ($excludedNodeTypes as $nodeType) {
$excludeTerms[] = [
'term' => [
'neos_type_and_supertypes' => $nodeType
]
];
}
if (!empty($excludeTerms)) {
$this->request->queryFilter(
'bool',
[
'should' => $excludeTerms
],
'must_not'
);
}

$includeTerms = [];
foreach ($expectedNodeTypes as $nodeType) {
$includeTerms[] = [
'term' => [
'neos_type_and_supertypes' => $nodeType
]
];
}
if (!empty($includeTerms)) {
$this->request->queryFilter(
'bool',
[
'should' => $includeTerms
],
'must'
);
}

return $this;
}

/**
* Sort descending by $propertyName
*
Expand Down
4 changes: 4 additions & 0 deletions Configuration/Testing/NodeTypes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
main:
type: 'Neos.Neos:ContentCollection'

'Flowpack.ElasticSearch.ContentRepositoryAdaptor:Document2':
superTypes:
'Flowpack.ElasticSearch.ContentRepositoryAdaptor:Document': true

'Flowpack.ElasticSearch.ContentRepositoryAdaptor:Content':
superTypes:
'Neos.Neos:Content': true
Expand Down
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,23 +427,24 @@ Furthermore, the following operators are supported:

As **value**, the following methods accept a simple type, a node object or a DateTime object.

| Query Operator | Description |
|----------------|-------------|
|`nodeType('Your.Node:Type')` |Filters on the given NodeType|
|`exactMatch('propertyName', value)` |Supports simple types: `exactMatch('tag', 'foo')`, or node references: `exactMatch('author', authorNode)`|
|`exclude('propertyName', value)` |Excludes results by property - the negation of exactMatch.
|`greaterThan('propertyName', value, [clauseType])` |Range filter with property values greater than the given value|
|`greaterThanOrEqual('propertyName', value, [clauseType])`|Range filter with property values greater than or equal to the given value|
|`lessThan('propertyName', value, [clauseType])` |Range filter with property values less than the given value|
|`lessThanOrEqual('propertyName', value, [clauseType])`|Range filter with property values less than or equal to the given value|
|`sortAsc('propertyName')` / `sortDesc('propertyName')`|Can also be used multiple times, e.g. `sortAsc('tag').sortDesc('date')` will first sort by tag ascending, and then by date descending.|
|`limit(5)` |Only return five results. If not specified, the default limit by Elasticsearch applies (which is at 10 by default)|
|`from(5)` |Return the results starting from the 6th one|
|`prefix('propertyName', 'prefix', [clauseType])` |Adds a prefix filter on the given field with the given prefix|
|`geoDistance(propertyName, geoPoint, distance, [clauseType])`. |Filters documents that include only hits that exists within a specific distance from a geo point.|
|`fulltext('searchWord', options)` |Does a query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-query-string-query.html) to the query_string. Recommendation: **use simpleQueryStringFulltext instead, as it yields better results and is more tolerant to user input**.|
|`simpleQueryStringFulltext('searchWord', options)` |Does a simple_query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/8.3/query-dsl-simple-query-string-query.html) to the simple_query_string. Supports phrase matching like `"firstname lastname"` and tolerates broken input without exceptions (in contrast to `fulltext()`)|
|`highlight(fragmentSize, fragmentCount, noMatchSize, field)` |Configure result highlighting for every fulltext field individually|
| Query Operator | Description |
|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `nodeType('Your.Node:Type')` | Filters on the given NodeType |
| `nodeTypeFilter(['Your.Node:Type'],['Your.ExcludedNode:Type'])` | Filters multiple NodeTypes |
| `exactMatch('propertyName', value)` | Supports simple types: `exactMatch('tag', 'foo')`, or node references: `exactMatch('author', authorNode)` |
| `exclude('propertyName', value)` | Excludes results by property - the negation of exactMatch. |
| `greaterThan('propertyName', value, [clauseType])` | Range filter with property values greater than the given value |
| `greaterThanOrEqual('propertyName', value, [clauseType])` | Range filter with property values greater than or equal to the given value |
| `lessThan('propertyName', value, [clauseType])` | Range filter with property values less than the given value |
| `lessThanOrEqual('propertyName', value, [clauseType])` | Range filter with property values less than or equal to the given value |
| `sortAsc('propertyName')` / `sortDesc('propertyName')` | Can also be used multiple times, e.g. `sortAsc('tag').sortDesc('date')` will first sort by tag ascending, and then by date descending. |
| `limit(5)` | Only return five results. If not specified, the default limit by Elasticsearch applies (which is at 10 by default) |
| `from(5)` | Return the results starting from the 6th one |
| `prefix('propertyName', 'prefix', [clauseType])` | Adds a prefix filter on the given field with the given prefix |
| `geoDistance(propertyName, geoPoint, distance, [clauseType])`. | Filters documents that include only hits that exists within a specific distance from a geo point. |
| `fulltext('searchWord', options)` | Does a query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-query-string-query.html) to the query_string. Recommendation: **use simpleQueryStringFulltext instead, as it yields better results and is more tolerant to user input**. |
| `simpleQueryStringFulltext('searchWord', options)` | Does a simple_query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/8.3/query-dsl-simple-query-string-query.html) to the simple_query_string. Supports phrase matching like `"firstname lastname"` and tolerates broken input without exceptions (in contrast to `fulltext()`) |
| `highlight(fragmentSize, fragmentCount, noMatchSize, field)` | Configure result highlighting for every fulltext field individually |

## Search Result Highlighting

Expand Down
20 changes: 16 additions & 4 deletions Tests/Functional/Eel/ElasticSearchQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ public function filterByNodeType(): void
->log($this->getLogMessagePrefix(__METHOD__))
->nodeType('Flowpack.ElasticSearch.ContentRepositoryAdaptor:Document')
->count();
static::assertEquals(6, $resultCount);
}

/**
* @test
*/
public function filterByNodeTypes(): void
{
$resultCount = $this->getQueryBuilder()
->log($this->getLogMessagePrefix(__METHOD__))
->nodeTypeFilter(['Flowpack.ElasticSearch.ContentRepositoryAdaptor:Document'], ['Flowpack.ElasticSearch.ContentRepositoryAdaptor:Document2'])
->count();
static::assertEquals(4, $resultCount);
}

Expand Down Expand Up @@ -142,7 +154,7 @@ public function limitDoesNotImpactCount(): void
->limit(1);

$resultCount = $query->count();
static::assertEquals(4, $resultCount, 'Asserting the count query returns the total count.');
static::assertEquals(6, $resultCount, 'Asserting the count query returns the total count.');
}

/**
Expand Down Expand Up @@ -174,7 +186,7 @@ public function fieldBasedAggregations(): void
->getAggregations();

static::assertArrayHasKey($aggregationTitle, $result);
static::assertCount(3, $result[$aggregationTitle]['buckets']);
static::assertCount(5, $result[$aggregationTitle]['buckets']);

$expectedChickenBucket = [
'key' => 'chicken',
Expand Down Expand Up @@ -247,8 +259,8 @@ public function nodesWillBeSortedDesc(): void
/** @var QueryResultInterface $result $node */

static::assertInstanceOf(QueryResultInterface::class, $result);
static::assertCount(4, $result, 'The result should have 3 items');
static::assertEquals(4, $result->count(), 'Count should be 3');
static::assertCount(6, $result, 'The result should have 6 items');
static::assertEquals(6, $result->count(), 'Count should be 6');

$node = $result->getFirst();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ protected function createNodesForNodeSearchTest(): void
$newDocumentNode3->setProperty('title', 'egg');
$newDocumentNode3->setProperty('title_analyzed', 'egg');

$newDocumentNode4 = $this->siteNode->createNode('test-node-4', $this->nodeTypeManager->getNodeType('Flowpack.ElasticSearch.ContentRepositoryAdaptor:Document2'));
$newDocumentNode4->setProperty('title', 'tiger');
$newDocumentNode4->setProperty('title_analyzed', 'tiger');

$newDocumentNode5 = $this->siteNode->createNode('test-node-5', $this->nodeTypeManager->getNodeType('Flowpack.ElasticSearch.ContentRepositoryAdaptor:Document2'));
$newDocumentNode5->setProperty('title', 'elephant');
$newDocumentNode5->setProperty('title_analyzed', 'elephant');

$dimensionContext = $this->contextFactory->create([
'workspaceName' => 'live',
'dimensions' => ['language' => ['de']]
Expand Down

0 comments on commit 2b66844

Please sign in to comment.