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

IBX-6413: Search Suggestion (Autocomplete for search) #33

Merged
merged 66 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
fb6b642
Added autocomplete configuration
kisztof Sep 18, 2023
4e67ad5
Added tests
kisztof Sep 19, 2023
2c7a5e3
Search Autocomplete Endpoint
kisztof Oct 9, 2023
885f626
Breadcrumps subscriber
kisztof Oct 11, 2023
7966db2
Removed root location
kisztof Oct 11, 2023
527468d
Proper Json Response
kisztof Oct 11, 2023
e8a862d
Code style fixes, PHPStan fixed
kisztof Oct 11, 2023
d1e4c95
Fixed response return type
kisztof Oct 12, 2023
64b2ec2
Refactored extension point
kisztof Oct 17, 2023
94cf57b
Added improvements
kisztof Oct 18, 2023
a5b5db3
Fixed suggestion configuration
kisztof Oct 19, 2023
d8fb688
Added tests
kisztof Oct 19, 2023
bb0781c
Added Truncate functionality
kisztof Oct 23, 2023
bfa1021
Fixed tests and models
kisztof Oct 23, 2023
3d8fe63
Generated baseline
kisztof Oct 23, 2023
12d9547
Fixed code suggestions
kisztof Oct 24, 2023
b1689ab
Added ParamConverter test
kisztof Oct 24, 2023
6efdc5b
Cleanup
kisztof Oct 24, 2023
c61c781
More improvements
kisztof Oct 24, 2023
fe7f398
Code review fixes
kisztof Oct 25, 2023
9685877
Added Custom Suggestion Normalizers
kisztof Oct 26, 2023
89a9a39
Fixed code smells
kisztof Oct 26, 2023
429ce34
Fixed yaml
kisztof Oct 26, 2023
aac1b4f
Expose route to JS
kisztof Oct 26, 2023
ff1728d
Fixed sonar cloud allert
kisztof Oct 26, 2023
c604cdb
Fixes
kisztof Oct 26, 2023
609a2c3
Fixed CS
kisztof Oct 26, 2023
3fc20a1
Code review suggestions applied
kisztof Oct 27, 2023
d086898
Argument Resolver added
kisztof Oct 27, 2023
ecd2748
Updated PHPUnit to v9
kisztof Oct 27, 2023
23d42e4
Removed deprecated "withConsecutive" method
kisztof Oct 27, 2023
da8c648
Update src/bundle/Resources/config/services/normalizers.yaml
kisztof Oct 27, 2023
fa58cef
Changed serializer
kisztof Oct 27, 2023
debf12e
Update tests/contracts/EventDispatcher/Event/SuggestionEventTest.php
kisztof Oct 27, 2023
58857dd
Additional fixes
kisztof Oct 27, 2023
258575c
Fixed removing root location
kisztof Oct 27, 2023
03288c1
Update composer.json
kisztof Oct 31, 2023
89b782d
Applied CR suggestions
kisztof Oct 31, 2023
fca194d
Code review suggestions applied
kisztof Oct 31, 2023
21aa941
Code review suggestions applied
kisztof Oct 31, 2023
5d130e6
Code review suggestions applied
kisztof Nov 2, 2023
4970986
Code review suggestions applied
kisztof Nov 2, 2023
df7e476
Code review suggestions applied
kisztof Nov 3, 2023
ac106c0
Code review suggestions applied
kisztof Nov 3, 2023
68a5e84
Code review suggestions applied
kisztof Nov 3, 2023
a5f395c
Added container.service_subscriber for serializer
kisztof Nov 7, 2023
c360c2a
Fixed phpstan.neon
kisztof Nov 7, 2023
739ff56
Fixed ContentType serialization
kisztof Nov 7, 2023
319359f
Update src/contracts/Model/Suggestion/SuggestionCollection.php
kisztof Nov 7, 2023
248cede
Update src/contracts/Model/Suggestion/ParentLocationCollection.php
kisztof Nov 7, 2023
746eeaa
Update src/contracts/Model/Suggestion/SuggestionCollection.php
kisztof Nov 7, 2023
308cc46
Update src/contracts/Model/Suggestion/SuggestionCollection.php
kisztof Nov 7, 2023
1cb0968
Update src/contracts/Model/Suggestion/ParentLocationCollection.php
kisztof Nov 7, 2023
28d94e7
Made changes in ContentSuggestion - Complete object provided
kisztof Nov 7, 2023
2336f02
BeforeSuggestioEvent and SuggestionEvent moved to Service namespace
kisztof Nov 7, 2023
280f8ce
Tests improvements
kisztof Nov 8, 2023
05513c9
Moved config resolving
kisztof Nov 8, 2023
132f0d9
Fixed sonar cloud
kisztof Nov 8, 2023
aa28f89
Fixed tests
kisztof Nov 8, 2023
db57d92
Fixed indent
kisztof Nov 8, 2023
9d9d9c5
Fixed test
kisztof Nov 8, 2023
9f110c9
Fixed issue with normalizer
kisztof Nov 14, 2023
40f2473
Fixed issue with normalizer
kisztof Nov 15, 2023
0ecc817
Fixed issue with normalizer
kisztof Nov 16, 2023
61bd3ce
Code review improvements
kisztof Nov 16, 2023
bd48916
Fixed default values
kisztof Nov 17, 2023
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
12 changes: 8 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
},
"autoload-dev": {
"psr-4": {
"Ibexa\\Tests\\Search\\": "tests/lib/",
"Ibexa\\Tests\\Bundle\\Search\\": "tests/bundle/",
"Ibexa\\Tests\\Contracts\\Search\\": "tests/contracts/",
"Ibexa\\Tests\\Search\\": "tests/lib/",
"Ibexa\\Platform\\Tests\\Contracts\\Search\\": "tests/contracts/",
"Ibexa\\Platform\\Tests\\Bundle\\Search\\": "tests/bundle/",
"Ibexa\\Platform\\Tests\\Search\\": "tests/lib/"
}
Expand All @@ -32,16 +34,18 @@
"symfony/config": "^5.0",
"symfony/form": "^5.0",
"symfony/event-dispatcher": "^5.0",
"pagerfanta/pagerfanta": "^2.1"
"pagerfanta/pagerfanta": "^2.1",
"symfony/serializer": "^5.4"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"phpunit/phpunit": "^9.6",
"friendsofphp/php-cs-fixer": "^3.0",
"ibexa/code-style": "^1.0",
"ibexa/doctrine-schema": "~4.6.x-dev",
"phpstan/phpstan": "^1.10",
"phpstan/phpstan-phpunit": "^1.3",
"phpstan/phpstan-symfony": "^1.3"
"phpstan/phpstan-symfony": "^1.3",
"matthiasnoback/symfony-dependency-injection-test": "^4.3"
},
"scripts": {
"fix-cs": "php-cs-fixer fix --config=.php-cs-fixer.php --show-progress=dots",
Expand Down
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ parameters:
count: 1
path: src/lib/Mapper/PagerSearchContentToDataMapper.php

-
message: "#^Property Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Content\\\\Search\\\\SearchHit\\:\\:\\$score \\(float\\) on left side of \\?\\? is not nullable\\.$#"
count: 1
path: src/lib/Mapper/SearchHitToContentSuggestionMapper.php

-
message: "#^Method Ibexa\\\\Search\\\\QueryType\\\\SearchQueryType\\:\\:doGetQuery\\(\\) has parameter \\$parameters with no value type specified in iterable type array\\.$#"
count: 1
Expand Down
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ parameters:
paths:
- src
- tests

ignoreErrors:
- message: "#^Cannot call method (log|debug|info|notice|warning|error|critical|alert|emergency)\\(\\) on Psr\\\\Log\\\\LoggerInterface\\|null\\.$#"
50 changes: 50 additions & 0 deletions src/bundle/ArgumentResolver/SuggestionQueryArgumentResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Bundle\Search\ArgumentResolver;

use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
use Ibexa\Search\Model\SuggestionQuery;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

final class SuggestionQueryArgumentResolver implements ArgumentValueResolverInterface
{
private ConfigResolverInterface $configResolver;

public function __construct(ConfigResolverInterface $configResolver)
{
$this->configResolver = $configResolver;
}

public function supports(Request $request, ArgumentMetadata $argument): bool
{
return SuggestionQuery::class === $argument->getType();
}

/**
* @return iterable<\Ibexa\Search\Model\SuggestionQuery>
*
* @throw \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$defaultLimit = $this->configResolver->getParameter('search.suggestion.result_limit');
$query = $request->query->get('query');
$limit = $request->query->getInt('limit', $defaultLimit);
$language = $request->query->get('language');

if ($query === null) {
throw new BadRequestHttpException('Missing query parameter');
}

yield new SuggestionQuery($query, $limit, $language);
}
}
32 changes: 32 additions & 0 deletions src/bundle/Controller/SuggestionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Bundle\Search\Controller;

use Ibexa\Search\Model\SuggestionQuery;
use Ibexa\Search\Service\SuggestionService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;

final class SuggestionController extends AbstractController
{
private SuggestionService $suggestionService;

public function __construct(
SuggestionService $suggestionService
) {
$this->suggestionService = $suggestionService;
}

public function suggestAction(SuggestionQuery $suggestionQuery): JsonResponse
{
$result = $this->suggestionService->suggest($suggestionQuery);

return $this->json($result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Bundle\Search\DependencyInjection\Configuration\Parser\SiteAccessAware;

use Ibexa\Bundle\Core\DependencyInjection\Configuration\AbstractParser;
use Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\ContextualizerInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;

/**
* Configuration parser for search.
*
* @Example configuration:
*
* ```yaml
* ibexa:
* system:
* default: # configuration per siteaccess or siteaccess group
* search:
* suggestion:
* min_query_length: 3
* result_limit: 5
* ```
*/
final class SuggestionParser extends AbstractParser
{
/**
* @param array<string, mixed> $scopeSettings
*/
public function mapConfig(
array &$scopeSettings,
$currentScope,
ContextualizerInterface $contextualizer
): void {
if (empty($scopeSettings['search'])) {
return;
}

$settings = $scopeSettings['search'];

$this->addSuggestionParameters($settings, $currentScope, $contextualizer);
}

public function addSemanticConfig(NodeBuilder $nodeBuilder): void
{
$rootProductCatalogNode = $nodeBuilder->arrayNode('search');
$rootProductCatalogNode->append($this->addSuggestionConfiguration());
}

/**
* @param array<string, mixed> $settings
*/
private function addSuggestionParameters(
array $settings,
string $currentScope,
ContextualizerInterface $contextualizer
): void {
$names = [
'min_query_length',
'result_limit',
];
foreach ($names as $name) {
if (isset($settings['suggestion'][$name])) {
$contextualizer->setContextualParameter(
'search.suggestion.' . $name,
$currentScope,
$settings['suggestion'][$name]
);
}
}
}

private function addSuggestionConfiguration(): ArrayNodeDefinition
{
$treeBuilder = new TreeBuilder('suggestion');
$node = $treeBuilder->getRootNode();

$node
->addDefaultsIfNotSet()
->children()
->integerNode('min_query_length')
->info('The minimum length of the query string needed to trigger suggestions. Minimum value is 3.')
->isRequired()
->defaultValue(3)
kisztof marked this conversation as resolved.
Show resolved Hide resolved
->min(3)
->end()
->integerNode('result_limit')
->info('The maximum number of suggestion results to return. Minimum value is 5.')
->isRequired()
->defaultValue(5)
->min(5)
->end()
->end();

return $node;
}
}
3 changes: 3 additions & 0 deletions src/bundle/IbexaSearchBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/

namespace Ibexa\Bundle\Search;

use Ibexa\Bundle\Search\DependencyInjection\Configuration\Parser\Search;
use Ibexa\Bundle\Search\DependencyInjection\Configuration\Parser\SearchView;
use Ibexa\Bundle\Search\DependencyInjection\Configuration\Parser\SiteAccessAware\SuggestionParser;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

Expand All @@ -21,6 +23,7 @@ public function build(ContainerBuilder $container)
$core->addDefaultSettings(__DIR__ . '/Resources/config', ['default_settings.yaml']);
$core->addConfigParser(new Search());
$core->addConfigParser(new SearchView());
$core->addConfigParser(new SuggestionParser());
}
}

Expand Down
12 changes: 9 additions & 3 deletions src/bundle/Resources/config/routing.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
ibexa.search:
path: /search
methods: ['GET']
defaults:
_controller: 'Ibexa\Bundle\Search\Controller\SearchController::searchAction'
methods: [GET]
controller: 'Ibexa\Bundle\Search\Controller\SearchController::searchAction'

ibexa.search.suggestion:
path: /suggestion
methods: [GET]
controller: 'Ibexa\Bundle\Search\Controller\SuggestionController::suggestAction'
options:
expose: true
45 changes: 26 additions & 19 deletions src/bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
imports:
- { resource: forms.yaml }
- { resource: twig.yaml }
- { resource: sorting_definitions.yaml }
- { resource: views.yaml }
- { resource: forms.yaml }
- { resource: twig.yaml }
- { resource: sorting_definitions.yaml }
- { resource: views.yaml }
- { resource: services/suggestions.yaml }
- { resource: services/normalizers.yaml }

services:
_defaults:
autoconfigure: true
autowire: true
public: false
_defaults:
autoconfigure: true
autowire: true
public: false

Ibexa\Bundle\Search\Controller\SearchController:
tags:
- controller.service_arguments
Ibexa\Bundle\Search\Controller\:
resource: './../../Controller'
tags:
- controller.service_arguments

Ibexa\Search\Mapper\PagerSearchContentToDataMapper:
arguments:
$contentTypeService: '@ibexa.api.service.content_type'
$userService: '@ibexa.api.service.user'
$userLanguagePreferenceProvider: '@Ibexa\Core\MVC\Symfony\Locale\UserLanguagePreferenceProvider'
$translationHelper: '@Ibexa\Core\Helper\TranslationHelper'
$languageService: '@ibexa.api.service.language'
Ibexa\Bundle\Search\Controller\SuggestionController:
tags:
- { name: 'container.service_subscriber', key: 'serializer', id: 'ibexa.search.suggestion.serializer' }

Ibexa\Search\QueryType\SearchQueryType: ~
Ibexa\Search\Mapper\PagerSearchContentToDataMapper:
arguments:
$contentTypeService: '@ibexa.api.service.content_type'
$userService: '@ibexa.api.service.user'
$userLanguagePreferenceProvider: '@Ibexa\Core\MVC\Symfony\Locale\UserLanguagePreferenceProvider'
$translationHelper: '@Ibexa\Core\Helper\TranslationHelper'
$languageService: '@ibexa.api.service.language'

Ibexa\Search\QueryType\SearchQueryType: ~
Steveb-p marked this conversation as resolved.
Show resolved Hide resolved
24 changes: 24 additions & 0 deletions src/bundle/Resources/config/services/normalizers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

ibexa.search.suggestion.serializer:
class: Symfony\Component\Serializer\Serializer
autoconfigure: false
arguments:
$normalizers:
- '@Ibexa\Search\Serializer\Normalizer\Suggestion\ContentSuggestionNormalizer'
- '@Ibexa\Search\Serializer\Normalizer\Suggestion\LocationNormalizer'
$encoders:
- '@serializer.encoder.json'

Ibexa\Search\Serializer\Normalizer\Suggestion\ContentSuggestionNormalizer:
autoconfigure: false

Ibexa\Search\Serializer\Normalizer\Suggestion\ParentLocationCollectionNormalizer:
autoconfigure: false
Steveb-p marked this conversation as resolved.
Show resolved Hide resolved

Ibexa\Search\Serializer\Normalizer\Suggestion\LocationNormalizer:
autoconfigure: false
30 changes: 30 additions & 0 deletions src/bundle/Resources/config/services/suggestions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
parameters:
ibexa.site_access.config.default.search.suggestion.min_query_length: 3
ibexa.site_access.config.default.search.suggestion.result_limit: 5

services:
_defaults:
autoconfigure: true
autowire: true
public: false

Ibexa\Bundle\Search\ArgumentResolver\SuggestionQueryArgumentResolver:
tags:
- { name: 'controller.argument_value_resolver' }

Ibexa\Search\EventDispatcher\EventListener\ContentSuggestionSubscriber: ~

Ibexa\Search\Mapper\SearchHitToContentSuggestionMapper: ~

Ibexa\Contracts\Search\Mapper\SearchHitToContentSuggestionMapperInterface: '@Ibexa\Search\Mapper\SearchHitToContentSuggestionMapper'

Ibexa\Search\Service\SuggestionService: ~

Ibexa\Contracts\Search\Service\SuggestionServiceInterface: '@Ibexa\Search\Service\SuggestionService'

Ibexa\Search\Service\Event\SuggestionService:
decorates: Ibexa\Contracts\Search\Service\SuggestionServiceInterface
Steveb-p marked this conversation as resolved.
Show resolved Hide resolved

Ibexa\Search\Provider\ParentLocationProvider: ~

Ibexa\Contracts\Search\Provider\ParentLocationProviderInterface: '@Ibexa\Search\Provider\ParentLocationProvider'
Loading
Loading