Skip to content

Paginated data in the resource data tool #7

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

Merged
merged 4 commits into from
Jun 11, 2025
Merged
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
12 changes: 6 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
vendor
composer.lock
node_modules
build
/.idea
/build
/node_modules
/vendor
.DS_Store
.pint.cache
.idea
build
composer.lock
7 changes: 7 additions & 0 deletions src/Exceptions/FilamentResourceIndexPageDoesNotExist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Kirschbaum\Loop\Filament\Exceptions;

use Exception;

class FilamentResourceIndexPageDoesNotExist extends Exception {}
4 changes: 2 additions & 2 deletions src/FilamentToolkit.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
use Kirschbaum\Loop\Enums\Mode;

/**
* @method static self make(Resource[] $resources, Mode $mode = Mode::ReadOnly)
* @method static self make(string[] $resources = [], Mode $mode = Mode::ReadOnly)
*/
class FilamentToolkit implements Toolkit
{
use Makeable;

/**
* @param resource[] $resources
* @param class-string<resource>[] $resources
*/
public function __construct(
public readonly array $resources = [],
Expand Down
120 changes: 81 additions & 39 deletions src/GetFilamentResourceDataTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
namespace Kirschbaum\Loop\Filament;

use Exception;
use Filament\Resources\Pages\ListRecords;
use Filament\Resources\Pages\PageRegistration;
use Filament\Resources\Resource;
use Filament\Tables\Columns\Column;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
Expand All @@ -11,7 +14,9 @@
use Kirschbaum\Loop\Contracts\Tool;
use Kirschbaum\Loop\Exceptions\LoopMcpException;
use Kirschbaum\Loop\Filament\Concerns\ProvidesFilamentResourceInstance;
use Kirschbaum\Loop\Filament\Exceptions\FilamentResourceIndexPageDoesNotExist;
use Prism\Prism\Tool as PrismTool;
use Throwable;

class GetFilamentResourceDataTool implements Tool
{
Expand All @@ -23,19 +28,17 @@ public function build(): PrismTool
return app(PrismTool::class)
->as($this->getName())
->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.')
->withStringParameter('resource', 'The resource class name of the resource to get data for, from the list_filament_resources tool.', required: true)
->withStringParameter('resource', 'The resource class name of the resource to get data for, from the list_filament_resources tool.')
->withStringParameter('filters', 'JSON string of filters to apply (e.g., \'{"status": "published", "author_id": [1, 2]}\').', required: false)
->using(function (string $resource, ?string $filters = null) {
->withNumberParameter('perPage', 'The resource data is paginated. This is the number of records per page. It defaults to 10', required: false)
->withNumberParameter('page', 'The resource data is paginated. This is the page the paginated results should be from.', required: false)
->using(function (string $resource, ?string $filters = null, ?int $perPage = 10, ?int $page = null) {
$resource = $this->getResourceInstance($resource);
$filters = $this->parseFilters($filters);

try {
$listPageClass = $resource::getPages()['index'];
$component = $listPageClass->getPage();
$listPage = $this->getListPage($resource);

/** @var InteractsWithTable $listPage */
$listPage = new $component;
$listPage->bootedInteractsWithTable();
$table = $listPage->getTable();
$tableColumns = $table->getColumns();

Expand Down Expand Up @@ -69,42 +72,50 @@ public function build(): PrismTool
}
}

// TODO: Allow the tool to specify the number of results to return with a max
$results = $listPage->getFilteredTableQuery()->take(10)->get();

$outputData = $results->map(function (Model $model) use ($tableColumns) {
$rowData = [
$model->getKeyName() => $model->getKey(),
];

foreach ($tableColumns as $column) {
/** @var Column $column */
$columnName = $column->getName();

try {
if (str_contains($columnName, '.')) {
$relationName = strtok($columnName, '.');

if (method_exists($model, $relationName)) {
$model->loadMissing($relationName);
$value = data_get($model, $columnName);
} else {
$value = null;
Log::warning("Relation '{$relationName}' not found on model for column '{$columnName}'.");
$results = $listPage->getFilteredTableQuery()->paginate(perPage: $perPage, page: $page);

$outputData = [
'data' => $results->getCollection()
->map(function (Model $model) use ($tableColumns) {
$rowData = [
$model->getKeyName() => $model->getKey(),
];

foreach ($tableColumns as $column) {
/** @var Column $column */
$columnName = $column->getName();

try {
if (str_contains($columnName, '.')) {
$relationName = strtok($columnName, '.');

if (method_exists($model, $relationName)) {
$model->loadMissing($relationName);
$value = data_get($model, $columnName);
} else {
$value = null;
Log::warning("Relation '{$relationName}' not found on model for column '{$columnName}'.");
}
} else {
$value = $model->getAttribute($columnName);
}

$rowData[$columnName] = $value;
} catch (Exception $e) {
$rowData[$columnName] = null;
Log::error("Could not retrieve value for column '{$columnName}' on model ID {$model->getKey()}': {$e->getMessage()}");
}
} else {
$value = $model->getAttribute($columnName);
}

$rowData[$columnName] = $value;
} catch (Exception $e) {
$rowData[$columnName] = null;
Log::error("Could not retrieve value for column '{$columnName}' on model ID {$model->getKey()}': {$e->getMessage()}");
}
}
return $rowData;
}),

return $rowData;
});
'pagination' => [
'total' => $results->total(),
'per_page' => $results->perPage(),
'current_page' => $results->currentPage(),
],
];

return json_encode($outputData);
} catch (Exception $e) {
Expand Down Expand Up @@ -144,4 +155,35 @@ protected function parseFilters(?string $filtersJson = null): array

return $filters;
}

/**
* @throws Throwable
*/
protected function getListPage(Resource $resource): ListRecords
{
/**
* @var ?PageRegistration $listPageClass
*/
$listPageClass = data_get($resource::getPages(), 'index');

throw_unless(
$listPageClass instanceof PageRegistration,
FilamentResourceIndexPageDoesNotExist::class,
'No index page exists for ['.get_class($resource).']'
);

/**
* @var class-string<ListRecords> $component
*/
$component = $listPageClass->getPage();

/**
* @var ListRecords $listPage
*/
$listPage = new $component;

$listPage->bootedInteractsWithTable();

return $listPage;
}
}
2 changes: 1 addition & 1 deletion src/ListFilamentResourcesTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ListFilamentResourcesTool implements Tool
/**
* @param resource[] $resources
*/
public function __construct(private array $resources = []) {}
public function __construct(protected readonly array $resources = []) {}

public function build(): PrismTool
{
Expand Down