Skip to content

Commit 87b4089

Browse files
authored
Merge pull request #8 from TimoKoerber/5-provide-tags
Provide tag to filter operations
2 parents 69d339c + bd241bc commit 87b4089

9 files changed

+382
-70
lines changed

README.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,15 @@ php artisan operations:make <operation_name> // create operation file
4646

4747
### Process operations
4848
```shell
49-
php artisan operations:process // process operation files
49+
php artisan operations:process // process all new operation files
50+
5051
php artisan operations:process --sync // force syncronously execution
5152
php artisan operations:process --async // force asyncronously execution
52-
php artisan operations:process --queue=<name> // force queue, that the job will be dispatched to
5353
php artisan operations:process --test // dont flag operations as processed
54+
55+
php artisan operations:process --queue=<name> // force queue, that the job will be dispatched to
56+
php artisan operations:process --tag=<tagname> // only process operations, that have the given tag
57+
5458
php artisan operations:process <operation_name> // re-run one specific operation
5559
```
5660

@@ -136,6 +140,11 @@ return new class extends OneTimeOperation
136140
* The queue that the job will be dispatched to.
137141
*/
138142
protected string $queue = 'default';
143+
144+
/**
145+
* A tag name, that this operation can be filtered by.
146+
*/
147+
protected ?string $tag = null;
139148

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

221+
### Run only operations with a given tag
222+
223+
You can provide the `$tag` attribute in your operation file:
224+
225+
```php
226+
<?php
227+
// operations/XXXX_XX_XX_XXXXXX_awesome_operation.php
228+
229+
protected ?string $tag = "awesome";
230+
};
231+
```
232+
233+
That way you can filter operations with this specific tag when processing the operations:
234+
235+
```shell
236+
php artisan operations:process --tag=awesome // run only operations with "awesome" tag
237+
```
238+
239+
This is quite usefull if, for example, you want to process some of your operations before and some after the migrations:
240+
241+
```text
242+
- php artisan operations:process --tag=before-migrations
243+
- php artisan migrate
244+
- php artisan operations:process
245+
```
246+
247+
You can also provide multiple tags:
248+
249+
```shell
250+
php artisan operations:process --tag=awesome --tag=foobar // run only operations with "awesome" or "foobar" tag
251+
```
252+
253+
*Hint!* `operations:process` (without tags) still processes *all* operations, even if they have a tag.
254+
212255
### Re-run an operation
213256

214257
![One-Time Operations for Laravel - Re-run an operation manually](https://user-images.githubusercontent.com/65356688/224440344-3d095730-12c3-4a2c-b4c3-42a8b6d60767.png)

src/Commands/OneTimeOperationShowCommand.php

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
namespace TimoKoerber\LaravelOneTimeOperations\Commands;
44

5+
use Illuminate\Support\Collection;
56
use Throwable;
7+
use TimoKoerber\LaravelOneTimeOperations\Commands\Utils\OperationsLineElement;
68
use TimoKoerber\LaravelOneTimeOperations\Models\Operation;
9+
use TimoKoerber\LaravelOneTimeOperations\OneTimeOperationFile;
710
use TimoKoerber\LaravelOneTimeOperations\OneTimeOperationManager;
811

912
class OneTimeOperationShowCommand extends OneTimeOperationsCommand
@@ -22,29 +25,18 @@ public function handle(): int
2225
{
2326
try {
2427
$this->validateFilters();
25-
26-
$operationModels = Operation::all();
27-
$operationFiles = OneTimeOperationManager::getAllOperationFiles();
2828
$this->newLine();
2929

30-
foreach ($operationModels as $operation) {
31-
if (OneTimeOperationManager::fileExistsByName($operation->name)) {
32-
continue;
33-
}
34-
35-
$this->shouldDisplay(self::LABEL_DISPOSED) && $this->components->twoColumnDetail($operation->name, $this->gray($operation->processed_at).' '.$this->green(self::LABEL_DISPOSED));
36-
}
30+
$operationOutputLines = $this->getOperationLinesForOutput();
31+
$operationOutputLines = $this->filterOperationLinesByStatus($operationOutputLines);
3732

38-
foreach ($operationFiles->toArray() as $file) {
39-
if ($model = $file->getModel()) {
40-
$this->shouldDisplay(self::LABEL_PROCESSED) && $this->components->twoColumnDetail($model->name, $this->gray($model->processed_at).' '.$this->brightgreen(self::LABEL_PROCESSED));
41-
} else {
42-
$this->shouldDisplay(self::LABEL_PENDING) && $this->components->twoColumnDetail($file->getOperationName(), $this->white(self::LABEL_PENDING));
43-
}
33+
if ($operationOutputLines->isEmpty()) {
34+
$this->components->info('No operations found.');
4435
}
4536

46-
if ($operationModels->isEmpty() && $operationFiles->isEmpty()) {
47-
$this->components->info('No operations found.');
37+
/** @var OperationsLineElement $lineElement */
38+
foreach ($operationOutputLines as $lineElement) {
39+
$lineElement->output($this->components);
4840
}
4941

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

71-
protected function shouldDisplay(string $filterName): bool
63+
protected function shouldDisplayByFilter(string $filterName): bool
7264
{
7365
$givenFilters = $this->argument('filter');
7466

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

8173
return in_array(strtolower($filterName), $givenFilters);
8274
}
75+
76+
protected function getOperationLinesForOutput(): Collection
77+
{
78+
$operationModels = Operation::all();
79+
$operationFiles = OneTimeOperationManager::getAllOperationFiles();
80+
$operationOutputLines = collect();
81+
82+
// add disposed operations
83+
foreach ($operationModels as $operation) {
84+
if (OneTimeOperationManager::fileExistsByName($operation->name)) {
85+
continue;
86+
}
87+
88+
$operationOutputLines->add(OperationsLineElement::make($operation->name, self::LABEL_DISPOSED, $operation->processed_at));
89+
}
90+
91+
// add processed and pending operations
92+
foreach ($operationFiles->toArray() as $file) {
93+
/** @var OneTimeOperationFile $file */
94+
if ($model = $file->getModel()) {
95+
$operationOutputLines->add(OperationsLineElement::make($model->name, self::LABEL_PROCESSED, $model->processed_at, $file->getClassObject()->getTag()));
96+
} else {
97+
$operationOutputLines->add(OperationsLineElement::make($file->getOperationName(), self::LABEL_PENDING, null, $file->getClassObject()->getTag()));
98+
}
99+
}
100+
101+
return $operationOutputLines;
102+
}
103+
104+
protected function filterOperationLinesByStatus(Collection $operationOutputLines): Collection
105+
{
106+
return $operationOutputLines->filter(function (OperationsLineElement $lineElement) {
107+
return $this->shouldDisplayByFilter($lineElement->getStatus());
108+
})->collect();
109+
}
83110
}

src/Commands/OneTimeOperationsCommand.php

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
namespace TimoKoerber\LaravelOneTimeOperations\Commands;
44

55
use Illuminate\Console\Command;
6+
use TimoKoerber\LaravelOneTimeOperations\Commands\Utils\ColoredOutput;
67
use TimoKoerber\LaravelOneTimeOperations\OneTimeOperationManager;
78

89
abstract class OneTimeOperationsCommand extends Command
910
{
10-
protected const LABEL_PROCESSED = 'PROCESSED';
11+
use ColoredOutput;
1112

12-
protected const LABEL_PENDING = 'PENDING';
13+
public const LABEL_PROCESSED = 'PROCESSED';
1314

14-
protected const LABEL_DISPOSED = 'DISPOSED';
15+
public const LABEL_PENDING = 'PENDING';
16+
17+
public const LABEL_DISPOSED = 'DISPOSED';
1518

1619
protected string $operationsDirectory;
1720

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

2225
$this->operationsDirectory = OneTimeOperationManager::getDirectoryPath();
2326
}
24-
25-
protected function bold(string $message): string
26-
{
27-
return sprintf('<options=bold>%s</>', $message);
28-
}
29-
30-
protected function lightgray(string $message): string
31-
{
32-
return sprintf('<fg=white>%s</>', $message);
33-
}
34-
35-
protected function gray(string $message): string
36-
{
37-
return sprintf('<fg=gray>%s</>', $message);
38-
}
39-
40-
protected function brightgreen(string $message): string
41-
{
42-
return sprintf('<fg=bright-green>%s</>', $message);
43-
}
44-
45-
protected function green(string $message): string
46-
{
47-
return sprintf('<fg=green>%s</>', $message);
48-
}
49-
50-
protected function white(string $message): string
51-
{
52-
return sprintf('<fg=bright-white>%s</>', $message);
53-
}
54-
55-
protected function grayBadge(string $message): string
56-
{
57-
return sprintf('<fg=#fff;bg=gray>%s</>', $message);
58-
}
5927
}

src/Commands/OneTimeOperationsProcessCommand.php

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

33
namespace TimoKoerber\LaravelOneTimeOperations\Commands;
44

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

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

@@ -24,15 +27,24 @@ class OneTimeOperationsProcessCommand extends OneTimeOperationsCommand
2427

2528
protected ?string $queue = null;
2629

30+
protected array $tags = [];
31+
2732
public function handle(): int
2833
{
2934
$this->displayTestmodeWarning();
3035

3136
$this->forceAsync = (bool) $this->option('async');
3237
$this->forceSync = (bool) $this->option('sync');
3338
$this->queue = $this->option('queue');
39+
$this->tags = $this->option('tag');
40+
41+
if (! $this->tagOptionsAreValid()) {
42+
$this->components->error('Abort! Do not provide empty tags!');
3443

35-
if ($this->forceAsync && $this->forceSync) {
44+
return self::FAILURE;
45+
}
46+
47+
if (! $this->syncOptionsAreValid()) {
3648
$this->components->error('Abort! Process either with --sync or --async.');
3749

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

103115
protected function processNextOperations(): int
104116
{
117+
$processingOutput = 'Processing operations.';
105118
$unprocessedOperationFiles = OneTimeOperationManager::getUnprocessedOperationFiles();
106119

120+
if ($this->tags) {
121+
$processingOutput = sprintf('Processing operations with tags (%s)', Arr::join($this->tags, ','));
122+
$unprocessedOperationFiles = $this->filterOperationsByTags($unprocessedOperationFiles);
123+
}
124+
107125
if ($unprocessedOperationFiles->isEmpty()) {
108126
$this->components->info('No operations to process.');
109127

110128
return self::SUCCESS;
111129
}
112130

113-
$this->components->info('Processing operations.');
131+
$this->components->info($processingOutput);
114132

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

146+
protected function tagMatched(OneTimeOperationFile $operationFile): bool
147+
{
148+
return in_array($operationFile->getClassObject()->getTag(), $this->tags);
149+
}
150+
128151
protected function storeOperation(OneTimeOperationFile $operationFile): void
129152
{
130153
if ($this->testModeEnabled()) {
@@ -178,4 +201,32 @@ protected function getQueue(OneTimeOperationFile $operationFile): ?string
178201

179202
return $operationFile->getClassObject()->getQueue() ?: null;
180203
}
204+
205+
protected function filterOperationsByTags(Collection $unprocessedOperationFiles): Collection
206+
{
207+
return $unprocessedOperationFiles->filter(function (OneTimeOperationFile $operationFile) {
208+
return $this->tagMatched($operationFile);
209+
})->collect();
210+
}
211+
212+
protected function tagOptionsAreValid(): bool
213+
{
214+
// no tags provided
215+
if (empty($this->tags)) {
216+
return true;
217+
}
218+
219+
// all tags are not empty
220+
if (count($this->tags) === count(array_filter($this->tags))) {
221+
return true;
222+
}
223+
224+
return false;
225+
}
226+
227+
protected function syncOptionsAreValid(): bool
228+
{
229+
// do not use both options at the same time
230+
return ! ($this->forceAsync && $this->forceSync);
231+
}
181232
}

src/Commands/Utils/ColoredOutput.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace TimoKoerber\LaravelOneTimeOperations\Commands\Utils;
4+
5+
trait ColoredOutput
6+
{
7+
protected function bold(string $message): string
8+
{
9+
return sprintf('<options=bold>%s</>', $message);
10+
}
11+
12+
protected function lightgray(string $message): string
13+
{
14+
return sprintf('<fg=white>%s</>', $message);
15+
}
16+
17+
protected function gray(string $message): string
18+
{
19+
return sprintf('<fg=gray>%s</>', $message);
20+
}
21+
22+
protected function brightgreen(string $message): string
23+
{
24+
return sprintf('<fg=bright-green>%s</>', $message);
25+
}
26+
27+
protected function green(string $message): string
28+
{
29+
return sprintf('<fg=green>%s</>', $message);
30+
}
31+
32+
protected function white(string $message): string
33+
{
34+
return sprintf('<fg=bright-white>%s</>', $message);
35+
}
36+
37+
protected function grayBadge(string $message): string
38+
{
39+
return sprintf('<fg=#fff;bg=gray>%s</>', $message);
40+
}
41+
}

0 commit comments

Comments
 (0)