Skip to content

Commit fe83766

Browse files
authored
Deal with form schema inside filters when present (#5)
* Deal with form schema inside filters when present * Fix styling * Removed unused stuff * Added test, added test CI * Fix styling * Additional adjustments * Removed coverage flag * Added phpunit.xml --------- Co-authored-by: luisdalmolin <[email protected]>
1 parent cbb2de2 commit fe83766

File tree

6 files changed

+176
-16
lines changed

6 files changed

+176
-16
lines changed

.github/workflows/run-tests.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: run-tests
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
runs-on: ${{ matrix.os }}
10+
strategy:
11+
fail-fast: true
12+
matrix:
13+
os: [ubuntu-latest]
14+
php: [8.3, 8.4]
15+
laravel: [11.*, 12.*]
16+
stability: [prefer-lowest, prefer-stable]
17+
include:
18+
- laravel: 11.*
19+
testbench: ^9.9
20+
carbon: ^2.63
21+
- laravel: 12.*
22+
testbench: 10.*
23+
carbon: ^2.63|^3.0
24+
25+
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
26+
27+
steps:
28+
- name: Checkout code
29+
uses: actions/checkout@v4
30+
31+
- name: Setup PHP
32+
uses: shivammathur/setup-php@v2
33+
with:
34+
php-version: ${{ matrix.php }}
35+
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, swoole, openssl
36+
coverage: pcov
37+
38+
- name: Setup problem matchers
39+
run: |
40+
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
41+
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
42+
43+
- name: Install dependencies
44+
run: |
45+
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update
46+
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
47+
48+
- name: List Installed Dependencies
49+
run: composer show -D
50+
51+
- name: Execute tests
52+
run: vendor/bin/pest --ci --bail --compact --memory

phpunit.xml.dist

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
>
7+
<testsuites>
8+
<testsuite name="Unit">
9+
<directory>tests/Unit</directory>
10+
</testsuite>
11+
<testsuite name="Feature">
12+
<directory>tests/Feature</directory>
13+
</testsuite>
14+
</testsuites>
15+
<coverage>
16+
<report>
17+
<html outputDirectory="build/coverage"/>
18+
<text outputFile="build/coverage.txt"/>
19+
<clover outputFile="build/logs/clover.xml"/>
20+
</report>
21+
</coverage>
22+
<source>
23+
<include>
24+
<directory>./src</directory>
25+
</include>
26+
</source>
27+
<php>
28+
<env name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
29+
<env name="APP_ENV" value="testing"/>
30+
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
31+
<env name="BCRYPT_ROUNDS" value="4"/>
32+
<env name="CACHE_STORE" value="array"/>
33+
<env name="DB_CONNECTION" value="testing"/>
34+
<env name="MAIL_MAILER" value="array"/>
35+
<env name="PULSE_ENABLED" value="false"/>
36+
<env name="QUEUE_CONNECTION" value="sync"/>
37+
<env name="SESSION_DRIVER" value="array"/>
38+
<env name="TELESCOPE_ENABLED" value="false"/>
39+
</php>
40+
</phpunit>

src/DescribeFilamentResourceTool.php

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -217,22 +217,20 @@ public function mapFilterType(BaseFilter $filter): string
217217
};
218218
}
219219

220-
public function mapFormComponent(Component $component, Resource $resource): ?array
220+
public function mapFormComponent(Component $component, ?Resource $resource = null): ?array
221221
{
222222
$baseInfo = [
223223
'name' => $component->getName(),
224224
'type' => $this->mapComponentType($component),
225225
'label' => $component->getLabel(),
226226
'required' => method_exists($component, 'isRequired') ? $component->isRequired() : null,
227-
'disabled' => method_exists($component, 'isDisabled') ? $component->isDisabled() : null,
228-
// 'nullable' => method_exists($component, 'isNullable') ? $component->isNullable() : null, // Needs checking validation rules
229227
];
230228

231229
if ($component instanceof TextInput) {
232230
$baseInfo['maxLength'] = $component->getMaxLength();
233231
}
234232

235-
if ($component instanceof Select && $component->getRelationshipName()) {
233+
if ($resource && $component instanceof Select && $component->getRelationshipName()) {
236234
$modelClass = $resource::getModel();
237235
$modelInstance = app($modelClass);
238236
$relationshipDefinition = $modelInstance->{$component->getRelationshipName()}();
@@ -241,25 +239,21 @@ public function mapFormComponent(Component $component, Resource $resource): ?arr
241239
'type' => class_basename($relationshipDefinition), // e.g., BelongsTo
242240
'model' => get_class($relationshipDefinition->getRelated()),
243241
'displayColumn' => $component->getRelationshipTitleAttribute(),
244-
'foreignKey' => $relationshipDefinition->getForeignKeyName(), // Might need adjustment based on relationship type
242+
'foreignKey' => $relationshipDefinition->getForeignKeyName(),
245243
];
246244
}
247245

248-
// Add more specific component type mappings here if needed
249-
250246
return $baseInfo;
251247
}
252248

253249
public function mapTableAction(Action|BulkAction $action): string
254250
{
255-
// Map common actions to simple strings, fallback to action name
256251
$name = $action->getName();
257252

258253
return match ($name) {
259254
'view', 'edit', 'delete', 'forceDelete', 'restore', 'replicate' => $name,
260-
default => $name, // Return the action name itself
255+
default => $name,
261256
};
262-
// Could potentially add more details like label, icon, color if needed
263257
}
264258

265259
public function mapTableColumn(Column $column): array
@@ -283,17 +277,26 @@ public function mapTableFilter(BaseFilter $filter): array
283277
'type' => $this->mapFilterType($filter),
284278
];
285279

280+
if ($filter->hasFormSchema()) {
281+
$baseInfo['usage'] = 'Please use the form schema to filter the data.';
282+
$baseInfo['type'] = 'form';
283+
$baseInfo['form'] = collect($filter->getFormSchema())
284+
->reject(fn (Component $component) => $component instanceof Grid || $component instanceof Fieldset)
285+
->map(fn (Component $component) => $this->mapFormComponent($component))
286+
->filter()
287+
->values()
288+
->all();
289+
}
290+
286291
if ($filter instanceof TernaryFilter) {
287292
// Condition is implicit (true/false/all)
288293
} elseif ($filter instanceof SelectFilter) {
289-
$baseInfo['optionsSource'] = 'Dynamic/Callable'; // Getting exact source is complex
294+
$baseInfo['options'] = 'Dynamic';
290295

291-
// Try to get options if they are simple array
292296
if (method_exists($filter, 'getOptions') && is_array($options = $filter->getOptions())) {
293-
$baseInfo['optionsSource'] = $options;
297+
$baseInfo['options'] = $options;
294298
}
295299
}
296-
// Add more specific filter type mappings here if needed
297300

298301
return $baseInfo;
299302
}

src/GetFilamentResourceDataTool.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function build(): PrismTool
2222
{
2323
return app(PrismTool::class)
2424
->as($this->getName())
25-
->for('Gets the data for a given Filament resource, applying optional filters provided in the describe_filament_resource tool. Always call the describe_filament_resource tool before calling this tool. Try to use the available filters to get the data you need.')
25+
->for('Gets the data for a given Filament resource, applying optional filters (try to use them). Always call the describe_filament_resource tool before calling this tool. Always try to use the available filters to get the data you need.')
2626
->withStringParameter('resource', 'The resource class name of the resource to get data for, from the list_filament_resources tool.', required: true)
2727
->withStringParameter('filters', 'JSON string of filters to apply (e.g., \'{"status": "published", "author_id": [1, 2]}\').', required: false)
2828
->using(function (string $resource, ?string $filters = null) {
@@ -32,6 +32,8 @@ public function build(): PrismTool
3232
try {
3333
$listPageClass = $resource::getPages()['index'];
3434
$component = $listPageClass->getPage();
35+
36+
/** @var InteractsWithTable $listPage */
3537
$listPage = new $component;
3638
$listPage->bootedInteractsWithTable();
3739
$table = $listPage->getTable();
@@ -44,6 +46,8 @@ public function build(): PrismTool
4446
$listPage->tableSearch = $filters[$column->getName()];
4547
});
4648

49+
$listPage->resetTableFiltersForm();
50+
4751
foreach ($listPage->getTable()->getFilters() as $filter) {
4852
if (method_exists($filter, 'isMultiple') && $filter->isMultiple()) {
4953
$listPage->tableFilters[$filter->getName()] = [
@@ -55,6 +59,13 @@ public function build(): PrismTool
5559
$listPage->tableFilters[$filter->getName()] = [
5660
'value' => $filters[$filter->getName()] ?? null,
5761
];
62+
63+
if ($filter->hasFormSchema()) {
64+
foreach ($filter->getFormSchema() as $formSchema) {
65+
$listPage->tableFilters[$filter->getName()][$formSchema->getName()] =
66+
$filters[$formSchema->getName()] ?? null;
67+
}
68+
}
5869
}
5970
}
6071

tests/Feature/DescribeFilamentResourceToolTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,34 @@
4545
->and($emailColumn['sortable'])->toBeTrue();
4646
});
4747

48+
it('can extract filters through the tool', function () {
49+
$tool = new DescribeFilamentResourceTool;
50+
$resource = app(TestUserResource::class);
51+
52+
$tableSchema = $tool->extractTableSchema($resource);
53+
$filters = $tableSchema['filters'];
54+
55+
$nameFilter = collect($filters)->firstWhere('name', 'name');
56+
expect($nameFilter)->toBeArray()
57+
->and($nameFilter['type'])->toBe('select')
58+
->and($nameFilter['options'])->toBeArray();
59+
60+
$emailFilter = collect($filters)->firstWhere('name', 'email');
61+
expect($emailFilter)->toBeArray()
62+
->and($emailFilter['type'])->toBe('searchable_column');
63+
64+
$createdAtFilter = collect($filters)->firstWhere('name', 'created_at');
65+
expect($createdAtFilter)->toBeArray()
66+
->and($createdAtFilter['type'])->toBe('form')
67+
->and($createdAtFilter['form'])->toBeArray()
68+
->and($createdAtFilter['form'][0])->toBeArray()
69+
->and($createdAtFilter['form'][0]['name'])->toBe('created_at_after')
70+
->and($createdAtFilter['form'][0]['type'])->toBe('datetime')
71+
->and($createdAtFilter['form'][1])->toBeArray()
72+
->and($createdAtFilter['form'][1]['name'])->toBe('created_at_before')
73+
->and($createdAtFilter['form'][1]['type'])->toBe('datetime');
74+
});
75+
4876
it('can extract bulk actions through the tool', function () {
4977
$tool = new DescribeFilamentResourceTool;
5078
$resource = app(TestUserResource::class);

tests/Feature/TestUserResource.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
namespace Tests\Feature;
44

5+
use Carbon\Carbon;
56
use Filament\Forms;
67
use Filament\Resources\Resource;
78
use Filament\Tables;
9+
use Filament\Tables\Filters\Filter;
810
use Filament\Tables\Table;
11+
use Illuminate\Database\Eloquent\Builder;
912

1013
class TestUserResource extends Resource
1114
{
@@ -42,7 +45,30 @@ public static function table(Table $table): Table
4245
->sortable(),
4346
])
4447
->filters([
45-
//
48+
Tables\Filters\SelectFilter::make('name')
49+
->options([
50+
'John' => 'John',
51+
'Jane' => 'Jane',
52+
]),
53+
Filter::make('created_at')
54+
->form([
55+
Forms\Components\DatePicker::make('created_at_after')
56+
->label('Created After')
57+
->default(Carbon::now()->subMonths(6)),
58+
Forms\Components\DatePicker::make('created_at_before')
59+
->label('Created Before'),
60+
])
61+
->query(function (Builder $query, array $data): Builder {
62+
return $query
63+
->when(
64+
$data['created_at_after'],
65+
fn (Builder $query, $date): Builder => $query->whereDate('date', '>=', $date),
66+
)
67+
->when(
68+
$data['created_at_before'],
69+
fn (Builder $query, $date): Builder => $query->whereDate('date', '<=', $date),
70+
);
71+
}),
4672
])
4773
->actions([
4874
Tables\Actions\EditAction::make(),

0 commit comments

Comments
 (0)