Skip to content

Commit 0144367

Browse files
committed
wip
1 parent 17a20e9 commit 0144367

1 file changed

Lines changed: 253 additions & 0 deletions

File tree

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
---
2+
title: Building a Filament integration
3+
weight: 7
4+
---
5+
6+
This package does not ship with a Filament integration, but it's straightforward to build one yourself. Below is a complete example of a Filament resource that lets you browse your search indexes and test queries from the admin panel.
7+
8+
## The resource class
9+
10+
Create a `SiteSearchConfigResource` that provides a read-only overview of all configured search indexes.
11+
12+
```php
13+
namespace App\Filament\Resources;
14+
15+
use Exception;
16+
use Filament\Resources\Resource;
17+
use Filament\Tables;
18+
use Filament\Tables\Table;
19+
use Spatie\SiteSearch\Models\SiteSearchConfig;
20+
21+
class SiteSearchConfigResource extends Resource
22+
{
23+
protected static ?string $model = SiteSearchConfig::class;
24+
25+
protected static ?string $navigationIcon = 'heroicon-o-magnifying-glass';
26+
27+
protected static ?string $navigationLabel = 'Site Search';
28+
29+
protected static ?string $slug = 'site-search';
30+
31+
public static function canCreate(): bool
32+
{
33+
return false;
34+
}
35+
36+
public static function table(Table $table): Table
37+
{
38+
return $table
39+
->columns([
40+
Tables\Columns\TextColumn::make('name')
41+
->searchable(),
42+
Tables\Columns\TextColumn::make('crawl_url')
43+
->label('Crawl URL')
44+
->limit(50),
45+
Tables\Columns\TextColumn::make('status')
46+
->badge()
47+
->getStateUsing(fn (SiteSearchConfig $record) => static::getStatus($record))
48+
->color(fn (string $state) => match ($state) {
49+
'OK' => 'success',
50+
'Crawling...' => 'info',
51+
'Processing...' => 'warning',
52+
'Waiting on first crawl' => 'warning',
53+
'Did not find index' => 'danger',
54+
default => 'gray',
55+
}),
56+
Tables\Columns\IconColumn::make('enabled')
57+
->boolean(),
58+
Tables\Columns\TextColumn::make('document_count')
59+
->label('Documents'),
60+
Tables\Columns\TextColumn::make('number_of_urls_indexed')
61+
->label('URLs indexed'),
62+
Tables\Columns\TextColumn::make('urls_failed')
63+
->label('Failed'),
64+
Tables\Columns\TextColumn::make('crawling_ended_at')
65+
->label('Last crawl')
66+
->since(),
67+
])
68+
->actions([
69+
Tables\Actions\ViewAction::make(),
70+
]);
71+
}
72+
73+
public static function getStatus(SiteSearchConfig $config): string
74+
{
75+
if (! $config->index_name) {
76+
return 'Waiting on first crawl';
77+
}
78+
79+
if ($config->pending_index_name) {
80+
return 'Crawling...';
81+
}
82+
83+
try {
84+
if ($config->getDriver()->isProcessing($config->index_name)) {
85+
return 'Processing...';
86+
}
87+
} catch (Exception) {
88+
return 'Did not find index';
89+
}
90+
91+
return 'OK';
92+
}
93+
94+
public static function getPages(): array
95+
{
96+
return [
97+
'index' => Pages\ListSiteSearchConfigs::route('/'),
98+
'view' => Pages\ViewSiteSearchConfig::route('/{record}'),
99+
];
100+
}
101+
}
102+
```
103+
104+
## The list page
105+
106+
The list page is a standard Filament `ListRecords` page.
107+
108+
```php
109+
namespace App\Filament\Resources\SiteSearchConfigResource\Pages;
110+
111+
use App\Filament\Resources\SiteSearchConfigResource;
112+
use Filament\Resources\Pages\ListRecords;
113+
114+
class ListSiteSearchConfigs extends ListRecords
115+
{
116+
protected static string $resource = SiteSearchConfigResource::class;
117+
}
118+
```
119+
120+
## The view page
121+
122+
The view page shows detailed information about a search index and includes a search form to test queries.
123+
124+
```php
125+
namespace App\Filament\Resources\SiteSearchConfigResource\Pages;
126+
127+
use App\Filament\Resources\SiteSearchConfigResource;
128+
use Filament\Infolists\Components\Section;
129+
use Filament\Infolists\Components\TextEntry;
130+
use Filament\Infolists\Infolist;
131+
use Filament\Resources\Pages\ViewRecord;
132+
use Livewire\Attributes\Url;
133+
use Spatie\SiteSearch\Models\SiteSearchConfig;
134+
use Spatie\SiteSearch\SearchResults\SearchResults;
135+
136+
class ViewSiteSearchConfig extends ViewRecord
137+
{
138+
protected static string $resource = SiteSearchConfigResource::class;
139+
140+
protected static string $view = 'filament.resources.site-search-config.view';
141+
142+
#[Url]
143+
public string $query = '';
144+
145+
public function infolist(Infolist $infolist): Infolist
146+
{
147+
return $infolist
148+
->schema([
149+
Section::make('General')
150+
->columns(3)
151+
->schema([
152+
TextEntry::make('name'),
153+
TextEntry::make('crawl_url')->label('Crawl URL'),
154+
TextEntry::make('index_name')->label('Index name'),
155+
TextEntry::make('status')
156+
->badge()
157+
->getStateUsing(fn (SiteSearchConfig $record) => SiteSearchConfigResource::getStatus($record)),
158+
TextEntry::make('driver_class')
159+
->label('Driver')
160+
->default('Default'),
161+
TextEntry::make('profile_class')
162+
->label('Profile')
163+
->default('Default'),
164+
]),
165+
Section::make('Crawl statistics')
166+
->columns(3)
167+
->schema([
168+
TextEntry::make('document_count')->label('Documents'),
169+
TextEntry::make('number_of_urls_indexed')->label('URLs indexed'),
170+
TextEntry::make('urls_found')->label('URLs found'),
171+
TextEntry::make('urls_failed')->label('URLs failed'),
172+
TextEntry::make('finish_reason')->label('Finish reason'),
173+
TextEntry::make('crawling_started_at')->label('Crawl started')->dateTime(),
174+
TextEntry::make('crawling_ended_at')->label('Crawl ended')->dateTime(),
175+
]),
176+
]);
177+
}
178+
179+
public function getSearchResults(): ?SearchResults
180+
{
181+
if (blank($this->query)) {
182+
return null;
183+
}
184+
185+
$config = $this->record;
186+
187+
if (! $config->index_name) {
188+
return null;
189+
}
190+
191+
return $config->getDriver()->search($config->index_name, $this->query);
192+
}
193+
}
194+
```
195+
196+
## The view template
197+
198+
Create a Blade view at `resources/views/filament/resources/site-search-config/view.blade.php`.
199+
200+
```blade
201+
<x-filament-panels::page>
202+
{{ $this->infolistAction }}
203+
204+
<x-filament::section heading="Search">
205+
<div class="space-y-4">
206+
<x-filament::input.wrapper>
207+
<x-filament::input
208+
type="text"
209+
wire:model.live.debounce.300ms="query"
210+
placeholder="Search this index..."
211+
/>
212+
</x-filament::input.wrapper>
213+
214+
@if ($searchResults = $this->getSearchResults())
215+
<p class="text-sm text-gray-500 dark:text-gray-400">
216+
{{ $searchResults->totalCount }} results in {{ $searchResults->processingTimeInMs }}ms
217+
</p>
218+
219+
<div class="space-y-3">
220+
@foreach ($searchResults->hits as $hit)
221+
<div class="rounded-lg border border-gray-200 p-4 dark:border-gray-700">
222+
<a
223+
href="{{ $hit->urlWithAnchor() }}"
224+
target="_blank"
225+
class="font-medium text-primary-600 hover:underline dark:text-primary-400"
226+
>
227+
{{ $hit->title() }}
228+
</a>
229+
230+
@if ($hit->snippet())
231+
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
232+
{!! $hit->highlightedSnippet() ?? e($hit->snippet()) !!}
233+
</p>
234+
@endif
235+
236+
<p class="mt-1 text-xs text-gray-400">
237+
{{ $hit->url }}
238+
@if ($hit->dateModified())
239+
&middot; {{ $hit->dateModified()->diffForHumans() }}
240+
@endif
241+
</p>
242+
</div>
243+
@endforeach
244+
</div>
245+
@elseif (filled($this->query))
246+
<p class="text-sm text-gray-500 dark:text-gray-400">No results found.</p>
247+
@endif
248+
</div>
249+
</x-filament::section>
250+
</x-filament-panels::page>
251+
```
252+
253+
These examples give you a working starting point. You can customize the columns, layout, and search result rendering to fit your application.

0 commit comments

Comments
 (0)