Skip to content

Commit 239cb12

Browse files
fix: use dehydrated settings for query builder constraints (#20180)
* fix: use dehydrated settings for query builder constraints * fix: apply query builder constraints using state snapshots * test: assert datetime constraint summaries use the Filament timezone * Update QueryBuilderTest.php --------- Co-authored-by: Dan Harrin <git@danharrin.com>
1 parent f97c7c7 commit 239cb12

2 files changed

Lines changed: 120 additions & 3 deletions

File tree

packages/tables/src/Filters/QueryBuilder.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public function getRuleSummaries(array $rules, RuleBuilder $ruleBuilder, int $it
138138
function (Operator $operator) use ($ruleIndex, &$summaries): void {
139139
$summaries[$ruleIndex] = $operator->getSummary();
140140
},
141+
shouldUseRawSettings: true,
141142
);
142143
}
143144

@@ -441,7 +442,7 @@ protected function getNestedRuleBuilder(Schema $schema, string $orGroupIndex): R
441442
/**
442443
* @param array<string, mixed> $rule
443444
*/
444-
protected function tapOperatorFromRule(array $rule, Schema $schema, Closure $callback): void
445+
protected function tapOperatorFromRule(array $rule, Schema $schema, Closure $callback, bool $shouldUseRawSettings = false): void
445446
{
446447
$constraint = $this->getConstraint($rule['type']);
447448

@@ -467,13 +468,15 @@ protected function tapOperatorFromRule(array $rule, Schema $schema, Closure $cal
467468
return;
468469
}
469470

471+
$settings = $shouldUseRawSettings ? $rule['data']['settings'] : ($schema->getStateSnapshot()['settings'] ?? []);
472+
470473
$constraint
471-
->settings($rule['data']['settings'])
474+
->settings($settings)
472475
->inverse($isInverseOperator);
473476

474477
$operator
475478
->constraint($constraint)
476-
->settings($rule['data']['settings'])
479+
->settings($settings)
477480
->inverse($isInverseOperator);
478481

479482
$callback($operator);

tests/src/Tables/Filters/QueryBuilderTest.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Filament\QueryBuilder\Constraints\SelectConstraint;
1919
use Filament\QueryBuilder\Constraints\SelectConstraint\Operators\IsOperator as SelectIsOperator;
2020
use Filament\QueryBuilder\Forms\Components\RuleBuilder;
21+
use Filament\Support\Facades\FilamentTimezone;
2122
use Filament\Tables\Filters\QueryBuilder;
2223
use Filament\Tables\Filters\QueryBuilder\Constraints\TextConstraint;
2324
use Filament\Tests\Fixtures\Livewire\PostsQueryBuilderTable;
@@ -3806,6 +3807,119 @@ function applyQueryBuilderFilter(array $rules)
38063807
->assertCanNotSeeTableRecords([$beforeThresholdPost]);
38073808
});
38083809

3810+
it('can filter records using datetime constraint with is after operator when the Filament timezone differs from the app timezone', function (): void {
3811+
config(['app.timezone' => 'UTC']);
3812+
FilamentTimezone::set('America/New_York');
3813+
3814+
$beforeThresholdPost = Post::factory()->create([
3815+
'published_at' => '2026-07-13 15:00:00',
3816+
]);
3817+
3818+
$afterThresholdPost = Post::factory()->create([
3819+
'published_at' => '2026-07-13 17:00:00',
3820+
]);
3821+
3822+
livewire(PostsQueryBuilderTable::class)
3823+
->assertCanSeeTableRecords([$beforeThresholdPost, $afterThresholdPost])
3824+
->tap(applyQueryBuilderFilter([
3825+
[
3826+
'type' => 'published_at',
3827+
'data' => [
3828+
'operator' => 'isAfter',
3829+
'settings' => [
3830+
'mode' => 'absolute',
3831+
'date' => '2026-07-13 12:00:00',
3832+
],
3833+
],
3834+
],
3835+
]))
3836+
->assertCanSeeTableRecords([$afterThresholdPost])
3837+
->assertCanNotSeeTableRecords([$beforeThresholdPost])
3838+
->assertSee('Published at is after Mon, Jul 13, 2026 12:00:00');
3839+
});
3840+
3841+
it('can filter records using datetime constraint with is before operator when the Filament timezone differs from the app timezone', function (): void {
3842+
config(['app.timezone' => 'UTC']);
3843+
FilamentTimezone::set('America/New_York');
3844+
3845+
// The cutoff of `12:00:00` is entered in the Filament timezone (`America/New_York`), so it
3846+
// must be dehydrated to `16:00:00` in the app timezone (`UTC`) before querying, while the
3847+
// summary continues to display the raw `12:00:00` the user entered.
3848+
$beforeThresholdPost = Post::factory()->create([
3849+
'published_at' => '2026-07-13 15:00:00',
3850+
]);
3851+
3852+
$afterThresholdPost = Post::factory()->create([
3853+
'published_at' => '2026-07-13 17:00:00',
3854+
]);
3855+
3856+
livewire(PostsQueryBuilderTable::class)
3857+
->assertCanSeeTableRecords([$beforeThresholdPost, $afterThresholdPost])
3858+
->tap(applyQueryBuilderFilter([
3859+
[
3860+
'type' => 'published_at',
3861+
'data' => [
3862+
'operator' => 'isBefore',
3863+
'settings' => [
3864+
'mode' => 'absolute',
3865+
'date' => '2026-07-13 12:00:00',
3866+
],
3867+
],
3868+
],
3869+
]))
3870+
->assertCanSeeTableRecords([$beforeThresholdPost])
3871+
->assertCanNotSeeTableRecords([$afterThresholdPost])
3872+
->assertSee('Published at is before Mon, Jul 13, 2026 12:00:00');
3873+
});
3874+
3875+
it('applies datetime constraints and summaries from the applied state, not unapplied deferred edits', function (): void {
3876+
$earlyPost = Post::factory()->create([
3877+
'published_at' => '2026-07-13 10:00:00',
3878+
]);
3879+
3880+
$latePost = Post::factory()->create([
3881+
'published_at' => '2026-07-13 20:00:00',
3882+
]);
3883+
3884+
livewire(PostsQueryBuilderTable::class)
3885+
->assertCanSeeTableRecords([$earlyPost, $latePost])
3886+
// Apply a rule that only matches the late post.
3887+
->tap(applyQueryBuilderFilter([
3888+
[
3889+
'type' => 'published_at',
3890+
'data' => [
3891+
'operator' => 'isAfter',
3892+
'settings' => [
3893+
'mode' => 'absolute',
3894+
'date' => '2026-07-13 15:00:00',
3895+
],
3896+
],
3897+
],
3898+
]))
3899+
->assertCanSeeTableRecords([$latePost])
3900+
->assertCanNotSeeTableRecords([$earlyPost])
3901+
->assertSee('Published at is after Mon, Jul 13, 2026 15:00:00')
3902+
// Edit the deferred form to a looser threshold that would match both posts, *without* applying it.
3903+
->set('tableDeferredFilters.query_builder.rules', [
3904+
[
3905+
'type' => 'published_at',
3906+
'data' => [
3907+
'operator' => 'isAfter',
3908+
'settings' => [
3909+
'mode' => 'absolute',
3910+
'date' => '2026-07-13 05:00:00',
3911+
],
3912+
],
3913+
],
3914+
])
3915+
// The query and the summary must still reflect the applied threshold (`15:00:00`),
3916+
// not the unapplied deferred edit (`05:00:00`).
3917+
->assertCanSeeTableRecords([$latePost])
3918+
->assertCanNotSeeTableRecords([$earlyPost])
3919+
->assertSee('Published at is after Mon, Jul 13, 2026 15:00:00')
3920+
->assertDontSee('Published at is after Mon, Jul 13, 2026 05:00:00');
3921+
});
3922+
38093923
it('can filter records using datetime constraint with is after operator with `this_minute` preset', function (): void {
38103924
$currentMinutePosts = Post::factory()->count(3)->create([
38113925
'published_at' => now()->startOfMinute()->addSeconds(30),

0 commit comments

Comments
 (0)