Skip to content

Commit 62e1a2c

Browse files
committed
TASK: Refactor SuggestController to build query without building nodes
Caches the request JSON Template by contxt node.
1 parent 7f1fc9a commit 62e1a2c

4 files changed

Lines changed: 115 additions & 51 deletions

File tree

Classes/Controller/SuggestController.php

Lines changed: 103 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,36 @@
1313

1414
use Flowpack\ElasticSearch\ContentRepositoryAdaptor\Eel\ElasticSearchQueryBuilder;
1515
use Flowpack\ElasticSearch\ContentRepositoryAdaptor\Eel\ElasticSearchQueryResult;
16+
use Flowpack\ElasticSearch\ContentRepositoryAdaptor\ElasticSearchClient;
17+
use Neos\Cache\Frontend\VariableFrontend;
1618
use Neos\ContentRepository\Domain\Model\NodeInterface;
1719
use Neos\Flow\Annotations as Flow;
1820
use Neos\Flow\Mvc\Controller\ActionController;
1921
use Neos\Flow\Mvc\View\JsonView;
22+
use Neos\Neos\Controller\CreateContentContextTrait;
2023

21-
/**
22-
* Class SuggestController
23-
*/
2424
class SuggestController extends ActionController
2525
{
26+
use CreateContentContextTrait;
27+
28+
/**
29+
* @Flow\Inject
30+
* @var ElasticSearchClient
31+
*/
32+
protected $elasticSearchClient;
33+
2634
/**
2735
* @Flow\Inject
2836
* @var ElasticSearchQueryBuilder
2937
*/
3038
protected $elasticSearchQueryBuilder;
3139

40+
/**
41+
* @Flow\Inject
42+
* @var VariableFrontend
43+
*/
44+
protected $elasticSearchQueryTemplateCache;
45+
3246
/**
3347
* @var array
3448
*/
@@ -37,11 +51,11 @@ class SuggestController extends ActionController
3751
];
3852

3953
/**
40-
* @param NodeInterface $contextNode
54+
* @param string $contextNodeIdentifier
4155
* @param string $term
4256
* @return void
4357
*/
44-
public function indexAction(NodeInterface $contextNode, $term)
58+
public function indexAction($contextNodeIdentifier, $term)
4559
{
4660
$result = [
4761
'completions' => [],
@@ -54,64 +68,103 @@ public function indexAction(NodeInterface $contextNode, $term)
5468
return;
5569
}
5670

71+
$requestJson = $this->buildRequestForTerm($term, $contextNodeIdentifier);
72+
73+
try {
74+
$response = $this->elasticSearchClient->getIndex()->request('POST', '/_search', [], $requestJson)->getTreatedContent();
75+
$result['completions'] = $this->extractCompletions($response);
76+
$result['suggestions'] = $this->extractSuggestions($response);
77+
} catch (\Exception $e) {
78+
$result['errors'] = ['Could not execute query'];
79+
}
80+
81+
$this->view->assign('value', $result);
82+
}
83+
84+
/**
85+
* @param string $term
86+
* @param string $contextNodeIdentifier
87+
* @return ElasticSearchQueryBuilder
88+
*/
89+
protected function buildRequestForTerm($term, $contextNodeIdentifier)
90+
{
91+
$termPlaceholder = '---term-soh2gufuNi---';
5792
$term = strtolower($term);
5893

59-
// TODO: cache query by node identifier
60-
61-
/** @var ElasticSearchQueryBuilder $query */
62-
$query = $this->elasticSearchQueryBuilder->query($contextNode);
63-
$query
64-
->queryFilter('prefix', [
65-
'__completion' => $term
66-
])
67-
->limit(0)
68-
->aggregation('autocomplete', [
69-
'terms' => [
70-
'field' => '__completion',
71-
'order' => [
72-
'_count' => 'desc'
73-
],
74-
'include' => [
75-
'pattern' => $term . '.*'
94+
if(!$this->elasticSearchQueryTemplateCache->has($contextNodeIdentifier)) {
95+
96+
$contentContext = $this->createContentContext('live', []);
97+
$contextNode = $contentContext->getNodeByIdentifier($contextNodeIdentifier);
98+
99+
/** @var ElasticSearchQueryBuilder $query */
100+
$query = $this->elasticSearchQueryBuilder->query($contextNode);
101+
$query
102+
->queryFilter('prefix', [
103+
'__completion' => $termPlaceholder
104+
])
105+
->limit(1)
106+
->aggregation('autocomplete', [
107+
'terms' => [
108+
'field' => '__completion',
109+
'order' => [
110+
'_count' => 'desc'
111+
],
112+
'include' => [
113+
'pattern' => $termPlaceholder . '.*'
114+
]
76115
]
77-
]
78-
])
79-
->suggestions('suggestions', [
80-
'text' => $term,
81-
'completion' => [
82-
'field' => '__suggestions',
83-
'fuzzy' => true,
84-
'context' => [
85-
'parentPath' => $contextNode->getPath(),
86-
'workspace' => 'live',
87-
'dimensionCombinationHash' => md5(json_encode($contextNode->getContext()->getDimensions())),
116+
])
117+
->suggestions('suggestions', [
118+
'text' => $termPlaceholder,
119+
'completion' => [
120+
'field' => '__suggestions',
121+
'fuzzy' => true,
122+
'context' => [
123+
'parentPath' => $contextNode->getPath(),
124+
'workspace' => 'live',
125+
'dimensionCombinationHash' => md5(json_encode($contextNode->getContext()->getDimensions())),
126+
]
88127
]
89-
]
90-
]);
128+
]);
91129

92-
try {
93-
/** @var ElasticSearchQueryResult $queryResult */
94-
$queryResult = $query->execute();
95-
} catch (\Exception $e) {
96-
$result['errors'] = ['Could not execute query'];
97-
$this->view->assign('value', $result);
98-
return;
130+
$requestTemplate = $query->getRequest()->getRequestAsJson();
131+
132+
$this->elasticSearchQueryTemplateCache->set($contextNodeIdentifier, $requestTemplate);
133+
} else {
134+
$requestTemplate = $this->elasticSearchQueryTemplateCache->get($contextNodeIdentifier);
99135
}
100136

101-
$aggregations = $queryResult->getAggregations();
137+
return str_replace($termPlaceholder, $term, $requestTemplate);
138+
}
139+
140+
/**
141+
* Extract autocomplete options
142+
*
143+
* @param $response
144+
* @return array
145+
*/
146+
protected function extractCompletions($response)
147+
{
148+
$aggregations = isset($response['aggregations']) ? $response['aggregations'] : [];
102149

103-
// Extract autocomplete options
104-
$autoCompletionOptions = array_map(function ($option) {
150+
return array_map(function ($option) {
105151
return $option['key'];
106152
}, $aggregations['autocomplete']['buckets']);
107-
$result['completions'] = $autoCompletionOptions;
153+
}
108154

109-
// Extract suggestion options
110-
$suggestionOptions = $queryResult->getSuggestions();
155+
/**
156+
* Extract suggestion options
157+
*
158+
* @param $response
159+
* @return array
160+
*/
161+
protected function extractSuggestions($response)
162+
{
163+
$suggestionOptions = isset($response['suggest']) ? $response['suggest'] : [];
111164
if (count($suggestionOptions['suggestions'][0]['options']) > 0) {
112-
$result['suggestions'] = $suggestionOptions['suggestions'][0]['options'];
165+
return $suggestionOptions['suggestions'][0]['options'];
113166
}
114167

115-
$this->view->assign('value', $result);
168+
return [];
116169
}
117170
}

Configuration/Caches.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FlowPackSearchPlugin_ElasticSearchQueryTemplateCache:
2+
frontend: Neos\Cache\Frontend\VariableFrontend

Configuration/Objects.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Flowpack\SearchPlugin\Controller\SuggestController:
2+
properties:
3+
elasticSearchQueryTemplateCache:
4+
object:
5+
factoryObjectName: Neos\Flow\Cache\CacheManager
6+
factoryMethodName: getCache
7+
arguments:
8+
1:
9+
value: FlowPackSearchPlugin_ElasticSearchQueryTemplateCache
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<form method="GET">
2-
<input type="search" name="search" value="{searchWord}" placeholder="{f:translate(id: 'search', package: 'Flowpack.SearchPlugin')}" data-autocomplete-source="{f:uri.action(action: 'index', controller: 'Suggest', package: 'Flowpack.SearchPlugin', format: 'json', absolute: 1, arguments: {contextNode: node})}"/>
2+
<input type="search" name="search" value="{searchWord}" placeholder="{f:translate(id: 'search', package: 'Flowpack.SearchPlugin')}" data-autocomplete-source="{f:uri.action(action: 'index', controller: 'Suggest', package: 'Flowpack.SearchPlugin', format: 'json', absolute: 1, arguments: {contextNode: node.identifier})}"/>
33
<button type="submit">{f:translate(id: 'search', package: 'Flowpack.SearchPlugin')}</button>
44
</form>

0 commit comments

Comments
 (0)