Skip to content

Commit ad70261

Browse files
Job Batching (#16)
* Batch import jobs * Sometimes, we won't have a batch ID * Fix styling * Get a database working in tests. * Fix styling * Translate this string. * Add `php artisan migrate` to the addon's install steps * fix indentation * Revert "Add `php artisan migrate` to the addon's install steps" This reverts commit 0ffa848. * Attempt to run migrations automatically, if possible. * wip * Fix styling * wip * Fix styling * wip * Fix styling * wip * wip * Enough now... make the tests pass. * Simplify. * Revert "Simplify." This reverts commit 1d66605. * tweak messaging --------- Co-authored-by: duncanmcclean <[email protected]>
1 parent 32808fb commit ad70261

File tree

12 files changed

+113
-12
lines changed

12 files changed

+113
-12
lines changed

lang/en/messages.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
return [
44
'configuration_instructions' => 'You can add or modify your Blueprint fields to customize what data is imported and what fieldtype it will be stored in. You can save, refresh, and come back to this import config later until it\'s ready to run.',
55
'mapping_instructions' => 'Map the fields from your import to the fields in your blueprint.',
6+
'migrations_needed' => 'In order to keep track of import progress, the importer uses Laravel\'s Job Batching feature. It uses a <code>job_batches</code> table in your database to store information about batches. Before you can run the importer, you will need to run the <code>php artisan migrate</code> command.',
67
'unique_field_instructions' => 'Select a "unique field" to determine if an item already exists.',
78
];

phpunit.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
<env name="APP_KEY" value="base64:ybcI9MKuhLnESRSuWDfnJQuohOXMBaynfbTC5Y5i1FE="/>
1212
<env name="BCRYPT_ROUNDS" value="4"/>
1313
<env name="CACHE_DRIVER" value="array"/>
14-
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
15-
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
14+
<env name="DB_CONNECTION" value="sqlite"/>
15+
<env name="DB_DATABASE" value=":memory:"/>
1616
<env name="MAIL_MAILER" value="array"/>
1717
<env name="QUEUE_CONNECTION" value="sync"/>
1818
<env name="SESSION_DRIVER" value="array"/>

resources/js/components/EditImportForm.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77

88
<div class="flex items-center space-x-4">
99
<button class="btn" :disabled="saving" @click="save()">{{ __('Save') }}</button>
10-
<button class="btn-primary" :disabled="saving" @click="save(true)">{{ __('Save & Run') }}</button>
10+
<button class="btn-primary" :disabled="saving || batchesTableMissing" @click="save(true)">{{ __('Save & Run') }}</button>
1111
</div>
1212
</div>
1313

14+
<div v-if="batchesTableMissing" class="text-xs border border-yellow-dark rounded p-4 bg-yellow dark:bg-dark-blue-100 dark:border-none">
15+
<div class="font-bold mb-2">{{ __('Please run your migrations.') }}</div>
16+
<p v-html="__('importer::messages.migrations_needed')"></p>
17+
</div>
18+
1419
<div class="mt-3 card overflow-hidden">
1520
<Mappings
1621
:config="config"
@@ -35,6 +40,7 @@ export default {
3540
title: String,
3641
initialConfig: Object,
3742
mappingsUrl: String,
43+
batchesTableMissing: Boolean,
3844
},
3945
4046
data() {

resources/views/edit.blade.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@use('Statamic\Support\Str')
2+
13
@extends('statamic::layout')
24
@section('title', $import->name())
35
@section('wrapper_class', 'max-w-3xl')
@@ -9,5 +11,6 @@
911
title="{{ $import->name() }}"
1012
:initial-config='@json($import->config())'
1113
mappings-url="{{ cp_route('utilities.importer.mappings') }}"
14+
:batches-table-missing="{{ Str::bool($batchesTableMissing) }}"
1215
></edit-import-form>
1316
@stop
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Statamic\Importer\Exceptions;
4+
5+
class JobBatchesTableMissingException extends \Exception
6+
{
7+
public function __construct()
8+
{
9+
parent::__construct('The job_batches table is missing. Please run `php artisan migrate` to run the required migrations.');
10+
}
11+
}

src/Http/Controllers/ImportController.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use Illuminate\Http\Request;
66
use Illuminate\Support\Arr;
7+
use Illuminate\Support\Facades\Artisan;
8+
use Illuminate\Support\Facades\Schema;
79
use Illuminate\Support\Facades\Storage;
810
use Illuminate\Support\Str;
911
use Statamic\CP\Breadcrumbs;
@@ -94,6 +96,7 @@ public function edit(Request $request, $import)
9496

9597
return view('importer::edit', [
9698
'import' => $import,
99+
'batchesTableMissing' => ! $this->ensureJobBatchesTableExists(),
97100
'breadcrumbs' => Breadcrumbs::make([
98101
['text' => __('Imports'), 'url' => cp_route('utilities.importer')],
99102
]),
@@ -211,4 +214,29 @@ private function getConfigBlueprint(): \Statamic\Fields\Blueprint
211214

212215
return $blueprint;
213216
}
217+
218+
private function ensureJobBatchesTableExists(): bool
219+
{
220+
if (Schema::connection(config('queue.batching.database'))->hasTable(config('queue.batching.table'))) {
221+
return true;
222+
}
223+
224+
if (app()->isProduction()) {
225+
return false;
226+
}
227+
228+
try {
229+
// When this return a non-zero exit code, it doesn't necessarily mean there's an issue.
230+
// It could be because the migration has already been published.
231+
Artisan::call('make:queue-batches-table');
232+
233+
if (Artisan::call('migrate') !== 0) {
234+
return false;
235+
}
236+
} catch (\Exception $e) {
237+
return false;
238+
}
239+
240+
return true;
241+
}
214242
}

src/Importer.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
namespace Statamic\Importer;
44

5+
use Illuminate\Bus\Batch;
6+
use Illuminate\Support\Facades\Bus;
7+
use Illuminate\Support\Facades\Schema;
8+
use Statamic\Importer\Exceptions\JobBatchesTableMissingException;
59
use Statamic\Importer\Imports\Import;
610
use Statamic\Importer\Jobs\ImportItemJob;
711
use Statamic\Importer\Sources\Csv;
@@ -13,12 +17,18 @@ class Importer
1317

1418
public static function run(Import $import): void
1519
{
20+
if (! Schema::connection(config('queue.batching.database'))->hasTable(config('queue.batching.table'))) {
21+
throw new JobBatchesTableMissingException;
22+
}
23+
1624
$items = match ($import->get('type')) {
1725
'csv' => (new Csv($import->config()->all()))->getItems($import->get('path')),
1826
'xml' => (new Xml($import->config()->all()))->getItems($import->get('path')),
1927
};
2028

21-
$items->each(fn (array $item) => ImportItemJob::dispatch($import, $item));
29+
Bus::batch($items->map(fn (array $item) => new ImportItemJob($import, $item)))
30+
->before(fn (Batch $batch) => $import->batchId($batch->id)->save())
31+
->dispatch();
2232
}
2333

2434
public static function getTransformer(string $fieldtype): ?string

src/Imports/Import.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Statamic\Importer\Imports;
44

5+
use Illuminate\Bus\PendingBatch;
6+
use Illuminate\Support\Facades\Bus;
57
use Statamic\Importer\Facades\Import as ImportFacade;
68
use Statamic\Importer\Importer;
79
use Statamic\Support\Traits\FluentlyGetsAndSets;
@@ -13,6 +15,7 @@ class Import
1315
public $id;
1416
public $name;
1517
public $config;
18+
public $batchId;
1619

1720
public function __construct()
1821
{
@@ -51,11 +54,26 @@ public function get(string $key, ?string $default = null): mixed
5154
return data_get($this->config, $key, $default);
5255
}
5356

57+
public function batchId($batchId = null)
58+
{
59+
return $this->fluentlyGetOrSet('batchId')->args(func_get_args());
60+
}
61+
62+
public function batch(): ?PendingBatch
63+
{
64+
if (! $this->batchId()) {
65+
return null;
66+
}
67+
68+
return Bus::batch($this->batchId());
69+
}
70+
5471
public function fileData(): array
5572
{
5673
return collect([
5774
'name' => $this->name(),
5875
'config' => $this->config()->all(),
76+
'batch_id' => $this->batchId(),
5977
])->filter()->all();
6078
}
6179

src/Imports/ImportRepository.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public function all(): Collection
2323
return $this->make()
2424
->id($id)
2525
->name(Arr::pull($data, 'name'))
26+
->batchId(Arr::pull($data, 'batch_id'))
2627
->config(Arr::pull($data, 'config'));
2728
})
2829
->sortBy->name()

src/Jobs/ImportItemJob.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Statamic\Importer\Jobs;
44

5+
use Illuminate\Bus\Batchable;
56
use Illuminate\Bus\Queueable;
67
use Illuminate\Contracts\Queue\ShouldQueue;
78
use Illuminate\Foundation\Bus\Dispatchable;
@@ -20,7 +21,7 @@
2021

2122
class ImportItemJob implements ShouldQueue
2223
{
23-
use Dispatchable, InteractsWithQueue, Queueable;
24+
use Batchable, Dispatchable, InteractsWithQueue, Queueable;
2425

2526
public function __construct(public Import $import, public array $item) {}
2627

@@ -71,9 +72,12 @@ protected function getBlueprint(): Blueprint
7172

7273
protected function findOrCreateEntry(array $data): void
7374
{
75+
$collection = Collection::find($this->import->get('destination.collection'));
76+
$site = Site::get($this->import->get('destination.site') ?? Site::selected()->handle());
77+
7478
$entry = Entry::query()
75-
->where('collection', $this->import->get('destination.collection'))
76-
->where('site', $this->import->get('destination.site') ?? Site::selected()->handle())
79+
->where('locale', $site->handle())
80+
->where('collection', $collection->handle())
7781
->where($this->import->get('unique_field'), $data[$this->import->get('unique_field')])
7882
->first();
7983

@@ -82,9 +86,7 @@ protected function findOrCreateEntry(array $data): void
8286
return;
8387
}
8488

85-
$entry = Entry::make()
86-
->collection($this->import->get('destination.collection'))
87-
->locale($this->import->get('destination.site') ?? Site::selected()->handle());
89+
$entry = Entry::make()->collection($collection)->locale($site);
8890
}
8991

9092
if ($entry->id() && ! $this->import->get('strategy.update', true)) {

tests/ImporterTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Statamic\Importer\Tests;
44

5+
use Illuminate\Foundation\Testing\DatabaseMigrations;
56
use Illuminate\Support\Facades\File;
67
use PHPUnit\Framework\Attributes\Test;
78
use Statamic\Facades\Collection;
@@ -12,7 +13,7 @@
1213

1314
class ImporterTest extends TestCase
1415
{
15-
use PreventsSavingStacheItemsToDisk;
16+
use DatabaseMigrations, PreventsSavingStacheItemsToDisk;
1617

1718
#[Test]
1819
public function it_can_import_from_csv_files()

tests/TestCase.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use Statamic\Importer\ServiceProvider;
88
use Statamic\Testing\AddonTestCase;
99

10+
use function Orchestra\Testbench\artisan;
11+
1012
abstract class TestCase extends AddonTestCase
1113
{
1214
protected string $addonServiceProvider = ServiceProvider::class;
@@ -23,12 +25,30 @@ protected function getEnvironmentSetUp($app)
2325
'driver' => 'file',
2426
'path' => storage_path('framework/cache/outpost-data'),
2527
]);
28+
29+
$app['config']->set('queue.batching.database', 'testing');
2630
}
2731

28-
protected function setSites($sites)
32+
protected function setSites($sites): void
2933
{
3034
Site::setSites($sites);
3135

3236
Config::set('statamic.system.multisite', Site::hasMultiple());
3337
}
38+
39+
/**
40+
* Define database migrations.
41+
*
42+
* @return void
43+
*/
44+
protected function defineDatabaseMigrations()
45+
{
46+
artisan($this, 'queue:batches-table');
47+
48+
artisan($this, 'migrate', ['--database' => 'testing']);
49+
50+
$this->beforeApplicationDestroyed(
51+
fn () => artisan($this, 'migrate:rollback', ['--database' => 'testing'])
52+
);
53+
}
3454
}

0 commit comments

Comments
 (0)