From 5f2952b62b1aef923570d7d3ba15725d2709b5ad Mon Sep 17 00:00:00 2001 From: Johannes Werner Date: Tue, 5 May 2026 14:38:35 +0200 Subject: [PATCH 1/2] Fix export sorting for joined alias columns Export queries were resolving sort fields differently from the normal table datasource query. Unqualified sort fields were always prefixed with the root table, so sorting an exported joined/aliased column like `nom_complet` produced `lieu.nom_complet` instead of `nom_complet`. Add a shared sort-field resolver and use it in both datasource sorting and export paths, including queued exports. This keeps qualified fields, table-prefix behavior, and explicit export query options intact. Also add regression coverage for preparing export data sorted by a joined alias column and normalize CSV line endings in the export test helper. --- src/Concerns/Sorting.php | 9 ++++ .../Processors/Database/Pipelines/Sorting.php | 14 +----- src/Traits/ExportableJob.php | 7 ++- src/Traits/WithExport.php | 3 +- tests/Feature/ExportTest.php | 47 ++++++++++++++++++- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/Concerns/Sorting.php b/src/Concerns/Sorting.php index 9fb46abc..5784b75b 100644 --- a/src/Concerns/Sorting.php +++ b/src/Concerns/Sorting.php @@ -116,6 +116,15 @@ public function updatedSortField(): void } } + public function resolveSortField(string $sortField): string + { + if (str_contains($sortField, '.') || $this->ignoreTablePrefix) { + return $sortField; + } + + return $this->currentTable.'.'.$sortField; + } + /** * Get the sort callback for a given field from the columns definition. * Returns null if no custom callback is defined. diff --git a/src/DataSource/Processors/Database/Pipelines/Sorting.php b/src/DataSource/Processors/Database/Pipelines/Sorting.php index da99480b..042f18d0 100644 --- a/src/DataSource/Processors/Database/Pipelines/Sorting.php +++ b/src/DataSource/Processors/Database/Pipelines/Sorting.php @@ -6,7 +6,6 @@ use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Database\Query\Builder as QueryBuilder; -use Illuminate\Support\Str; use PowerComponents\LivewirePowerGrid\PowerGridComponent; class Sorting @@ -40,7 +39,7 @@ private function applySingleSort(EloquentBuilder|MorphToMany|QueryBuilder $query return; } - $query->orderBy($this->makeSortField($sortField), $direction); + $query->orderBy($this->component->resolveSortField($sortField), $direction); } private function applyMultipleSort(EloquentBuilder|MorphToMany|QueryBuilder $results): void @@ -54,16 +53,7 @@ private function applyMultipleSort(EloquentBuilder|MorphToMany|QueryBuilder $res continue; } - $results->orderBy($this->makeSortField($sortField), $direction); + $results->orderBy($this->component->resolveSortField($sortField), $direction); } } - - private function makeSortField(string $sortField): string - { - if (Str::of($sortField)->contains('.') || $this->component->ignoreTablePrefix) { - return $sortField; - } - - return $this->component->currentTable.'.'.$sortField; - } } diff --git a/src/Traits/ExportableJob.php b/src/Traits/ExportableJob.php index 8fda8b8b..674f84bc 100644 --- a/src/Traits/ExportableJob.php +++ b/src/Traits/ExportableJob.php @@ -52,6 +52,7 @@ private function prepareToExport(array $properties = []): Eloquent\Collection|Co $filtered = $processDataSource->component->filtered ?? []; $currentTable = $processDataSource->component->currentTable; + $queryOptions = data_get($this->exportable, 'queryOptions', []); $property = function (string $property) use ($processDataSource, $currentTable) { $property = $processDataSource->component->{$property}; @@ -61,6 +62,10 @@ private function prepareToExport(array $properties = []): Eloquent\Collection|Co : $currentTable.'.'.$property; }; + $sortField = $queryOptions['sortField'] + ?? $processDataSource->component->resolveSortField($processDataSource->component->sortField); + $sortDirection = $queryOptions['sortDirection'] ?? $processDataSource->component->sortDirection; + $results = $processDataSource->datasource ->where(function ($query) { app()->makeWith(SearchHandlerContract::class, [ @@ -73,7 +78,7 @@ private function prepareToExport(array $properties = []): Eloquent\Collection|Co }) ->offset($this->offset) ->limit($this->limit) - ->orderBy($property('sortField'), $processDataSource->component->sortDirection) + ->orderBy($sortField, $sortDirection) ->get(); $dataTransformer = new DataTransformer($processDataSource->component); diff --git a/src/Traits/WithExport.php b/src/Traits/WithExport.php index b8a0f02f..6f9175a1 100644 --- a/src/Traits/WithExport.php +++ b/src/Traits/WithExport.php @@ -206,7 +206,8 @@ public function prepareToExport(bool $selected = false): Eloquent\Collection|Col return $query->whereIn($property('primaryKey'), $filtered); }) ->when($this->sortField, function ($query) use ($property, $processDataSource, $queryOptions) { - $sortField = $queryOptions['sortField'] ?? $property('sortField'); + $sortField = $queryOptions['sortField'] + ?? $processDataSource->component->resolveSortField($processDataSource->component->sortField); $sortDirection = $queryOptions['sortDirection'] ?? $processDataSource->component->sortDirection; return $query->orderBy($sortField, $sortDirection); diff --git a/tests/Feature/ExportTest.php b/tests/Feature/ExportTest.php index ed11d533..56bb603d 100644 --- a/tests/Feature/ExportTest.php +++ b/tests/Feature/ExportTest.php @@ -1,5 +1,6 @@ [$exportWithHtml::class], ]); +$exportWithJoinedAliasSort = new class() extends ExportTable +{ + public function datasource(): \Illuminate\Database\Eloquent\Builder + { + return parent::datasource() + ->join('categories', function ($categories) { + $categories->on('dishes.category_id', '=', 'categories.id'); + }) + ->select('dishes.*', DB::raw('categories.name as category_name')); + } + + public function fields(): PowerGridFields + { + return PowerGrid::fields() + ->add('category_name'); + } + + public function columns(): array + { + return [ + Column::add() + ->title('Category') + ->field('category_name') + ->sortable(), + ]; + } +}; + +it('properly prepares export data sorted by a joined alias column', function (string $component) { + $component = livewire($component) + ->set('checkboxValues', [ + 0 => '1', + 1 => '3', + ]) + ->call('sortBy', 'category_name'); + + expect($component->instance()->prepareToExport(true)->pluck('category_name')->values()->all()) + ->toBe(['Carnes', 'Sobremesas']); +})->with('export_with_joined_alias_sort'); + +dataset('export_with_joined_alias_sort', [ + 'joined alias' => [$exportWithJoinedAliasSort::class], +]); + $exportWithQueryOptions = new class() extends ExportTable { public string $testSortField = ''; @@ -302,7 +347,7 @@ public function columns(): array data_get($downloadEffect, 'name') ); - $content = str_replace(PHP_EOL, '', base64_decode(data_get($downloadEffect, 'content'))); + $content = str_replace(["\r\n", "\n", "\r"], '', base64_decode(data_get($downloadEffect, 'content'))); $expected = collect(array_merge([$headings], $rows)) ->transform(function ($heading) use ($delimiter, $separator) { From 6b2cdb1f55d1c6d03a1e68234f9a4c8ab65ffcc9 Mon Sep 17 00:00:00 2001 From: Johannes Werner Date: Tue, 12 May 2026 08:35:29 +0200 Subject: [PATCH 2/2] Pint and PHPStan --- src/Traits/ExportableJob.php | 11 ++++++++++- src/Traits/WithExport.php | 2 +- tests/Feature/ExportTest.php | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Traits/ExportableJob.php b/src/Traits/ExportableJob.php index 674f84bc..e289270e 100644 --- a/src/Traits/ExportableJob.php +++ b/src/Traits/ExportableJob.php @@ -52,8 +52,15 @@ private function prepareToExport(array $properties = []): Eloquent\Collection|Co $filtered = $processDataSource->component->filtered ?? []; $currentTable = $processDataSource->component->currentTable; + + /** @var array{sortField?: string, sortDirection?: string} $queryOptions */ $queryOptions = data_get($this->exportable, 'queryOptions', []); + // data_get's default only applies when the key is missing, so guard against malformed query options. + if (! is_array($queryOptions)) { + $queryOptions = []; + } + $property = function (string $property) use ($processDataSource, $currentTable) { $property = $processDataSource->component->{$property}; @@ -64,7 +71,9 @@ private function prepareToExport(array $properties = []): Eloquent\Collection|Co $sortField = $queryOptions['sortField'] ?? $processDataSource->component->resolveSortField($processDataSource->component->sortField); - $sortDirection = $queryOptions['sortDirection'] ?? $processDataSource->component->sortDirection; + + $sortDirection = $queryOptions['sortDirection'] + ?? $processDataSource->component->sortDirection; $results = $processDataSource->datasource ->where(function ($query) { diff --git a/src/Traits/WithExport.php b/src/Traits/WithExport.php index 6f9175a1..3faf15f2 100644 --- a/src/Traits/WithExport.php +++ b/src/Traits/WithExport.php @@ -205,7 +205,7 @@ public function prepareToExport(bool $selected = false): Eloquent\Collection|Col ->when($filtered, function ($query, $filtered) use ($property) { return $query->whereIn($property('primaryKey'), $filtered); }) - ->when($this->sortField, function ($query) use ($property, $processDataSource, $queryOptions) { + ->when($this->sortField, function ($query) use ($processDataSource, $queryOptions) { $sortField = $queryOptions['sortField'] ?? $processDataSource->component->resolveSortField($processDataSource->component->sortField); $sortDirection = $queryOptions['sortDirection'] ?? $processDataSource->component->sortDirection; diff --git a/tests/Feature/ExportTest.php b/tests/Feature/ExportTest.php index 56bb603d..253fa190 100644 --- a/tests/Feature/ExportTest.php +++ b/tests/Feature/ExportTest.php @@ -1,5 +1,6 @@ join('categories', function ($categories) {