Skip to content

Commit

Permalink
[feature] add task "extra config"
Browse files Browse the repository at this point in the history
  • Loading branch information
kbond committed Jul 16, 2020
1 parent 05a9c99 commit 7922464
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,7 @@ zenstruck_schedule:

# Email subject (leave blank to use extension default)
subject: null

# Additional Configuration/Metadata
config: []
```
12 changes: 12 additions & 0 deletions src/Command/ScheduleListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ private function renderDetail(Schedule $schedule, SymfonyStyle $io): int
$details[] = ['Next Run' => $task->getNextRun()->format('D, M d, Y @ g:i (e O)')];

$this->renderDefinitionList($io, $details);

$config = [];

foreach ($task->config()->humanized() as $key => $value) {
$config[] = [$key => $value];
}

if (\count($config)) {
$io->block('Additional Configuration:');
$this->renderDefinitionList($io, $config);
}

$this->renderExtenstions($io, 'Task', $task->getExtensions());

$issues = \iterator_to_array($this->getTaskIssues($task), false);
Expand Down
4 changes: 4 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ private static function taskConfiguration(): ArrayNodeDefinition
->append(self::createPingExtension('ping_on_failure', 'Ping a url if the task failed'))
->append(self::createEmailExtension('email_after', 'Send email after task runs'))
->append(self::createEmailExtension('email_on_failure', 'Send email if task fails'))
->arrayNode('config')
->info('Additional Configuration/Metadata')
->scalarPrototype()->end()
->end()
->end()
->end()
;
Expand Down
4 changes: 4 additions & 0 deletions src/EventListener/TaskConfigurationSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ private function addTask(Schedule $schedule, array $config): void
$task->emailOnFailure($config['email_on_failure']['to'], $config['email_on_failure']['subject']);
}

foreach ($config['config'] as $key => $value) {
$task->config()->set($key, $value);
}

$schedule->add($task);
}

Expand Down
11 changes: 11 additions & 0 deletions src/Schedule/Task.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Zenstruck\ScheduleBundle\Schedule\Extension\PingExtension;
use Zenstruck\ScheduleBundle\Schedule\Extension\SingleServerExtension;
use Zenstruck\ScheduleBundle\Schedule\Extension\WithoutOverlappingExtension;
use Zenstruck\ScheduleBundle\Schedule\Task\Config;
use Zenstruck\ScheduleBundle\Schedule\Task\TaskRunContext;

/**
Expand All @@ -30,10 +31,12 @@ abstract class Task
private $description;
private $expression = self::DEFAULT_EXPRESSION;
private $timezone;
private $config;

public function __construct(string $description)
{
$this->description = $description;
$this->config = new Config();
}

final public function __toString(): string
Expand Down Expand Up @@ -91,6 +94,14 @@ final public function description(string $description): self
return $this;
}

/**
* Set extra configuration/metadata for this task.
*/
final public function config(): Config
{
return $this->config;
}

/**
* The timezone this task should run in.
*
Expand Down
4 changes: 4 additions & 0 deletions src/Schedule/Task/CompoundTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ public function getIterator(): iterable
$task->addExtension($extension);
}

foreach ($this->config()->all() as $key => $value) {
$task->config()->set($key, $task->config()->get($key, $value));
}

yield $task;
}
}
Expand Down
65 changes: 65 additions & 0 deletions src/Schedule/Task/Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace Zenstruck\ScheduleBundle\Schedule\Task;

/**
* @author Kevin Bond <[email protected]>
*/
final class Config
{
private $data = [];

/**
* @param mixed $value
*/
public function set(string $name, $value): self
{
$this->data[$name] = $value;

return $this;
}

/**
* @param mixed $default
*
* @return mixed
*/
public function get(string $name, $default = null)
{
return $this->data[$name] ?? $default;
}

public function all(): array
{
return $this->data;
}

public function humanized(): array
{
$ret = [];

foreach ($this->data as $key => $value) {
switch (true) {
case \is_bool($value):
$value = $value ? 'yes' : 'no';

break;

case \is_scalar($value):
break;

case \is_object($value):
$value = \sprintf('(%s)', \get_class($value));

break;

default:
$value = \sprintf('(%s)', \gettype($value));
}

$ret[$key] = $value;
}

return $ret;
}
}
37 changes: 37 additions & 0 deletions tests/Command/ScheduleListCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,43 @@ public function shows_schedule_issue_for_duplicate_task_id()
$this->assertStringContainsString('[ERROR] Task "MockTask: task3" (* * * * *) is duplicated 3 times. Make their descriptions unique to fix.', $output);
}

/**
* @test
*/
public function shows_extra_configuration_in_detail_view(): void
{
$task = (new MockTask('my task'))->cron('@daily');
$task->config()->set('config1', 'config1 value');
$task->config()->set('config2', ['an', 'array']);
$task->config()->set('config3', true);
$task->config()->set('config4', false);
$task->config()->set('config5', new Schedule());
$runner = (new MockScheduleBuilder())
->addTask($task)
->getRunner()
;

$command = new ScheduleListCommand($runner, new ExtensionHandlerRegistry([]));
$command->setHelperSet(new HelperSet([new FormatterHelper()]));
$command->setApplication(new Application());
$commandTester = new CommandTester($command);

$commandTester->execute(['--detail' => null]);
$output = $this->normalizeOutput($commandTester);

$this->assertStringContainsString('Additional Configuration', $output);
$this->assertStringContainsString('config1', $output);
$this->assertStringContainsString('config1 value', $output);
$this->assertStringContainsString('config2', $output);
$this->assertStringContainsString('(array)', $output);
$this->assertStringContainsString('config3', $output);
$this->assertStringContainsString('yes', $output);
$this->assertStringContainsString('config4', $output);
$this->assertStringContainsString('no', $output);
$this->assertStringContainsString('config5', $output);
$this->assertStringContainsString('('.Schedule::class.')', $output);
}

private function normalizeOutput(CommandTester $tester): string
{
return \preg_replace('/\s+/', ' ', \str_replace("\n", '', $tester->getDisplay(true)));
Expand Down
19 changes: 19 additions & 0 deletions tests/DependencyInjection/ZenstruckScheduleExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,25 @@ public function between_and_unless_between_config_can_be_shortened()
$this->assertSame('13:15', $config['unless_between']['end']);
}

/**
* @test
*/
public function task_config_must_be_an_array(): void
{
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('Invalid type for path "zenstruck_schedule.tasks.0.config"');

$this->load([
'tasks' => [
[
'task' => 'my:command',
'frequency' => '0 * * * *',
'config' => 'not an array',
],
],
]);
}

protected function getContainerExtensions(): array
{
return [new ZenstruckScheduleExtension()];
Expand Down
25 changes: 25 additions & 0 deletions tests/EventListener/TaskConfigurationSubscriberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,31 @@ public function full_task_configuration()
$this->assertSame('On Task Failure, email output to "[email protected]"', (string) $extensions[8]);
}

/**
* @test
*/
public function can_add_task_config(): void
{
$schedule = $this->createSchedule([
[
'task' => 'my:command',
'frequency' => '0 * * * *',
'config' => [
'foo' => 'bar',
'bar' => 'foo',
],
],
]);

$this->assertSame(
[
'foo' => 'bar',
'bar' => 'foo',
],
$schedule->all()[0]->config()->all()
);
}

private function createSchedule(array $taskConfig): Schedule
{
$processor = new Processor();
Expand Down
38 changes: 38 additions & 0 deletions tests/Schedule/Task/CompoundTaskTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PHPUnit\Framework\TestCase;
use Zenstruck\ScheduleBundle\Schedule\Task\CompoundTask;
use Zenstruck\ScheduleBundle\Tests\Fixture\MockTask;

/**
* @author Kevin Bond <[email protected]>
Expand All @@ -22,4 +23,41 @@ public function cannot_nest_compound_tasks()

$task->add(new CompoundTask());
}

/**
* @test
*/
public function config_is_passed_to_sub_tasks(): void
{
$task = new CompoundTask();
$task->add(new MockTask('subtask1'));
$task->add(new MockTask('subtask2'));
$task->config()->set('foo', 'bar');
$task->config()->set('bar', 'foo');

[$subtask1, $subtask2] = \iterator_to_array($task);

$this->assertSame('bar', $subtask1->config()->get('foo'));
$this->assertSame('foo', $subtask1->config()->get('bar'));
$this->assertSame('bar', $subtask2->config()->get('foo'));
$this->assertSame('foo', $subtask2->config()->get('bar'));
}

/**
* @test
*/
public function config_on_sub_tasks_takes_precedence_over_compound_task(): void
{
$subTask = new MockTask('subtask');
$subTask->config()->set('key2', 'subtask value2');
$task = new CompoundTask();
$task->config()->set('key1', 'compound value1');
$task->config()->set('key2', 'compound value2');
$task->add($subTask);

[$subTask] = \iterator_to_array($task);

$this->assertSame('compound value1', $subTask->config()->get('key1'));
$this->assertSame('subtask value2', $subTask->config()->get('key2'));
}
}
43 changes: 43 additions & 0 deletions tests/Schedule/TaskTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PHPUnit\Framework\TestCase;
use Symfony\Component\Mime\Email;
use Zenstruck\ScheduleBundle\Schedule;
use Zenstruck\ScheduleBundle\Schedule\Extension\SingleServerExtension;
use Zenstruck\ScheduleBundle\Schedule\Task;
use Zenstruck\ScheduleBundle\Tests\Fixture\MockTask;
Expand Down Expand Up @@ -207,6 +208,48 @@ public function can_add_single_server_extension()
$this->assertInstanceOf(SingleServerExtension::class, $task->getExtensions()[0]);
}

/**
* @test
*/
public function can_add_and_retrieve_task_config(): void
{
$task = self::task();
$task->config()->set('foo', 'bar');
$task->config()->set('bar', 'baz')->set('baz', 'foo');

$this->assertSame('bar', $task->config()->get('foo'));
$this->assertSame('baz', $task->config()->get('bar'));
$this->assertSame('foo', $task->config()->get('baz'));
$this->assertNull($task->config()->get('invalid'));
$this->assertSame('default', $task->config()->get('invalid', 'default'));
$this->assertSame([
'foo' => 'bar',
'bar' => 'baz',
'baz' => 'foo',
], $task->config()->all());
}

/**
* @test
*/
public function can_get_humanized_config(): void
{
$task = self::task();
$task->config()->set('number', 2);
$task->config()->set('true', true);
$task->config()->set('false', false);
$task->config()->set('array', ['bar']);
$task->config()->set('object', new Schedule());

$this->assertSame([
'number' => 2,
'true' => 'yes',
'false' => 'no',
'array' => '(array)',
'object' => '('.Schedule::class.')',
], $task->config()->humanized());
}

private static function task(string $description = 'task description'): Task
{
return new MockTask($description);
Expand Down

0 comments on commit 7922464

Please sign in to comment.