diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66990202..37b89a8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,8 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/)
## [Unreleased]
+
+## [2.0.1] - 2019-05-06
### Added
-- Progress bar for console commands
+- Progress report for console commands
## [2.0.0] - 2019-04-09
### Added
diff --git a/README.md b/README.md
index 80d0d67e..bcd7690c 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
+
+
diff --git a/composer.json b/composer.json
index 2f4bda73..61463f23 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,7 @@
{
"name": "matchish/laravel-scout-elasticsearch",
"description": "This package extends Laravel Scout adding full power of ElasticSearch",
- "version": "2.0.0",
+ "version": "2.0.1",
"keywords": [
"laravel",
"scout",
@@ -20,7 +20,6 @@
"php": "^7.1.3",
"elasticsearch/elasticsearch": "~6.0",
"laravel/scout": "^6.1.1|^7.0",
- "league/pipeline": "^1.0",
"ongr/elasticsearch-dsl": "^6.0"
},
"require-dev": {
diff --git a/docs/demo.gif b/docs/demo.gif
new file mode 100644
index 00000000..17ea84a3
Binary files /dev/null and b/docs/demo.gif differ
diff --git a/resources/lang/en/flush.php b/resources/lang/en/flush.php
new file mode 100644
index 00000000..7fe80295
--- /dev/null
+++ b/resources/lang/en/flush.php
@@ -0,0 +1,5 @@
+ 'All [:searchable] records have been flushed.',
+];
diff --git a/resources/lang/en/import.php b/resources/lang/en/import.php
new file mode 100644
index 00000000..d71a44ce
--- /dev/null
+++ b/resources/lang/en/import.php
@@ -0,0 +1,7 @@
+ 'Importing [:searchable]',
+ 'done' => 'All [:searchable] records have been imported.',
+ 'done.queue' => 'Import job dispatched to the queue.',
+];
diff --git a/src/Console/Commands/FlushCommand.php b/src/Console/Commands/FlushCommand.php
index ed0622f6..2fafa0c7 100644
--- a/src/Console/Commands/FlushCommand.php
+++ b/src/Console/Commands/FlushCommand.php
@@ -32,7 +32,10 @@ public function handle(): void
});
$searchableList->each(function ($searchable) {
$searchable::removeAllFromSearch();
- $this->output->success('All ['.$searchable.'] records have been flushed.');
+ $doneMessage = trans('scout::flush.done', [
+ 'searchable' => $searchable,
+ ]);
+ $this->output->success($doneMessage);
});
}
}
diff --git a/src/Console/Commands/ImportCommand.php b/src/Console/Commands/ImportCommand.php
index a482ebe2..410ec314 100644
--- a/src/Console/Commands/ImportCommand.php
+++ b/src/Console/Commands/ImportCommand.php
@@ -6,6 +6,7 @@
use Illuminate\Console\Command;
use Matchish\ScoutElasticSearch\Jobs\Import;
+use Matchish\ScoutElasticSearch\Jobs\QueueableJob;
use Matchish\ScoutElasticSearch\Searchable\SearchableListFactory;
final class ImportCommand extends Command
@@ -24,22 +25,41 @@ final class ImportCommand extends Command
*/
public function handle(): void
{
- $command = $this;
- $searchableList = collect($command->argument('searchable'))->whenEmpty(function () {
+ $this->searchableList($this->argument('searchable'))
+ ->each(function ($searchable) {
+ $this->import($searchable);
+ });
+ }
+
+ private function searchableList($argument)
+ {
+ return collect($argument)->whenEmpty(function () {
$factory = new SearchableListFactory(app()->getNamespace(), app()->path());
return $factory->make();
});
- $searchableList->each(function ($searchable) {
- $job = new Import($searchable);
- if (config('scout.queue')) {
- dispatch($job)->allOnQueue((new $searchable)->syncWithSearchUsingQueue())
- ->allOnConnection(config((new $searchable)->syncWithSearchUsing()));
- $this->output->success('All ['.$searchable.'] records have been dispatched to import job.');
- } else {
- dispatch_now($job);
- $this->output->success('All ['.$searchable.'] records have been searchable.');
- }
- });
+ }
+
+ private function import($searchable)
+ {
+ $job = new Import($searchable);
+
+ if (config('scout.queue')) {
+ $job = (new QueueableJob())->chain([$job]);
+ }
+
+ $bar = (new ProgressBarFactory($this->output))->create();
+ $job->withProgressReport($bar);
+
+ $startMessage = trans('scout::import.start', ['searchable' => "$searchable"]);
+ $this->line($startMessage);
+
+ dispatch($job)->allOnQueue((new $searchable)->syncWithSearchUsingQueue())
+ ->allOnConnection(config((new $searchable)->syncWithSearchUsing()));
+
+ $doneMessage = trans(config('scout.queue') ? 'scout::import.done.queue' : 'scout::import.done', [
+ 'searchable' => $searchable,
+ ]);
+ $this->output->success($doneMessage);
}
}
diff --git a/src/Console/Commands/ProgressBarFactory.php b/src/Console/Commands/ProgressBarFactory.php
new file mode 100644
index 00000000..9f16be5f
--- /dev/null
+++ b/src/Console/Commands/ProgressBarFactory.php
@@ -0,0 +1,34 @@
+output = $output;
+ }
+
+ public function create(int $max = 0)
+ {
+ $bar = $this->output->createProgressBar($max);
+ $bar->setBarCharacter('⚬>');
+ $bar->setEmptyBarCharacter('⚬>');
+ $bar->setProgressCharacter('➤>');
+ $bar->setFormat(
+ "%message%\n%current%/%max% [%bar%] %percent:3s%%\n"
+ );
+
+ return $bar;
+ }
+}
diff --git a/src/Engines/ElasticSearchEngine.php b/src/Engines/ElasticSearchEngine.php
index 49f075eb..260432ff 100644
--- a/src/Engines/ElasticSearchEngine.php
+++ b/src/Engines/ElasticSearchEngine.php
@@ -8,7 +8,6 @@
use Laravel\Scout\Builder as BaseBuilder;
use ONGR\ElasticsearchDSL\Query\MatchAllQuery;
use Matchish\ScoutElasticSearch\ElasticSearch\Index;
-use Matchish\ScoutElasticSearch\Pipelines\ImportPipeline;
use Matchish\ScoutElasticSearch\ElasticSearch\Params\Bulk;
use Matchish\ScoutElasticSearch\ElasticSearch\SearchFactory;
use Matchish\ScoutElasticSearch\ElasticSearch\SearchResults;
@@ -62,10 +61,13 @@ public function delete($models)
public function flush($model)
{
$indexName = $model->searchableAs();
- $body = (new Search())->addQuery(new MatchAllQuery())->toArray();
- $params = new SearchParams($indexName, $body);
- $this->elasticsearch->deleteByQuery($params->toArray());
- $this->elasticsearch->indices()->refresh((new Refresh($indexName))->toArray());
+ $exist = $this->elasticsearch->indices()->exists(['index' => $indexName]);
+ if ($exist) {
+ $body = (new Search())->addQuery(new MatchAllQuery())->toArray();
+ $params = new SearchParams($indexName, $body);
+ $this->elasticsearch->deleteByQuery($params->toArray());
+ $this->elasticsearch->indices()->refresh((new Refresh($indexName))->toArray());
+ }
}
/**
@@ -114,15 +116,6 @@ public function getTotalCount($results)
return $results['hits']['total'];
}
- /**
- * @internal
- */
- public function sync($model)
- {
- $pipeline = new ImportPipeline($this->elasticsearch);
- $pipeline->process([Index::fromSearchable($model), $model]);
- }
-
/**
* @param BaseBuilder $builder
* @param array $options
diff --git a/src/Jobs/Import.php b/src/Jobs/Import.php
index 9483a0bf..139c09d6 100644
--- a/src/Jobs/Import.php
+++ b/src/Jobs/Import.php
@@ -2,16 +2,18 @@
namespace Matchish\ScoutElasticSearch\Jobs;
+use Elasticsearch\Client;
use Illuminate\Bus\Queueable;
-use Illuminate\Contracts\Queue\ShouldQueue;
-use Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine;
+use Illuminate\Support\Collection;
+use Matchish\ScoutElasticSearch\ProgressReportable;
/**
* @internal
*/
-final class Import implements ShouldQueue
+final class Import
{
use Queueable;
+ use ProgressReportable;
/**
* @var string
@@ -27,10 +29,22 @@ public function __construct(string $searchable)
}
/**
- * @param ElasticSearchEngine $engine
+ * @param Client $elasticsearch
*/
- public function handle(ElasticSearchEngine $engine): void
+ public function handle(Client $elasticsearch): void
{
- $engine->sync(new $this->searchable);
+ $stages = $this->stages();
+ $estimate = $stages->sum->estimate();
+ $this->progressBar()->setMaxSteps($estimate);
+ $stages->each(function ($stage) use ($elasticsearch) {
+ $this->progressBar()->setMessage($stage->title());
+ $stage->handle($elasticsearch);
+ $this->progressBar()->advance($stage->estimate());
+ });
+ }
+
+ private function stages(): Collection
+ {
+ return ImportStages::fromSearchable(new $this->searchable);
}
}
diff --git a/src/Jobs/ImportStages.php b/src/Jobs/ImportStages.php
new file mode 100644
index 00000000..86224595
--- /dev/null
+++ b/src/Jobs/ImportStages.php
@@ -0,0 +1,36 @@
+count()) {
+ return (new static([
+ new CleanUp($searchable),
+ new CreateWriteIndex($searchable, $index),
+ PullFromSource::chunked($searchable),
+ new RefreshIndex($index),
+ new SwitchToNewAndRemoveOldIndex($searchable, $index),
+ ]))->flatten();
+ } else {
+ return new static;
+ }
+ }
+}
diff --git a/src/Jobs/QueueableJob.php b/src/Jobs/QueueableJob.php
new file mode 100644
index 00000000..8d4c9564
--- /dev/null
+++ b/src/Jobs/QueueableJob.php
@@ -0,0 +1,17 @@
+searchable = $searchable;
+ }
+
+ public function handle(Client $elasticsearch): void
+ {
+ /** @var Searchable $searchable */
+ $searchable = $this->searchable;
+ $params = GetAliasParams::anyIndex($searchable->searchableAs());
+ try {
+ $response = $elasticsearch->indices()->getAliases($params->toArray());
+ } catch (Missing404Exception $e) {
+ $response = [];
+ }
+ foreach ($response as $indexName => $data) {
+ foreach ($data['aliases'] as $alias => $config) {
+ if (array_key_exists('is_write_index', $config) && $config['is_write_index']) {
+ $params = new DeleteIndexParams((string) $indexName);
+ $elasticsearch->indices()->delete($params->toArray());
+ continue 2;
+ }
+ }
+ }
+ }
+
+ public function title(): string
+ {
+ return 'Clean up';
+ }
+
+ public function estimate(): int
+ {
+ return 1;
+ }
+}
diff --git a/src/Jobs/Stages/CreateWriteIndex.php b/src/Jobs/Stages/CreateWriteIndex.php
new file mode 100644
index 00000000..6a7799e3
--- /dev/null
+++ b/src/Jobs/Stages/CreateWriteIndex.php
@@ -0,0 +1,60 @@
+searchable = $searchable;
+ $this->index = $index;
+ }
+
+ public function handle(Client $elasticsearch): void
+ {
+ /** @var Searchable $searchable */
+ $searchable = $this->searchable;
+ $this->index->addAlias(new WriteAlias(new DefaultAlias($searchable->searchableAs())));
+
+ $params = new Create(
+ $this->index->name(),
+ $this->index->config()
+ );
+
+ $elasticsearch->indices()->create($params->toArray());
+ }
+
+ public function title(): string
+ {
+ return 'Create write index';
+ }
+
+ public function estimate(): int
+ {
+ return 1;
+ }
+}
diff --git a/src/Jobs/Stages/PullFromSource.php b/src/Jobs/Stages/PullFromSource.php
new file mode 100644
index 00000000..be34dfea
--- /dev/null
+++ b/src/Jobs/Stages/PullFromSource.php
@@ -0,0 +1,73 @@
+query = $query;
+ }
+
+ public function handle(): void
+ {
+ $results = $this->query->get();
+ $results->filter->shouldBeSearchable()->searchable();
+ }
+
+ public function estimate(): int
+ {
+ return 1;
+ }
+
+ public function title(): string
+ {
+ return 'Indexing...';
+ }
+
+ /**
+ * @param Model $searchable
+ * @return Collection
+ */
+ public static function chunked(Model $searchable): Collection
+ {
+ /** @var Searchable $searchable */
+ $softDelete = config('scout.soft_delete', false);
+ $query = $searchable->newQuery()
+ ->when($softDelete, function ($query) {
+ return $query->withTrashed();
+ })
+ ->orderBy($searchable->getKeyName());
+ $totalSearchables = $query->count();
+ if ($totalSearchables) {
+ $chunkSize = (int) config('scout.chunk.searchable', self::DEFAULT_CHUNK_SIZE);
+ $totalChunks = (int) ceil($totalSearchables / $chunkSize);
+
+ return collect(range(1, $totalChunks))->map(function ($page) use ($query, $chunkSize) {
+ $clone = (clone $query)->forPage($page, $chunkSize);
+
+ return new static($clone);
+ });
+ } else {
+ return collect();
+ }
+ }
+}
diff --git a/src/Jobs/Stages/RefreshIndex.php b/src/Jobs/Stages/RefreshIndex.php
new file mode 100644
index 00000000..285b0919
--- /dev/null
+++ b/src/Jobs/Stages/RefreshIndex.php
@@ -0,0 +1,43 @@
+index = $index;
+ }
+
+ public function handle(Client $elasticsearch): void
+ {
+ $params = new Refresh($this->index->name());
+ $elasticsearch->indices()->refresh($params->toArray());
+ }
+
+ public function estimate(): int
+ {
+ return 1;
+ }
+
+ public function title(): string
+ {
+ return 'Refreshing index';
+ }
+}
diff --git a/src/Jobs/Stages/SwitchToNewAndRemoveOldIndex.php b/src/Jobs/Stages/SwitchToNewAndRemoveOldIndex.php
new file mode 100644
index 00000000..025c7242
--- /dev/null
+++ b/src/Jobs/Stages/SwitchToNewAndRemoveOldIndex.php
@@ -0,0 +1,62 @@
+searchable = $searchable;
+ $this->index = $index;
+ }
+
+ public function handle(Client $elasticsearch): void
+ {
+ /** @var Searchable $searchable */
+ $searchable = $this->searchable;
+ $params = Get::anyIndex($searchable->searchableAs());
+ $response = $elasticsearch->indices()->getAliases($params->toArray());
+
+ $params = new Update();
+ foreach ($response as $indexName => $alias) {
+ if ($indexName != $this->index->name()) {
+ $params->removeIndex($indexName);
+ } else {
+ $params->add((string) $indexName, $searchable->searchableAs());
+ }
+ }
+ $elasticsearch->indices()->updateAliases($params->toArray());
+ }
+
+ public function estimate(): int
+ {
+ return 1;
+ }
+
+ public function title(): string
+ {
+ return 'Switching to the new index';
+ }
+}
diff --git a/src/Pipelines/ImportPipeline.php b/src/Pipelines/ImportPipeline.php
deleted file mode 100644
index a0cbb8de..00000000
--- a/src/Pipelines/ImportPipeline.php
+++ /dev/null
@@ -1,32 +0,0 @@
-elasticsearch = $elasticsearch;
- }
-
- public function __invoke($payload): array
- {
- [$index, $source] = $payload;
- $params = Get::anyIndex($source->searchableAs());
- try {
- $response = $this->elasticsearch->indices()->getAliases($params->toArray());
- } catch (Missing404Exception $e) {
- $response = [];
- }
- foreach ($response as $indexName => $data) {
- foreach ($data['aliases'] as $alias => $config) {
- if (array_key_exists('is_write_index', $config) && $config['is_write_index']) {
- $params = new Delete((string) $indexName);
- $this->elasticsearch->indices()->delete($params->toArray());
- continue 2;
- }
- }
- }
-
- return [$index, $source];
- }
-}
diff --git a/src/Pipelines/Stages/CreateWriteIndex.php b/src/Pipelines/Stages/CreateWriteIndex.php
deleted file mode 100644
index aa287b81..00000000
--- a/src/Pipelines/Stages/CreateWriteIndex.php
+++ /dev/null
@@ -1,40 +0,0 @@
-elasticsearch = $elasticsearch;
- }
-
- public function __invoke($payload)
- {
- [$index, $source] = $payload;
-
- $index->addAlias(new WriteAlias(new DefaultAlias($source->searchableAs())));
-
- $params = new Create(
- $index->name(),
- $index->config()
- );
-
- $this->elasticsearch->indices()->create($params->toArray());
-
- return [$index, $source];
- }
-}
diff --git a/src/Pipelines/Stages/PullFromSource.php b/src/Pipelines/Stages/PullFromSource.php
deleted file mode 100644
index f50b910c..00000000
--- a/src/Pipelines/Stages/PullFromSource.php
+++ /dev/null
@@ -1,48 +0,0 @@
-elasticsearch = $elasticsearch;
- }
-
- public function __invoke($payload)
- {
- [$index, $source] = $payload;
-
- $softDelete = $source::usesSoftDelete() && config('scout.soft_delete', false);
- $query = $source->newQuery()
- ->when($softDelete, function ($query) {
- $query->withTrashed();
- })
- ->orderBy($source->getKeyName());
- $totalSearchables = $query->count();
- if ($totalSearchables) {
- $chunkSize = (int) config('scout.chunk.searchable', 500);
- $totalChunks = (int) ceil($totalSearchables / $chunkSize);
- collect(range(1, $totalChunks))->each(function ($page) use ($query, $chunkSize) {
- $results = $query->forPage($page, $chunkSize)->get();
- $countResults = $results->count();
- if ($countResults == 0) {
- return false;
- }
- $results->filter->shouldBeSearchable()->searchable();
- });
- }
-
- return [$index, $source];
- }
-}
diff --git a/src/Pipelines/Stages/RefreshIndex.php b/src/Pipelines/Stages/RefreshIndex.php
deleted file mode 100644
index b4fa0e15..00000000
--- a/src/Pipelines/Stages/RefreshIndex.php
+++ /dev/null
@@ -1,32 +0,0 @@
-elasticsearch = $elasticsearch;
- }
-
- public function __invoke($payload)
- {
- [$index, $source] = $payload;
-
- $params = new Refresh($index->name());
- $this->elasticsearch->indices()->refresh($params->toArray());
-
- return [$index, $source];
- }
-}
diff --git a/src/Pipelines/Stages/SwitchToNewAndRemoveOldIndex.php b/src/Pipelines/Stages/SwitchToNewAndRemoveOldIndex.php
deleted file mode 100644
index 66a45358..00000000
--- a/src/Pipelines/Stages/SwitchToNewAndRemoveOldIndex.php
+++ /dev/null
@@ -1,43 +0,0 @@
-elasticsearch = $elasticsearch;
- }
-
- public function __invoke($payload)
- {
- [$index, $source] = $payload;
-
- $params = Get::anyIndex($source->searchableAs());
- $response = $this->elasticsearch->indices()->getAliases($params->toArray());
-
- $params = new Update();
- foreach ($response as $indexName => $alias) {
- if ($indexName != $index->name()) {
- $params->removeIndex($indexName);
- } else {
- $params->add((string) $indexName, $source->searchableAs());
- }
- }
- $this->elasticsearch->indices()->updateAliases($params->toArray());
-
- return [$index, $source];
- }
-}
diff --git a/src/ProgressReportable.php b/src/ProgressReportable.php
new file mode 100644
index 00000000..e4edcdb4
--- /dev/null
+++ b/src/ProgressReportable.php
@@ -0,0 +1,21 @@
+progressBar = $progressBar;
+ }
+
+ private function progressBar(): ProgressBar
+ {
+ return $this->progressBar ?: new ProgressBar(new NullOutput());
+ }
+}
diff --git a/src/ScoutElasticSearchServiceProvider.php b/src/ScoutElasticSearchServiceProvider.php
index 0925449a..ff1e3870 100644
--- a/src/ScoutElasticSearchServiceProvider.php
+++ b/src/ScoutElasticSearchServiceProvider.php
@@ -19,6 +19,8 @@ final class ScoutElasticSearchServiceProvider extends ServiceProvider
*/
public function boot(): void
{
+ $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'scout');
+
resolve(EngineManager::class)->extend(ElasticSearchEngine::class, function () {
$elasticsearch = resolve(Client::class);
diff --git a/tests/Feature/ImportCommandTest.php b/tests/Feature/ImportCommandTest.php
index ff7e5f0c..ededcff4 100644
--- a/tests/Feature/ImportCommandTest.php
+++ b/tests/Feature/ImportCommandTest.php
@@ -4,9 +4,12 @@
namespace Tests\Feature;
+use App\Book;
+use stdClass;
use App\Product;
use Tests\IntegrationTestCase;
use Illuminate\Support\Facades\Artisan;
+use Symfony\Component\Console\Output\BufferedOutput;
final class ImportCommandTest extends IntegrationTestCase
{
@@ -28,7 +31,7 @@ public function test_import_entites(): void
'index' => 'products',
'body' => [
'query' => [
- 'match_all' => new \stdClass(),
+ 'match_all' => new stdClass(),
],
],
];
@@ -54,7 +57,7 @@ public function test_import_entites_in_queue(): void
'index' => 'products',
'body' => [
'query' => [
- 'match_all' => new \stdClass(),
+ 'match_all' => new stdClass(),
],
],
];
@@ -78,7 +81,7 @@ public function test_import_all_pages(): void
'index' => (new Product())->searchableAs(),
'body' => [
'query' => [
- 'match_all' => new \stdClass(),
+ 'match_all' => new stdClass(),
],
],
];
@@ -91,7 +94,7 @@ public function test_remove_old_index_after_switching_to_new(): void
$params = [
'index' => 'products_old',
'body' => [
- 'aliases' => ['products' => new \stdClass()],
+ 'aliases' => ['products' => new stdClass()],
'settings' => [
'number_of_shards' => 1,
'number_of_replicas' => 0,
@@ -112,4 +115,40 @@ public function test_remove_old_index_after_switching_to_new(): void
$this->assertFalse($this->elasticsearch->indices()->exists(['index' => 'products_old']), 'Old index must be deleted');
}
+
+ public function test_progress_report()
+ {
+ $output = new BufferedOutput();
+ Artisan::call('scout:import', [], $output);
+
+ $output = explode("\n", $output->fetch());
+ $this->assertEquals(
+ trans('scout::import.start', ['searchable' => Product::class]),
+ trim($output[0]));
+ $this->assertEquals(
+ '[OK] '.trans('scout::import.done', ['searchable' => Product::class]),
+ trim($output[2]));
+ $this->assertEquals(
+ trans('scout::import.start', ['searchable' => Book::class]),
+ trim($output[4]));
+ $this->assertEquals(
+ '[OK] '.trans('scout::import.done', ['searchable' => Book::class]),
+ trim($output[6]));
+ }
+
+ public function test_progress_report_in_queue()
+ {
+ $this->app['config']->set('scout.queue', ['connection' => 'sync', 'queue' => 'scout']);
+
+ $output = new BufferedOutput();
+ Artisan::call('scout:import', [], $output);
+
+ $output = explode("\n", $output->fetch());
+ $this->assertEquals(
+ trans('scout::import.start', ['searchable' => Product::class]),
+ trim($output[0]));
+ $this->assertEquals(
+ '[OK] '.trans('scout::import.done.queue', ['searchable' => Product::class]),
+ trim($output[2]));
+ }
}
diff --git a/tests/Feature/ScoutElasticSearchServiceProviderTest.php b/tests/Feature/ScoutElasticSearchServiceProviderTest.php
index 0e2af790..99d7fb2e 100644
--- a/tests/Feature/ScoutElasticSearchServiceProviderTest.php
+++ b/tests/Feature/ScoutElasticSearchServiceProviderTest.php
@@ -3,10 +3,11 @@
namespace Matchish\ScoutElasticSearch;
use Tests\TestCase;
+use Elasticsearch\Client;
class ScoutElasticSearchServiceProviderTest extends TestCase
{
- public function testConfigPublishing()
+ public function test_config_publishing()
{
\File::delete(config_path('elasticsearch.php'));
$provider = new ElasticSearchServiceProvider($this->app);
@@ -18,4 +19,10 @@ public function testConfigPublishing()
$this->assertFileExists(config_path('elasticsearch.php'));
}
+
+ public function test_provides()
+ {
+ $provider = new ElasticSearchServiceProvider($this->app);
+ $this->assertEquals([Client::class], $provider->provides());
+ }
}
diff --git a/tests/Integration/Jobs/ImportTest.php b/tests/Integration/Jobs/ImportTest.php
new file mode 100644
index 00000000..60b55068
--- /dev/null
+++ b/tests/Integration/Jobs/ImportTest.php
@@ -0,0 +1,41 @@
+create();
+ Product::setEventDispatcher($dispatcher);
+
+ $job = new Import(Product::class);
+ $output = new DummyOutput();
+ $outputStyle = new OutputStyle(new ArrayInput([]), $output);
+ $progressBar = $outputStyle->createProgressBar();
+ $progressBar->setFormat('[%message%] %current%/%max%');
+ $job->withProgressReport($progressBar);
+
+ dispatch($job);
+
+ $this->assertEquals([
+ 'Clean up 1/7',
+ 'Create write index 2/7',
+ 'Indexing... 3/7',
+ 'Indexing... 4/7',
+ 'Indexing... 5/7',
+ 'Refreshing index 6/7',
+ 'Switching to the new index 7/7',
+ ], $output->getLogs());
+ }
+}
diff --git a/tests/Integration/Pipelines/Stages/CleanUpTest.php b/tests/Integration/Jobs/Stages/CleanUpTest.php
similarity index 56%
rename from tests/Integration/Pipelines/Stages/CleanUpTest.php
rename to tests/Integration/Jobs/Stages/CleanUpTest.php
index c9d692ef..ca2c6442 100644
--- a/tests/Integration/Pipelines/Stages/CleanUpTest.php
+++ b/tests/Integration/Jobs/Stages/CleanUpTest.php
@@ -1,11 +1,11 @@
elasticsearch->indices()->create([
'index' => 'products_old',
- 'body' => ['aliases' => ['products' => new \stdClass()]],
+ 'body' => ['aliases' => ['products' => new stdClass()]],
]);
$this->elasticsearch->indices()->create([
'index' => 'products_new',
@@ -24,22 +24,12 @@ public function test_remove_write_index()
'body' => ['aliases' => ['products' => ['is_write_index' => false]]],
]);
- $stage = new CleanUp($this->elasticsearch);
- $stage([new Index(new Product(), []), new Product()]);
+ $stage = new CleanUp(new Product());
+ $stage->handle($this->elasticsearch);
$writeIndexExist = $this->elasticsearch->indices()->exists(['index' => 'products_new']);
$readIndexExist = $this->elasticsearch->indices()->exists(['index' => 'products_old']);
$this->assertFalse($writeIndexExist);
$this->assertTrue($readIndexExist);
}
-
- public function test_return_same_payload()
- {
- $stage = new CleanUp($this->elasticsearch);
- $payload = [new Index(new Product(), []), new Product()];
- $nextPayload = $stage($payload);
- $this->assertEquals(2, count($nextPayload));
- $this->assertSame($payload[0], $nextPayload[0]);
- $this->assertSame($payload[1], $nextPayload[1]);
- }
}
diff --git a/tests/Integration/Pipelines/Stages/CreateWriteIndexTest.php b/tests/Integration/Jobs/Stages/CreateWriteIndexTest.php
similarity index 79%
rename from tests/Integration/Pipelines/Stages/CreateWriteIndexTest.php
rename to tests/Integration/Jobs/Stages/CreateWriteIndexTest.php
index 37ae1616..0877a117 100644
--- a/tests/Integration/Pipelines/Stages/CreateWriteIndexTest.php
+++ b/tests/Integration/Jobs/Stages/CreateWriteIndexTest.php
@@ -2,21 +2,21 @@
declare(strict_types=1);
-namespace Tests\Integration\Pipelines\Stages;
+namespace Tests\Integration\Jobs\Stages;
use App\Product;
use Elasticsearch\Client;
use Tests\IntegrationTestCase;
use Matchish\ScoutElasticSearch\ElasticSearch\Index;
-use Matchish\ScoutElasticSearch\Pipelines\Stages\CreateWriteIndex;
+use Matchish\ScoutElasticSearch\Jobs\Stages\CreateWriteIndex;
final class CreateWriteIndexTest extends IntegrationTestCase
{
public function test_create_write_index(): void
{
$elasticsearch = $this->app->make(Client::class);
- $stage = new CreateWriteIndex($elasticsearch);
- $stage([Index::fromSearchable(new Product()), new Product()]);
+ $stage = new CreateWriteIndex(new Product(), Index::fromSearchable(new Product()));
+ $stage->handle($elasticsearch);
$response = $elasticsearch->indices()->getAliases(['index' => '*', 'name' => 'products']);
$this->assertTrue($this->containsWriteIndex($response, 'products'));
}
diff --git a/tests/Integration/Pipelines/Stages/PullFromSourceTest.php b/tests/Integration/Jobs/Stages/PullFromSourceTest.php
similarity index 51%
rename from tests/Integration/Pipelines/Stages/PullFromSourceTest.php
rename to tests/Integration/Jobs/Stages/PullFromSourceTest.php
index 8dec6af4..f54851d6 100644
--- a/tests/Integration/Pipelines/Stages/PullFromSourceTest.php
+++ b/tests/Integration/Jobs/Stages/PullFromSourceTest.php
@@ -2,12 +2,12 @@
declare(strict_types=1);
-namespace Tests\Integration\Pipelines\Stages;
+namespace Tests\Integration\Jobs\Stages;
+use stdClass;
use App\Product;
use Tests\IntegrationTestCase;
-use Matchish\ScoutElasticSearch\ElasticSearch\Index;
-use Matchish\ScoutElasticSearch\Pipelines\Stages\PullFromSource;
+use Matchish\ScoutElasticSearch\Jobs\Stages\PullFromSource;
final class PullFromSourceTest extends IntegrationTestCase
{
@@ -23,10 +23,10 @@ public function test_put_all_entites_to_index(): void
Product::setEventDispatcher($dispatcher);
$this->elasticsearch->indices()->create([
'index' => 'products_index',
- 'body' => ['aliases' => ['products' => new \stdClass()]],
+ 'body' => ['aliases' => ['products' => new stdClass()]],
]);
- $stage = new PullFromSource($this->elasticsearch);
- $stage([Index::fromSearchable(new Product()), new Product()]);
+ $stage = new PullFromSource(Product::query());
+ $stage->handle();
$this->elasticsearch->indices()->refresh([
'index' => 'products',
]);
@@ -34,7 +34,7 @@ public function test_put_all_entites_to_index(): void
'index' => 'products',
'body' => [
'query' => [
- 'match_all' => new \stdClass(),
+ 'match_all' => new stdClass(),
],
],
];
@@ -46,10 +46,10 @@ public function test_dont_put_entities_if_no_entities_in_collection(): void
{
$this->elasticsearch->indices()->create([
'index' => 'products_index',
- 'body' => ['aliases' => ['products' => new \stdClass()]],
+ 'body' => ['aliases' => ['products' => new stdClass()]],
]);
- $stage = new PullFromSource($this->elasticsearch);
- $stage([Index::fromSearchable(new Product()), new Product()]);
+ $stage = new PullFromSource(Product::query());
+ $stage->handle();
$this->elasticsearch->indices()->refresh([
'index' => 'products',
]);
@@ -57,7 +57,7 @@ public function test_dont_put_entities_if_no_entities_in_collection(): void
'index' => 'products',
'body' => [
'query' => [
- 'match_all' => new \stdClass(),
+ 'match_all' => new stdClass(),
],
],
];
@@ -78,10 +78,10 @@ public function test_put_all_to_index_if_amount_of_entities_more_than_chunk_size
Product::setEventDispatcher($dispatcher);
$this->elasticsearch->indices()->create([
'index' => 'products_index',
- 'body' => ['aliases' => ['products' => new \stdClass()]],
+ 'body' => ['aliases' => ['products' => new stdClass()]],
]);
- $stage = new PullFromSource($this->elasticsearch);
- $stage([Index::fromSearchable(new Product()), new Product()]);
+ $stage = new PullFromSource(Product::query());
+ $stage->handle();
$this->elasticsearch->indices()->refresh([
'index' => 'products',
]);
@@ -89,7 +89,7 @@ public function test_put_all_to_index_if_amount_of_entities_more_than_chunk_size
'index' => 'products',
'body' => [
'query' => [
- 'match_all' => new \stdClass(),
+ 'match_all' => new stdClass(),
],
],
];
@@ -97,7 +97,7 @@ public function test_put_all_to_index_if_amount_of_entities_more_than_chunk_size
$this->assertEquals($productsAmount, $response['hits']['total']);
}
- public function test_push_soft_delete_meta_data()
+ public function test_pull_soft_delete_meta_data()
{
$this->app['config']['scout.soft_delete'] = true;
@@ -111,10 +111,10 @@ public function test_push_soft_delete_meta_data()
Product::setEventDispatcher($dispatcher);
$this->elasticsearch->indices()->create([
'index' => 'products_index',
- 'body' => ['aliases' => ['products' => new \stdClass()]],
+ 'body' => ['aliases' => ['products' => new stdClass()]],
]);
- $stage = new PullFromSource($this->elasticsearch);
- $stage([Index::fromSearchable(new Product()), new Product()]);
+ $stage = new PullFromSource(Product::query());
+ $stage->handle();
$this->elasticsearch->indices()->refresh([
'index' => 'products',
]);
@@ -122,11 +122,82 @@ public function test_push_soft_delete_meta_data()
'index' => 'products',
'body' => [
'query' => [
- 'match_all' => new \stdClass(),
+ 'match_all' => new stdClass(),
],
],
];
$response = $this->elasticsearch->search($params);
$this->assertEquals(0, $response['hits']['hits'][0]['_source']['__soft_deleted']);
}
+
+ public function test_pull_soft_deleted()
+ {
+ $this->app['config']['scout.soft_delete'] = true;
+
+ $dispatcher = Product::getEventDispatcher();
+ Product::unsetEventDispatcher();
+
+ $productsAmount = 3;
+
+ factory(Product::class, $productsAmount)->create();
+
+ Product::limit(1)->get()->first()->delete();
+
+ Product::setEventDispatcher($dispatcher);
+ $this->elasticsearch->indices()->create([
+ 'index' => 'products_index',
+ 'body' => ['aliases' => ['products' => new stdClass()]],
+ ]);
+ $stages = PullFromSource::chunked(new Product());
+ $stages->first()->handle();
+ $this->elasticsearch->indices()->refresh([
+ 'index' => 'products',
+ ]);
+ $params = [
+ 'index' => 'products',
+ 'body' => [
+ 'query' => [
+ 'match_all' => new stdClass(),
+ ],
+ ],
+ ];
+ $response = $this->elasticsearch->search($params);
+ $this->assertEquals(3, $response['hits']['total']);
+ }
+
+ public function test_no_searchables_no_chunks()
+ {
+ $stages = PullFromSource::chunked(new Product());
+
+ $this->assertEquals(0, $stages->count());
+ }
+
+ public function test_chunked_pull_only_one_page()
+ {
+ $dispatcher = Product::getEventDispatcher();
+ Product::unsetEventDispatcher();
+
+ $productsAmount = 5;
+
+ factory(Product::class, $productsAmount)->create();
+
+ Product::setEventDispatcher($dispatcher);
+
+ $chunks = PullFromSource::chunked(new Product());
+ $chunks->first()->handle();
+ $this->elasticsearch->indices()->refresh([
+ 'index' => 'products',
+ ]);
+ $params = [
+ 'index' => 'products',
+ 'body' => [
+ 'query' => [
+ 'match_all' => new stdClass(),
+ ],
+ ],
+ ];
+ $response = $this->elasticsearch->search($params);
+
+ $this->assertEquals(3, $response['hits']['total']);
+ }
}
diff --git a/tests/Integration/Pipelines/Stages/RefreshIndexTest.php b/tests/Integration/Jobs/Stages/RefreshIndexTest.php
similarity index 71%
rename from tests/Integration/Pipelines/Stages/RefreshIndexTest.php
rename to tests/Integration/Jobs/Stages/RefreshIndexTest.php
index e18c3349..6d32c0b3 100644
--- a/tests/Integration/Pipelines/Stages/RefreshIndexTest.php
+++ b/tests/Integration/Jobs/Stages/RefreshIndexTest.php
@@ -2,12 +2,12 @@
declare(strict_types=1);
-namespace Tests\Integration\Pipelines\Stages;
+namespace Tests\Integration\Jobs\Stages;
-use App\Product;
+use stdClass;
use Tests\IntegrationTestCase;
use Matchish\ScoutElasticSearch\ElasticSearch\Index;
-use Matchish\ScoutElasticSearch\Pipelines\Stages\RefreshIndex;
+use Matchish\ScoutElasticSearch\Jobs\Stages\RefreshIndex;
final class RefreshIndexTest extends IntegrationTestCase
{
@@ -15,7 +15,7 @@ public function test_refresh_index(): void
{
$this->elasticsearch->indices()->create([
'index' => 'products_index',
- 'body' => ['aliases' => ['products' => new \stdClass()]],
+ 'body' => ['aliases' => ['products' => new stdClass()]],
]);
$this->elasticsearch->bulk(['body' => [
['index' => [
@@ -29,14 +29,14 @@ public function test_refresh_index(): void
], ],
]);
- $stage = new RefreshIndex($this->elasticsearch);
- $stage([new Index('products_index'), new Product()]);
+ $stage = new RefreshIndex(new Index('products_index'));
+ $stage->handle($this->elasticsearch);
$params = [
'index' => 'products',
'body' => [
'query' => [
- 'match_all' => new \stdClass(),
+ 'match_all' => new stdClass(),
],
],
];
diff --git a/tests/Integration/Pipelines/Stages/SwitchToNewAndRemoveOldIndexTest.php b/tests/Integration/Jobs/Stages/SwitchToNewAndRemoveOldIndexTest.php
similarity index 75%
rename from tests/Integration/Pipelines/Stages/SwitchToNewAndRemoveOldIndexTest.php
rename to tests/Integration/Jobs/Stages/SwitchToNewAndRemoveOldIndexTest.php
index f1f0e7b7..f608f2ec 100644
--- a/tests/Integration/Pipelines/Stages/SwitchToNewAndRemoveOldIndexTest.php
+++ b/tests/Integration/Jobs/Stages/SwitchToNewAndRemoveOldIndexTest.php
@@ -2,12 +2,13 @@
declare(strict_types=1);
-namespace Tests\Integration\Pipelines\Stages;
+namespace Tests\Integration\Jobs\Stages;
+use stdClass;
use App\Product;
use Tests\IntegrationTestCase;
use Matchish\ScoutElasticSearch\ElasticSearch\Index;
-use Matchish\ScoutElasticSearch\Pipelines\Stages\SwitchToNewAndRemoveOldIndex;
+use Matchish\ScoutElasticSearch\Jobs\Stages\SwitchToNewAndRemoveOldIndex;
final class SwitchToNewAndRemoveOldIndexTest extends IntegrationTestCase
{
@@ -19,11 +20,11 @@ public function test_switch_to_new_and_remove_old_index(): void
]);
$this->elasticsearch->indices()->create([
'index' => 'products_old',
- 'body' => ['aliases' => ['products' => new \stdClass()]],
+ 'body' => ['aliases' => ['products' => new stdClass()]],
]);
- $stage = new SwitchToNewAndRemoveOldIndex($this->elasticsearch);
- $stage([new Index('products_new'), new Product()]);
+ $stage = new SwitchToNewAndRemoveOldIndex(new Product(), new Index('products_new'));
+ $stage->handle($this->elasticsearch);
$newIndexExist = $this->elasticsearch->indices()->exists(['index' => 'products_new']);
$oldIndexExist = $this->elasticsearch->indices()->exists(['index' => 'products_old']);
diff --git a/tests/Unit/Jobs/ImportStagesTest.php b/tests/Unit/Jobs/ImportStagesTest.php
new file mode 100644
index 00000000..764f5c84
--- /dev/null
+++ b/tests/Unit/Jobs/ImportStagesTest.php
@@ -0,0 +1,36 @@
+assertEquals(0, $stages->count());
+ }
+
+ public function test_stages()
+ {
+ factory(Product::class, 10)->create();
+ $stages = ImportStages::fromSearchable(new Product());
+ $this->assertEquals(8, $stages->count());
+ $this->assertInstanceOf(CleanUp::class, $stages->get(0));
+ $this->assertInstanceOf(CreateWriteIndex::class, $stages->get(1));
+ $this->assertInstanceOf(PullFromSource::class, $stages->get(2));
+ $this->assertInstanceOf(PullFromSource::class, $stages->get(3));
+ $this->assertInstanceOf(PullFromSource::class, $stages->get(4));
+ $this->assertInstanceOf(PullFromSource::class, $stages->get(5));
+ $this->assertInstanceOf(RefreshIndex::class, $stages->get(6));
+ $this->assertInstanceOf(SwitchToNewAndRemoveOldIndex::class, $stages->get(7));
+ }
+}
diff --git a/tests/Unit/Pipelines/ImportPipelineTest.php b/tests/Unit/Pipelines/ImportPipelineTest.php
deleted file mode 100644
index 66a6d819..00000000
--- a/tests/Unit/Pipelines/ImportPipelineTest.php
+++ /dev/null
@@ -1,55 +0,0 @@
-process(new \stdClass());
- $this->assertEquals(5, count($processor->stages));
- $this->assertInstanceOf(CleanUp::class, $processor->stages[0]);
- $this->assertInstanceOf(CreateWriteIndex::class, $processor->stages[1]);
- $this->assertInstanceOf(PullFromSource::class, $processor->stages[2]);
- $this->assertInstanceOf(RefreshIndex::class, $processor->stages[3]);
- $this->assertInstanceOf(SwitchToNewAndRemoveOldIndex::class, $processor->stages[4]);
- }
-}
-
-class Processor implements ProcessorInterface
-{
- public $stages;
-
- /**
- * Process the payload using multiple stages.
- *
- * @param mixed $payload
- *
- * @return mixed
- */
- public function process($payload, callable ...$stages)
- {
- $this->stages = $stages;
-
- return $payload;
- }
-}
diff --git a/tests/laravel/app/Book.php b/tests/laravel/app/Book.php
new file mode 100644
index 00000000..aee0cbf8
--- /dev/null
+++ b/tests/laravel/app/Book.php
@@ -0,0 +1,28 @@
+getKeyName());
+ }
+
+ public function getScoutKey()
+ {
+ return $this->getAttribute($this->getScoutKeyName());
+ }
+}
diff --git a/tests/laravel/database/factories/BookFactory.php b/tests/laravel/database/factories/BookFactory.php
new file mode 100644
index 00000000..af23a933
--- /dev/null
+++ b/tests/laravel/database/factories/BookFactory.php
@@ -0,0 +1,15 @@
+define(Book::class, function (Faker $faker) {
+ return [
+ 'custom_key' => $faker->uuid,
+ 'title' => $faker->text,
+ 'author' => $faker->name,
+ 'year' => $faker->year,
+ ];
+});
diff --git a/tests/laravel/database/migrations/2019_15_02_000002_create_books_table.php b/tests/laravel/database/migrations/2019_15_02_000002_create_books_table.php
new file mode 100644
index 00000000..ccf193f0
--- /dev/null
+++ b/tests/laravel/database/migrations/2019_15_02_000002_create_books_table.php
@@ -0,0 +1,28 @@
+increments('id');
+ $table->string('custom_key');
+ $table->string('title');
+ $table->string('author');
+ $table->integer('year');
+ $table->softDeletes();
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('books');
+ }
+}