Skip to content

Commit

Permalink
custom export function
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Becker committed Nov 15, 2024
1 parent 37413a2 commit 06c65f4
Show file tree
Hide file tree
Showing 7 changed files with 401 additions and 125 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"prefer-stable": true,
"require": {
"php" : "^8.0",
"spatie/laravel-package-tools": "^1.0|^1.13"
"spatie/laravel-package-tools": "^1.0|^1.13",
"maatwebsite/excel": "^3.1"
}
}
186 changes: 186 additions & 0 deletions src/Actions/ReportExportAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

namespace Wjbecker\FilamentReportBuilder\Actions;

use AnourValar\EloquentSerialize\Facades\EloquentSerializeFacade;
use Filament\Actions\Action;
use Filament\Actions\Concerns\CanExportRecords;
use Filament\Actions\ExportAction;
use Filament\Actions\Exports\Enums\ExportFormat;
use Filament\Actions\Exports\ExportColumn;
use Filament\Actions\Exports\Jobs\CreateXlsxFile;
use Filament\Actions\Exports\Jobs\ExportCompletion;
use Filament\Actions\Exports\Models\Export;
use Filament\Notifications\Notification;
use Filament\Tables\Actions\ExportAction as ExportTableAction;
use Filament\Tables\Actions\ExportBulkAction as ExportTableBulkAction;
use Filament\Tables\Contracts\HasTable;
use Illuminate\Bus\PendingBatch;
use Illuminate\Foundation\Bus\PendingChain;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Number;
use Illuminate\Support\Str;
use Livewire\Component;

class ReportExportAction extends Action
{
use CanExportRecords;

protected function setUp(): void
{
parent::setUp();

$this->action(function (ReportExportAction | ExportAction | ExportTableAction | ExportTableBulkAction $action, array $data, Component $livewire) {
$exporter = $action->getExporter();

if ($livewire instanceof HasTable) {
$query = $livewire->getTableQueryForExport();
} else {
$query = $exporter::getModel()::query();
}

$query = $exporter::modifyQuery($query);

if ($this->modifyQueryUsing) {
$query = $this->evaluate($this->modifyQueryUsing, [
'query' => $query,
]) ?? $query;
}

$records = $action instanceof ExportTableBulkAction ? $action->getRecords() : null;

$totalRows = $records ? $records->count() : $query->count();
$maxRows = $action->getMaxRows() ?? $totalRows;

if ($maxRows < $totalRows) {
Notification::make()
->title(__('filament-actions::export.notifications.max_rows.title'))
->body(trans_choice('filament-actions::export.notifications.max_rows.body', $maxRows, [
'count' => Number::format($maxRows),
]))
->danger()
->send();

return;
}

$user = auth()->user();

$options = array_merge(
$action->getOptions(),
Arr::except($data, ['columnMap']),
);

if ($action->hasColumnMapping()) {
$columnMap = collect($data['columnMap'])
->dot()
->reduce(fn (Collection $carry, mixed $value, string $key): Collection => $carry->mergeRecursive([
Str::beforeLast($key, '.') => [Str::afterLast($key, '.') => $value],
]), collect())
->filter(fn (array $column): bool => $column['isEnabled'] ?? false)
->mapWithKeys(fn (array $column, string $columnName): array => [$columnName => $column['label']])
->all();
} else {
$columnMap = collect($exporter::getColumns($this->record))
->mapWithKeys(fn (ExportColumn $column): array => [$column->getName() => $column->getLabel()])
->all();
}

$export = app(Export::class);
$export->user()->associate($user);
$export->exporter = $exporter;
$export->total_rows = $totalRows;

$exporter = $export->getExporter(
columnMap: $columnMap,
options: $options,
);

$export->file_disk = $action->getFileDisk() ?? $exporter->getFileDisk();
$export->save();

$export->file_name = $action->getFileName($export) ?? $exporter->getFileName($export);
$export->save();

$formats = $action->getFormats() ?? $exporter->getFormats();
$hasCsv = in_array(ExportFormat::Csv, $formats);
$hasXlsx = in_array(ExportFormat::Xlsx, $formats);

$serializedQuery = EloquentSerializeFacade::serialize($query);

$job = $action->getJob();
$jobQueue = $exporter->getJobQueue();
$jobConnection = $exporter->getJobConnection();
$jobBatchName = $exporter->getJobBatchName();

// We do not want to send the loaded user relationship to the queue in job payloads,
// in case it contains attributes that are not serializable, such as binary columns.
$export->unsetRelation('user');

$makeCreateXlsxFileJob = fn (): CreateXlsxFile => app(CreateXlsxFile::class, [
'export' => $export,
'columnMap' => $columnMap,
'options' => $options,
]);

Bus::chain([
Bus::batch([new $job(
$export,
query: $serializedQuery,
columnMap: $columnMap,
options: $options,
chunkSize: $action->getChunkSize(),
records: $action instanceof ExportTableBulkAction ? $action->getRecords()->all() : null,
)])
->when(
filled($jobQueue),
fn (PendingBatch $batch) => $batch->onQueue($jobQueue),
)
->when(
filled($jobConnection),
fn (PendingBatch $batch) => $batch->onConnection($jobConnection),
)
->when(
filled($jobBatchName),
fn (PendingBatch $batch) => $batch->name($jobBatchName),
)
->allowFailures(),
...(($hasXlsx && (! $hasCsv)) ? [$makeCreateXlsxFileJob()] : []),
app(ExportCompletion::class, [
'export' => $export,
'columnMap' => $columnMap,
'formats' => $formats,
'options' => $options,
]),
...(($hasXlsx && $hasCsv) ? [$makeCreateXlsxFileJob()] : []),
])
->when(
filled($jobQueue),
fn (PendingChain $chain) => $chain->onQueue($jobQueue),
)
->when(
filled($jobConnection),
fn (PendingChain $chain) => $chain->onConnection($jobConnection),
)
->dispatch();

Notification::make()
->title($action->getSuccessNotificationTitle())
->body(trans_choice('filament-actions::export.notifications.started.body', $export->total_rows, [
'count' => Number::format($export->total_rows),
]))
->duration(2000)
->success()
->send();
});
}

public function record($record): static
{
$this->record = $record;

return $this;
}
}
68 changes: 68 additions & 0 deletions src/Exports/Jobs/ExportCsv.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Wjbecker\FilamentReportBuilder\Exports\Jobs;

use AnourValar\EloquentSerialize\Facades\EloquentSerializeFacade;
use Filament\Actions\Exports\Jobs\ExportCsv as FilamentExportCsv;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Filesystem\Filesystem;
use League\Csv\Writer;
use SplTempFileObject;
use Throwable;

class ExportCsv extends FilamentExportCsv
{
public function handle(): void
{
/** @var Authenticatable $user */
$user = $this->export->user;

auth()->login($user);

$exceptions = [];

$processedRows = 0;
$successfulRows = 0;

$csv = Writer::createFromFileObject(new SplTempFileObject());
$csv->setDelimiter($this->exporter::getCsvDelimiter());

$query = EloquentSerializeFacade::unserialize($this->query);

// foreach ($this->exporter->getCachedColumns() as $column) {
// $column->applyRelationshipAggregates($query);
// $column->applyEagerLoading($query);
// }

foreach ($query->find($this->records) as $record) {
try {
$csv->insertOne(($this->exporter)($record));

$successfulRows++;
} catch (Throwable $exception) {
$exceptions[$exception::class] = $exception;
}

$processedRows++;
}

$filePath = $this->export->getFileDirectory() . DIRECTORY_SEPARATOR . str_pad(strval($this->page), 16, '0', STR_PAD_LEFT) . '.csv';
$this->export->getFileDisk()->put($filePath, $csv->toString(), Filesystem::VISIBILITY_PRIVATE);

$this->export->refresh();

$exportProcessedRows = $this->export->processed_rows + $processedRows;
$this->export->processed_rows = ($exportProcessedRows < $this->export->total_rows) ?
$exportProcessedRows :
$this->export->total_rows;

$exportSuccessfulRows = $this->export->successful_rows + $successfulRows;
$this->export->successful_rows = ($exportSuccessfulRows < $this->export->total_rows) ?
$exportSuccessfulRows :
$this->export->total_rows;

$this->export->save();

$this->handleExceptions($exceptions);
}
}
12 changes: 12 additions & 0 deletions src/Exports/Jobs/PrepareCsvExport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Wjbecker\FilamentReportBuilder\Exports\Jobs;

use Filament\Actions\Exports\Jobs\PrepareCsvExport AS FilamentPrepareCsvExport;
class PrepareCsvExport extends FilamentPrepareCsvExport
{
public function getExportCsvJob(): string
{
return ExportCsv::class;
}
}
7 changes: 1 addition & 6 deletions src/Exports/ReportExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@

namespace Wjbecker\FilamentReportBuilder\Exports;

use Wjbecker\FilamentReportBuilder\Models\Report;
use Carbon\Carbon;
use Filament\Actions\Exports\ExportColumn;
use Filament\Actions\Exports\Exporter;
use Filament\Actions\Exports\Models\Export;
use Illuminate\Support\Str;

class ReportExporter extends Exporter
{
public static function getColumns(): array
public static function getColumns($report = null): array
{
$id = Str::of(url()->previous())->between('reports/', '/view')->toInteger();
$report = Report::find($id);

$columns = [];
foreach ($report->data['columns'] as $header) {
$data = json_decode($header['column_data']);
Expand Down
Loading

0 comments on commit 06c65f4

Please sign in to comment.