Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: Add Node-Type Filter #398

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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