Skip to content

Commit

Permalink
Merge pull request #8 from TimoKoerber/5-provide-tags
Browse files Browse the repository at this point in the history
Provide tag to filter operations
  • Loading branch information
TimoKoerber authored Mar 31, 2023
2 parents 69d339c + bd241bc commit 87b4089
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 70 deletions.
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,15 @@ php artisan operations:make <operation_name> // create operation file

### Process operations
```shell
php artisan operations:process // process operation files
php artisan operations:process // process all new operation files

php artisan operations:process --sync // force syncronously execution
php artisan operations:process --async // force asyncronously execution
php artisan operations:process --queue=<name> // force queue, that the job will be dispatched to
php artisan operations:process --test // dont flag operations as processed

php artisan operations:process --queue=<name> // force queue, that the job will be dispatched to
php artisan operations:process --tag=<tagname> // only process operations, that have the given tag

php artisan operations:process <operation_name> // re-run one specific operation
```

Expand Down Expand Up @@ -136,6 +140,11 @@ return new class extends OneTimeOperation
* The queue that the job will be dispatched to.
*/
protected string $queue = 'default';

/**
* A tag name, that this operation can be filtered by.
*/
protected ?string $tag = null;

/**
* Process the operation.
Expand Down Expand Up @@ -209,6 +218,40 @@ You can provide the `--queue` option in the artisan call. The given queue will b
php artisan operations:process --queue=redis // force redis queue
```

### Run only operations with a given tag

You can provide the `$tag` attribute in your operation file:

```php
<?php
// operations/XXXX_XX_XX_XXXXXX_awesome_operation.php

protected ?string $tag = "awesome";
};
```

That way you can filter operations with this specific tag when processing the operations:

```shell
php artisan operations:process --tag=awesome // run only operations with "awesome" tag
```

This is quite usefull if, for example, you want to process some of your operations before and some after the migrations:

```text
- php artisan operations:process --tag=before-migrations
- php artisan migrate
- php artisan operations:process
```

You can also provide multiple tags:

```shell
php artisan operations:process --tag=awesome --tag=foobar // run only operations with "awesome" or "foobar" tag
```

*Hint!* `operations:process` (without tags) still processes *all* operations, even if they have a tag.

### Re-run an operation

![One-Time Operations for Laravel - Re-run an operation manually](https://user-images.githubusercontent.com/65356688/224440344-3d095730-12c3-4a2c-b4c3-42a8b6d60767.png)
Expand Down
65 changes: 46 additions & 19 deletions src/Commands/OneTimeOperationShowCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace TimoKoerber\LaravelOneTimeOperations\Commands;

use Illuminate\Support\Collection;
use Throwable;
use TimoKoerber\LaravelOneTimeOperations\Commands\Utils\OperationsLineElement;
use TimoKoerber\LaravelOneTimeOperations\Models\Operation;
use TimoKoerber\LaravelOneTimeOperations\OneTimeOperationFile;
use TimoKoerber\LaravelOneTimeOperations\OneTimeOperationManager;

class OneTimeOperationShowCommand extends OneTimeOperationsCommand
Expand All @@ -22,29 +25,18 @@ public function handle(): int
{
try {
$this->validateFilters();

$operationModels = Operation::all();
$operationFiles = OneTimeOperationManager::getAllOperationFiles();
$this->newLine();

foreach ($operationModels as $operation) {
if (OneTimeOperationManager::fileExistsByName($operation->name)) {
continue;
}

$this->shouldDisplay(self::LABEL_DISPOSED) && $this->components->twoColumnDetail($operation->name, $this->gray($operation->processed_at).' '.$this->green(self::LABEL_DISPOSED));
}
$operationOutputLines = $this->getOperationLinesForOutput();
$operationOutputLines = $this->filterOperationLinesByStatus($operationOutputLines);

foreach ($operationFiles->toArray() as $file) {
if ($model = $file->getModel()) {
$this->shouldDisplay(self::LABEL_PROCESSED) && $this->components->twoColumnDetail($model->name, $this->gray($model->processed_at).' '.$this->brightgreen(self::LABEL_PROCESSED));
} else {
$this->shouldDisplay(self::LABEL_PENDING) && $this->components->twoColumnDetail($file->getOperationName(), $this->white(self::LABEL_PENDING));
}
if ($operationOutputLines->isEmpty()) {
$this->components->info('No operations found.');
}

if ($operationModels->isEmpty() && $operationFiles->isEmpty()) {
$this->components->info('No operations found.');
/** @var OperationsLineElement $lineElement */
foreach ($operationOutputLines as $lineElement) {
$lineElement->output($this->components);
}

$this->newLine();
Expand All @@ -68,7 +60,7 @@ protected function validateFilters(): void
throw_if(array_diff($filters, $validFilters), \Exception::class, 'Given filter is not valid. Allowed filters: '.implode('|', array_map('strtolower', $this->validFilters)));
}

protected function shouldDisplay(string $filterName): bool
protected function shouldDisplayByFilter(string $filterName): bool
{
$givenFilters = $this->argument('filter');

Expand All @@ -80,4 +72,39 @@ protected function shouldDisplay(string $filterName): bool

return in_array(strtolower($filterName), $givenFilters);
}

protected function getOperationLinesForOutput(): Collection
{
$operationModels = Operation::all();
$operationFiles = OneTimeOperationManager::getAllOperationFiles();
$operationOutputLines = collect();

// add disposed operations
foreach ($operationModels as $operation) {
if (OneTimeOperationManager::fileExistsByName($operation->name)) {
continue;
}

$operationOutputLines->add(OperationsLineElement::make($operation->name, self::LABEL_DISPOSED, $operation->processed_at));
}

// add processed and pending operations
foreach ($operationFiles->toArray() as $file) {
/** @var OneTimeOperationFile $file */
if ($model = $file->getModel()) {
$operationOutputLines->add(OperationsLineElement::make($model->name, self::LABEL_PROCESSED, $model->processed_at, $file->getClassObject()->getTag()));
} else {
$operationOutputLines->add(OperationsLineElement::make($file->getOperationName(), self::LABEL_PENDING, null, $file->getClassObject()->getTag()));
}
}

return $operationOutputLines;
}

protected function filterOperationLinesByStatus(Collection $operationOutputLines): Collection
{
return $operationOutputLines->filter(function (OperationsLineElement $lineElement) {
return $this->shouldDisplayByFilter($lineElement->getStatus());
})->collect();
}
}
44 changes: 6 additions & 38 deletions src/Commands/OneTimeOperationsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
namespace TimoKoerber\LaravelOneTimeOperations\Commands;

use Illuminate\Console\Command;
use TimoKoerber\LaravelOneTimeOperations\Commands\Utils\ColoredOutput;
use TimoKoerber\LaravelOneTimeOperations\OneTimeOperationManager;

abstract class OneTimeOperationsCommand extends Command
{
protected const LABEL_PROCESSED = 'PROCESSED';
use ColoredOutput;

protected const LABEL_PENDING = 'PENDING';
public const LABEL_PROCESSED = 'PROCESSED';

protected const LABEL_DISPOSED = 'DISPOSED';
public const LABEL_PENDING = 'PENDING';

public const LABEL_DISPOSED = 'DISPOSED';

protected string $operationsDirectory;

Expand All @@ -21,39 +24,4 @@ public function __construct()

$this->operationsDirectory = OneTimeOperationManager::getDirectoryPath();
}

protected function bold(string $message): string
{
return sprintf('<options=bold>%s</>', $message);
}

protected function lightgray(string $message): string
{
return sprintf('<fg=white>%s</>', $message);
}

protected function gray(string $message): string
{
return sprintf('<fg=gray>%s</>', $message);
}

protected function brightgreen(string $message): string
{
return sprintf('<fg=bright-green>%s</>', $message);
}

protected function green(string $message): string
{
return sprintf('<fg=green>%s</>', $message);
}

protected function white(string $message): string
{
return sprintf('<fg=bright-white>%s</>', $message);
}

protected function grayBadge(string $message): string
{
return sprintf('<fg=#fff;bg=gray>%s</>', $message);
}
}
57 changes: 54 additions & 3 deletions src/Commands/OneTimeOperationsProcessCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace TimoKoerber\LaravelOneTimeOperations\Commands;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use TimoKoerber\LaravelOneTimeOperations\Jobs\OneTimeOperationProcessJob;
use TimoKoerber\LaravelOneTimeOperations\Models\Operation;
use TimoKoerber\LaravelOneTimeOperations\OneTimeOperationFile;
Expand All @@ -14,7 +16,8 @@ class OneTimeOperationsProcessCommand extends OneTimeOperationsCommand
{--test : Process operation without tagging it as processed, so you can call it again}
{--async : Ignore setting in operation and process all operations asynchronously}
{--sync : Ignore setting in operation and process all operations synchronously}
{--queue= : Set the queue, that all jobs will be dispatched to}';
{--queue= : Set the queue, that all jobs will be dispatched to}
{--tag=* : Process only operations, that have one of the given tag}';

protected $description = 'Process all unprocessed one-time operations';

Expand All @@ -24,15 +27,24 @@ class OneTimeOperationsProcessCommand extends OneTimeOperationsCommand

protected ?string $queue = null;

protected array $tags = [];

public function handle(): int
{
$this->displayTestmodeWarning();

$this->forceAsync = (bool) $this->option('async');
$this->forceSync = (bool) $this->option('sync');
$this->queue = $this->option('queue');
$this->tags = $this->option('tag');

if (! $this->tagOptionsAreValid()) {
$this->components->error('Abort! Do not provide empty tags!');

if ($this->forceAsync && $this->forceSync) {
return self::FAILURE;
}

if (! $this->syncOptionsAreValid()) {
$this->components->error('Abort! Process either with --sync or --async.');

return self::FAILURE;
Expand Down Expand Up @@ -102,15 +114,21 @@ protected function processOperationModel(Operation $operationModel): int

protected function processNextOperations(): int
{
$processingOutput = 'Processing operations.';
$unprocessedOperationFiles = OneTimeOperationManager::getUnprocessedOperationFiles();

if ($this->tags) {
$processingOutput = sprintf('Processing operations with tags (%s)', Arr::join($this->tags, ','));
$unprocessedOperationFiles = $this->filterOperationsByTags($unprocessedOperationFiles);
}

if ($unprocessedOperationFiles->isEmpty()) {
$this->components->info('No operations to process.');

return self::SUCCESS;
}

$this->components->info('Processing operations.');
$this->components->info($processingOutput);

foreach ($unprocessedOperationFiles as $operationFile) {
$this->components->task($operationFile->getOperationName(), function () use ($operationFile) {
Expand All @@ -125,6 +143,11 @@ protected function processNextOperations(): int
return self::SUCCESS;
}

protected function tagMatched(OneTimeOperationFile $operationFile): bool
{
return in_array($operationFile->getClassObject()->getTag(), $this->tags);
}

protected function storeOperation(OneTimeOperationFile $operationFile): void
{
if ($this->testModeEnabled()) {
Expand Down Expand Up @@ -178,4 +201,32 @@ protected function getQueue(OneTimeOperationFile $operationFile): ?string

return $operationFile->getClassObject()->getQueue() ?: null;
}

protected function filterOperationsByTags(Collection $unprocessedOperationFiles): Collection
{
return $unprocessedOperationFiles->filter(function (OneTimeOperationFile $operationFile) {
return $this->tagMatched($operationFile);
})->collect();
}

protected function tagOptionsAreValid(): bool
{
// no tags provided
if (empty($this->tags)) {
return true;
}

// all tags are not empty
if (count($this->tags) === count(array_filter($this->tags))) {
return true;
}

return false;
}

protected function syncOptionsAreValid(): bool
{
// do not use both options at the same time
return ! ($this->forceAsync && $this->forceSync);
}
}
41 changes: 41 additions & 0 deletions src/Commands/Utils/ColoredOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace TimoKoerber\LaravelOneTimeOperations\Commands\Utils;

trait ColoredOutput
{
protected function bold(string $message): string
{
return sprintf('<options=bold>%s</>', $message);
}

protected function lightgray(string $message): string
{
return sprintf('<fg=white>%s</>', $message);
}

protected function gray(string $message): string
{
return sprintf('<fg=gray>%s</>', $message);
}

protected function brightgreen(string $message): string
{
return sprintf('<fg=bright-green>%s</>', $message);
}

protected function green(string $message): string
{
return sprintf('<fg=green>%s</>', $message);
}

protected function white(string $message): string
{
return sprintf('<fg=bright-white>%s</>', $message);
}

protected function grayBadge(string $message): string
{
return sprintf('<fg=#fff;bg=gray>%s</>', $message);
}
}
Loading

0 comments on commit 87b4089

Please sign in to comment.