Skip to content
Open
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
18 changes: 3 additions & 15 deletions packages/actions/src/Exports/Downloaders/CsvDownloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,9 @@ public function __invoke(Export $export): StreamedResponse
abort(404);
}

return response()->streamDownload(function () use ($disk, $directory): void {
echo $disk->get($directory . DIRECTORY_SEPARATOR . 'headers.csv');

flush();

foreach ($disk->files($directory) as $file) {
if (str($file)->endsWith('headers.csv')) {
continue;
}

if (! str($file)->endsWith('.csv')) {
continue;
}

echo $disk->get($file);
return response()->streamDownload(function () use ($export): void {
foreach (app(CsvExportContent::class)($export) as $chunk) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the creation of the content to a separate class so that someone can extend the CSVDownloader/XsxDownloader and only override the invoke method, without having to touch the actual creation of the rows

echo $chunk;

flush();
}
Expand Down
32 changes: 32 additions & 0 deletions packages/actions/src/Exports/Downloaders/CsvExportContent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Filament\Actions\Exports\Downloaders;

use Filament\Actions\Exports\Models\Export;
use Generator;

class CsvExportContent
{
/**
* @return Generator<string>
*/
public function __invoke(Export $export): Generator
{
$disk = $export->getFileDisk();
$directory = $export->getFileDirectory();

yield $disk->get($directory . DIRECTORY_SEPARATOR . 'headers.csv');

foreach ($disk->files($directory) as $file) {
if (str($file)->endsWith('headers.csv')) {
continue;
}

if (! str($file)->endsWith('.csv')) {
continue;
}

yield $disk->get($file);
}
}
}
31 changes: 2 additions & 29 deletions packages/actions/src/Exports/Downloaders/XlsxDownloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

use Filament\Actions\Exports\Downloaders\Contracts\Downloader;
use Filament\Actions\Exports\Models\Export;
use League\Csv\Reader as CsvReader;
use League\Csv\Statement;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Writer\XLSX\Writer;
use Symfony\Component\HttpFoundation\StreamedResponse;

Expand Down Expand Up @@ -37,34 +34,10 @@ public function __invoke(Export $export): StreamedResponse

$writer = app(Writer::class);

$csvDelimiter = $export->exporter::getCsvDelimiter();

$writeRowsFromFile = function (string $file) use ($csvDelimiter, $disk, $writer): void {
$csvReader = CsvReader::from($disk->readStream($file));
$csvReader->setDelimiter($csvDelimiter);
$csvResults = (new Statement)->process($csvReader);

foreach ($csvResults->getRecords() as $row) {
$writer->addRow(Row::fromValues($row));
}
};

return response()->streamDownload(function () use ($disk, $directory, $fileName, $writer, $writeRowsFromFile): void {
return response()->streamDownload(function () use ($export, $fileName, $writer): void {
$writer->openToBrowser($fileName);

$writeRowsFromFile($directory . DIRECTORY_SEPARATOR . 'headers.csv');

foreach ($disk->files($directory) as $file) {
if (str($file)->endsWith('headers.csv')) {
continue;
}

if (! str($file)->endsWith('.csv')) {
continue;
}

$writeRowsFromFile($file);
}
app(XlsxExportContent::class)($export, $writer);

$writer->close();
}, $fileName, [
Expand Down
43 changes: 43 additions & 0 deletions packages/actions/src/Exports/Downloaders/XlsxExportContent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Filament\Actions\Exports\Downloaders;

use Filament\Actions\Exports\Models\Export;
use League\Csv\Reader as CsvReader;
use League\Csv\Statement;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Writer\XLSX\Writer;

class XlsxExportContent
{
public function __invoke(Export $export, Writer $writer): void
{
$disk = $export->getFileDisk();
$directory = $export->getFileDirectory();
$csvDelimiter = $export->exporter::getCsvDelimiter();

$writeRowsFromFile = function (string $file) use ($csvDelimiter, $disk, $writer): void {
$csvReader = CsvReader::from($disk->readStream($file));
$csvReader->setDelimiter($csvDelimiter);
$csvResults = (new Statement)->process($csvReader);

foreach ($csvResults->getRecords() as $row) {
$writer->addRow(Row::fromValues($row));
}
};

$writeRowsFromFile($directory . DIRECTORY_SEPARATOR . 'headers.csv');

foreach ($disk->files($directory) as $file) {
if (str($file)->endsWith('headers.csv')) {
continue;
}

if (! str($file)->endsWith('.csv')) {
continue;
}

$writeRowsFromFile($file);
}
}
}
6 changes: 6 additions & 0 deletions packages/actions/src/Exports/Exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Carbon\CarbonInterface;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\Exports\Downloaders\Contracts\Downloader;
use Filament\Actions\Exports\Enums\Contracts\ExportFormat as ExportFormatInterface;
use Filament\Actions\Exports\Enums\ExportFormat;
use Filament\Actions\Exports\Models\Export;
Expand Down Expand Up @@ -222,6 +223,11 @@ public function getFormats(): array
return [ExportFormat::Csv, ExportFormat::Xlsx];
}

public static function getDownloader(ExportFormatInterface $format): Downloader

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allows someone to specify their own downloader depending on the Formatter used

{
return $format->getDownloader();
}

public function getXlsxCellStyle(): ?Style
{
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function __invoke(Request $request, Export $export): StreamedResponse

abort_unless($format !== null, 404);

return $format->getDownloader()($export);
return $export->exporter::getDownloader($format)($export);
}

protected function resolveFormatFromRequest(Request $request): ?ExportFormatInterface
Expand Down
68 changes: 66 additions & 2 deletions tests/src/Actions/Exports/Http/Controllers/DownloadExportTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<?php

use Filament\Actions\Exports\Downloaders\Contracts\Downloader;
use Filament\Actions\Exports\Enums\Contracts\ExportFormat as ExportFormatInterface;
use Filament\Actions\Exports\ExportColumn;
use Filament\Actions\Exports\Exporter;
use Filament\Actions\Exports\Models\Export;
use Filament\Tests\Fixtures\Models\User;
use Filament\Tests\TestCase;
Expand All @@ -8,6 +12,7 @@
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Symfony\Component\HttpFoundation\StreamedResponse;

uses(TestCase::class, RefreshDatabase::class);

Expand All @@ -29,12 +34,57 @@ public function view(Authenticatable $user, Export $export): bool
}
}

function createExportForOwner(User $owner): Export
class TestDownloadExportExporter extends Exporter
{
public static function getColumns(): array
{
return [
ExportColumn::make('name'),
];
}

public static function getCompletedNotificationBody(Export $export): string
{
return 'Export completed';
}
}

class TestCustomDownloader implements Downloader
{
public function __invoke(Export $export): StreamedResponse
{
return response()->streamDownload(function (): void {
echo 'custom downloader content';
}, 'custom.csv');
}
}

class TestCustomDownloaderExporter extends Exporter
{
public static function getColumns(): array
{
return [
ExportColumn::make('name'),
];
}

public static function getCompletedNotificationBody(Export $export): string
{
return 'Export completed';
}

public static function getDownloader(ExportFormatInterface $format): Downloader
{
return app(TestCustomDownloader::class);
}
}

function createExportForOwner(User $owner, string $exporter = TestDownloadExportExporter::class): Export
{
return Export::create([
'file_disk' => 'local',
'file_name' => 'export',
'exporter' => 'App\\Filament\\Exports\\TestExporter',
'exporter' => $exporter,
'total_rows' => 1,
'successful_rows' => 1,
'user_id' => $owner->getKey(),
Expand Down Expand Up @@ -131,3 +181,17 @@ function fakeExportFile(Export $export): void
->get(signedExportDownloadUrl($export, format: 'unknown'))
->assertStatus(404);
});

it('uses the exporter\'s `getDownloader()` override instead of the format\'s default downloader', function (): void {
$owner = User::factory()->create();

$export = createExportForOwner($owner, exporter: TestCustomDownloaderExporter::class);

fakeExportFile($export);

$response = $this->actingAs($owner)
->get(signedExportDownloadUrl($export))
->assertStatus(200);

expect($response->streamedContent())->toBe('custom downloader content');
});