Skip to content

Commit 4f05f40

Browse files
Merge pull request #13 from Guiziweb/feat/ai-searchable-options
feat(grid-assistant): add opt-in grid and opt-out filter/field options
2 parents 84f2bca + 07df9e1 commit 4f05f40

9 files changed

Lines changed: 204 additions & 14 deletions

File tree

config/services.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ services:
1616
arguments:
1717
$model: '%guiziweb.grid_assistant.model%'
1818

19+
Guiziweb\SyliusGridAssistantPlugin\Twig\Extension\GridAssistantExtension:
20+
arguments:
21+
$enabledGrids: '%guiziweb.grid_assistant.enabled_grids%'
22+
1923
Guiziweb\SyliusGridAssistantPlugin\Twig\Component\GridAssistantComponent:
2024
tags:
2125
- { name: 'sylius.live_component.admin', key: 'guiziweb:grid_assistant:search', template: '@GuiziwebSyliusGridAssistantPlugin/components/grid_assistant.html.twig' }

src/DependencyInjection/Configuration.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ public function getConfigTreeBuilder(): TreeBuilder
2020
->cannotBeEmpty()
2121
->info('The LLM model name to use. Must match the platform configured in symfony/ai-bundle (e.g. "gpt-4o", "claude-sonnet-4-6").')
2222
->end()
23+
->arrayNode('enabled_grids')
24+
->scalarPrototype()->end()
25+
->defaultValue([])
26+
->info('Grid codes where the AI assistant is enabled. Opt-in: empty by default.')
27+
->end()
2328
->end()
2429
;
2530

src/DependencyInjection/GuiziwebSyliusGridAssistantExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public function load(array $configs, ContainerBuilder $container): void
2121
$config = $this->processConfiguration($configuration, $configs);
2222

2323
$container->setParameter('guiziweb.grid_assistant.model', $config['model']);
24+
$container->setParameter('guiziweb.grid_assistant.enabled_grids', $config['enabled_grids']);
2425
}
2526

2627
public function prepend(ContainerBuilder $container): void

src/Schema/GridSchemaBuilder.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ private function buildFiltersSchema(Grid $grid): array
6060
$filters = [];
6161

6262
foreach ($grid->getEnabledFilters() as $name => $filter) {
63+
if (false === ($filter->getOptions()['ai_searchable'] ?? true)) {
64+
continue;
65+
}
66+
6367
$filters[$name] = $this->buildFilterSchema($filter);
6468
}
6569

@@ -88,12 +92,18 @@ private function buildSortableFieldsSchema(Grid $grid): array
8892
$sortableFields = [];
8993

9094
foreach ($grid->getEnabledFields() as $name => $field) {
91-
if ($field->isSortable()) {
92-
$sortableFields[$name] = [
93-
'label' => $this->translateLabel($field->getLabel()),
94-
'path' => $field->getSortable(),
95-
];
95+
if (!$field->isSortable()) {
96+
continue;
9697
}
98+
99+
if (false === ($field->getOptions()['ai_searchable'] ?? true)) {
100+
continue;
101+
}
102+
103+
$sortableFields[$name] = [
104+
'label' => $this->translateLabel($field->getLabel()),
105+
'path' => $field->getSortable(),
106+
];
97107
}
98108

99109
return $sortableFields;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Guiziweb\SyliusGridAssistantPlugin\Twig\Extension;
6+
7+
use Twig\Extension\AbstractExtension;
8+
use Twig\TwigFunction;
9+
10+
final class GridAssistantExtension extends AbstractExtension
11+
{
12+
/**
13+
* @param list<string> $enabledGrids
14+
*/
15+
public function __construct(
16+
private readonly array $enabledGrids,
17+
) {
18+
}
19+
20+
public function getFunctions(): array
21+
{
22+
return [
23+
new TwigFunction('guiziweb_grid_assistant_enabled', $this->isEnabled(...)),
24+
];
25+
}
26+
27+
public function isEnabled(string $gridCode): bool
28+
{
29+
return in_array($gridCode, $this->enabledGrids, true);
30+
}
31+
}

templates/admin/grid/grid_assistant.html.twig

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
{% if resources is not null and resources.definition is defined %}
44
{% set grid_code = resources.definition.code %}
5-
{% set current_route = app.request.attributes.get('_route') %}
6-
{% set route_params = app.request.attributes.all('_route_params')|json_encode %}
7-
{% set ai_query = app.request.query.get('ai_query', '') %}
5+
{% if guiziweb_grid_assistant_enabled(grid_code) %}
6+
{% set current_route = app.request.attributes.get('_route') %}
7+
{% set route_params = app.request.attributes.all('_route_params')|json_encode %}
8+
{% set ai_query = app.request.query.get('ai_query', '') %}
89

9-
<twig:guiziweb:grid_assistant:search
10-
gridCode="{{ grid_code }}"
11-
routeName="{{ current_route }}"
12-
routeParams="{{ route_params }}"
13-
query="{{ ai_query }}"
14-
/>
10+
<twig:guiziweb:grid_assistant:search
11+
gridCode="{{ grid_code }}"
12+
routeName="{{ current_route }}"
13+
routeParams="{{ route_params }}"
14+
query="{{ ai_query }}"
15+
/>
16+
{% endif %}
1517
{% endif %}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
guiziweb_sylius_grid_assistant:
2+
enabled_grids:
3+
- sylius_admin_order
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Guiziweb\SyliusGridAssistantPlugin\Tests\Unit\Schema;
6+
7+
use Guiziweb\SyliusGridAssistantPlugin\Schema\Builder\FilterSchemaBuilderInterface;
8+
use Guiziweb\SyliusGridAssistantPlugin\Schema\Builder\FilterSchemaBuilderRegistry;
9+
use Guiziweb\SyliusGridAssistantPlugin\Schema\GridSchemaBuilder;
10+
use PHPUnit\Framework\TestCase;
11+
use Sylius\Component\Grid\Definition\Field;
12+
use Sylius\Component\Grid\Definition\Filter;
13+
use Sylius\Component\Grid\Definition\Grid;
14+
use Sylius\Component\Grid\Provider\GridProviderInterface;
15+
use Symfony\Contracts\Translation\TranslatorInterface;
16+
17+
final class GridSchemaBuilderTest extends TestCase
18+
{
19+
public function testFiltersWithAiSearchableFalseAreExcluded(): void
20+
{
21+
$grid = $this->makeGrid();
22+
$grid->addFilter($this->makeFilter('customer', 'string', ['ai_searchable' => true]));
23+
$grid->addFilter($this->makeFilter('internal_notes', 'string', ['ai_searchable' => false]));
24+
$grid->addFilter($this->makeFilter('date', 'date'));
25+
26+
$schema = $this->makeBuilder($grid)->buildSchema('any');
27+
28+
self::assertArrayHasKey('customer', $schema['filters']);
29+
self::assertArrayHasKey('date', $schema['filters']);
30+
self::assertArrayNotHasKey('internal_notes', $schema['filters']);
31+
}
32+
33+
public function testSortableFieldsWithAiSearchableFalseAreExcluded(): void
34+
{
35+
$grid = $this->makeGrid();
36+
$grid->addField($this->makeField('number', sortable: 'number'));
37+
$grid->addField($this->makeField('internal_id', sortable: 'internalId', options: ['ai_searchable' => false]));
38+
$grid->addField($this->makeField('total', sortable: 'total', options: ['ai_searchable' => true]));
39+
$grid->addField($this->makeField('actions', sortable: null));
40+
41+
$schema = $this->makeBuilder($grid)->buildSchema('any');
42+
43+
self::assertArrayHasKey('number', $schema['sortable_fields']);
44+
self::assertArrayHasKey('total', $schema['sortable_fields']);
45+
self::assertArrayNotHasKey('internal_id', $schema['sortable_fields']);
46+
self::assertArrayNotHasKey('actions', $schema['sortable_fields']);
47+
}
48+
49+
private function makeGrid(): Grid
50+
{
51+
return Grid::fromCodeAndDriverConfiguration('test_grid', 'doctrine/orm', ['class' => 'Foo']);
52+
}
53+
54+
/**
55+
* @param array<string, mixed> $options
56+
*/
57+
private function makeFilter(string $name, string $type, array $options = []): Filter
58+
{
59+
$filter = Filter::fromNameAndType($name, $type);
60+
$filter->setOptions($options);
61+
62+
return $filter;
63+
}
64+
65+
/**
66+
* @param array<string, mixed> $options
67+
*/
68+
private function makeField(string $name, ?string $sortable, array $options = []): Field
69+
{
70+
$field = Field::fromNameAndType($name, 'string');
71+
$field->setSortable($sortable);
72+
$field->setLabel($name);
73+
$field->setOptions($options);
74+
75+
return $field;
76+
}
77+
78+
private function makeBuilder(Grid $grid): GridSchemaBuilder
79+
{
80+
$gridProvider = $this->createMock(GridProviderInterface::class);
81+
$gridProvider->method('get')->willReturn($grid);
82+
83+
$registry = new FilterSchemaBuilderRegistry();
84+
$registry->register(new class implements FilterSchemaBuilderInterface {
85+
public static function getType(): string|array
86+
{
87+
return ['string', 'date'];
88+
}
89+
90+
public function build(Filter $filter): array
91+
{
92+
return ['type' => 'string'];
93+
}
94+
});
95+
96+
$translator = $this->createMock(TranslatorInterface::class);
97+
$translator->method('trans')->willReturnArgument(0);
98+
99+
return new GridSchemaBuilder($gridProvider, $registry, $translator);
100+
}
101+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Guiziweb\SyliusGridAssistantPlugin\Tests\Unit\Twig\Extension;
6+
7+
use Guiziweb\SyliusGridAssistantPlugin\Twig\Extension\GridAssistantExtension;
8+
use PHPUnit\Framework\TestCase;
9+
10+
final class GridAssistantExtensionTest extends TestCase
11+
{
12+
public function testIsEnabledReturnsTrueForGridInTheEnabledList(): void
13+
{
14+
$extension = new GridAssistantExtension(['sylius_admin_order', 'sylius_admin_customer']);
15+
16+
self::assertTrue($extension->isEnabled('sylius_admin_order'));
17+
self::assertTrue($extension->isEnabled('sylius_admin_customer'));
18+
}
19+
20+
public function testIsEnabledReturnsFalseForGridNotInTheEnabledList(): void
21+
{
22+
$extension = new GridAssistantExtension(['sylius_admin_order']);
23+
24+
self::assertFalse($extension->isEnabled('sylius_admin_product'));
25+
}
26+
27+
public function testIsEnabledReturnsFalseWhenNoGridIsEnabled(): void
28+
{
29+
$extension = new GridAssistantExtension([]);
30+
31+
self::assertFalse($extension->isEnabled('sylius_admin_order'));
32+
}
33+
}

0 commit comments

Comments
 (0)