Skip to content

Commit

Permalink
Add cron job execution state
Browse files Browse the repository at this point in the history
  • Loading branch information
DumitracheAdrian committed Apr 23, 2024
1 parent db27d1a commit a5a6c8e
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 13 deletions.
33 changes: 33 additions & 0 deletions app/migrations/Version20240423100707.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240423100707 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE cron_job__cron_job_execution ADD state VARCHAR(20) DEFAULT \'requested\' NOT NULL');
$this->addSql('CREATE INDEX state ON cron_job__cron_job_execution (state)');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX state ON cron_job__cron_job_execution');
$this->addSql('ALTER TABLE cron_job__cron_job_execution DROP state');
}
}
6 changes: 4 additions & 2 deletions packages/cron-job/CronJobProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@ public function queue(CronJob $cronJob, bool $force): void

public function process(CronJobExecution $execution): void
{
$manager = $this->managerRegistry->getManagerForClass(CronJobExecution::class);
$event = $this->eventDispatcher->dispatch(new PreCronJobExecutionEvent($execution));

if ($event->isExecutionCancelled()) {
$execution->skip();
$manager->flush();

return;
}

$manager = $this->managerRegistry->getManagerForClass(CronJobExecution::class);

$execution->start();
$manager->flush();

Expand Down
38 changes: 38 additions & 0 deletions packages/cron-job/Entity/CronJobExecution.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,24 @@
#[
ORM\Entity,
ORM\Table(name: 'cron_job__cron_job_execution'),
ORM\Index(fields: ['state'], name: 'state'),
]
class CronJobExecution implements \Stringable
{
public const STATE_REQUESTED = 'requested';
public const STATE_RUNNING = 'running';
public const STATE_TERMINATED = 'terminated';
public const STATE_ERRORED = 'errored';
public const STATE_SKIPPED = 'skipped';

public const STATES = [
self::STATE_REQUESTED,
self::STATE_RUNNING,
self::STATE_TERMINATED,
self::STATE_ERRORED,
self::STATE_SKIPPED,
];

#[
ORM\Id,
ORM\GeneratedValue,
Expand All @@ -22,6 +37,9 @@ class CronJobExecution implements \Stringable
#[ORM\Column(name: 'requested_at', type: 'datetime_immutable', nullable: false)]
private \DateTimeImmutable $requestedAt;

#[ORM\Column(name: 'state', type: 'string', length: 20, nullable: false, options: ['default' => self::STATE_REQUESTED])]
private string $state = self::STATE_REQUESTED;

#[ORM\Column(name: '`force`', type: 'boolean', nullable: false, options: ['default' => false])]
private bool $force;

Expand Down Expand Up @@ -74,6 +92,18 @@ public function getRequestedAt(): ?\DateTimeImmutable
return $this->requestedAt;
}

public function getState(): string
{
return $this->state;
}

private function setState(string $state): self
{
$this->state = $state;

return $this;
}

public function isForce(): bool
{
return $this->force;
Expand Down Expand Up @@ -166,13 +196,15 @@ public function isExecutable(\DateTimeImmutable $dateTime): bool
public function start(): void
{
$this
->setState(self::STATE_RUNNING)
->setExecutionStartedAt(new \DateTimeImmutable())
->setExecutionEndedAt(null);
}

public function end(): static
{
$this
->setState(self::STATE_TERMINATED)
->setExitCode(0)
->setExecutionEndedAt($executionEndedAt = new \DateTimeImmutable())
->setExecutionDelay(
Expand All @@ -186,10 +218,16 @@ public function fail(?int $exitCode, ?array $error): void
{
$this
->end()
->setState(self::STATE_ERRORED)
->setExitCode($exitCode)
->setError($error);
}

public function skip(): void
{
$this->setState(self::STATE_SKIPPED);
}

public function __toString(): string
{
return implode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

namespace Draw\Component\CronJob\MessageHandler;

use Doctrine\Persistence\ManagerRegistry;
use Draw\Component\CronJob\CronJobProcessor;
use Draw\Component\CronJob\Entity\CronJobExecution;
use Draw\Component\CronJob\Message\ExecuteCronJobMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

class ExecuteCronJobMessageHandler
{
public function __construct(
private ManagerRegistry $managerRegistry,
private CronJobProcessor $cronJobProcessor,
) {
}
Expand All @@ -19,6 +22,12 @@ public function __construct(
public function handleExecuteCronJobMessage(ExecuteCronJobMessage $message): void
{
if (!($execution = $message->getExecution())->isExecutable(new \DateTimeImmutable())) {
$execution->skip();

$this->managerRegistry
->getManagerForClass(CronJobExecution::class)
->flush();

return;
}

Expand Down
6 changes: 5 additions & 1 deletion packages/cron-job/Tests/CronJobProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ public function testProcess(

$this->cronJobProcessor->process($execution);

static::assertEquals(CronJobExecution::STATE_TERMINATED, $execution->getState());
static::assertNotNull($execution->getExecutionStartedAt());
static::assertNotNull($execution->getExecutionEndedAt());
static::assertEquals(
Expand Down Expand Up @@ -225,6 +226,7 @@ public function testProcessWithError(): void

$this->cronJobProcessor->process($execution);

static::assertEquals(CronJobExecution::STATE_ERRORED, $execution->getState());
static::assertNotNull($execution->getExecutionStartedAt());
static::assertNotNull($execution->getExecutionEndedAt());
static::assertNotNull($execution->getExecutionDelay());
Expand All @@ -245,14 +247,16 @@ public function testProcessWithCancelledExecution(): void
);

$this->entityManager
->expects(static::never())
->expects(static::once())
->method('flush');

$this->processFactory
->expects(static::never())
->method('createFromShellCommandLine');

$this->cronJobProcessor->process($execution);

static::assertEquals(CronJobExecution::STATE_SKIPPED, $execution->getState());
}

private function createCronJobExecution(string $command = 'bin/console draw:test:execute'): CronJobExecution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

namespace Draw\Component\CronJob\Tests\MessageHandler;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Draw\Component\CronJob\CronJobProcessor;
use Draw\Component\CronJob\Entity\CronJob;
use Draw\Component\CronJob\Entity\CronJobExecution;
use Draw\Component\CronJob\Message\ExecuteCronJobMessage;
use Draw\Component\CronJob\MessageHandler\ExecuteCronJobMessageHandler;
Expand All @@ -19,17 +22,31 @@ class ExecuteCronJobMessageHandlerTest extends TestCase

private CronJobProcessor&MockObject $cronJobProcessor;

private EntityManagerInterface&MockObject $entityManager;

protected function setUp(): void
{
parent::setUp();

$managerRegistry = $this->createMock(ManagerRegistry::class);
$managerRegistry
->expects(static::any())
->method('getManagerForClass')
->with(CronJobExecution::class)
->willReturn($this->entityManager = $this->createMock(EntityManagerInterface::class));

$this->handler = new ExecuteCronJobMessageHandler(
$managerRegistry,
$this->cronJobProcessor = $this->createMock(CronJobProcessor::class)
);
}

public function testHandleExecuteCronJobMessage(): void
{
$this->entityManager
->expects(static::never())
->method('flush');

$this->cronJobProcessor
->expects(static::once())
->method('process')
Expand All @@ -38,27 +55,35 @@ public function testHandleExecuteCronJobMessage(): void
$this->handler->handleExecuteCronJobMessage(
new ExecuteCronJobMessage($execution)
);

static::assertEquals(CronJobExecution::STATE_REQUESTED, $execution->getState());
static::assertNotNull($execution->getRequestedAt());
}

public function testHandleExecuteCronJobMessageWithNotExecutableExecution(): void
{
$this->entityManager
->expects(static::once())
->method('flush');

$this->cronJobProcessor
->expects(static::never())
->method('process');

$this->handler->handleExecuteCronJobMessage(
new ExecuteCronJobMessage($this->createCronJobExecution(false))
new ExecuteCronJobMessage($execution = $this->createCronJobExecution(false))
);

static::assertEquals(CronJobExecution::STATE_SKIPPED, $execution->getState());
static::assertNotNull($execution->getRequestedAt());
}

private function createCronJobExecution(bool $executable = true): CronJobExecution&MockObject
private function createCronJobExecution(bool $active = true): CronJobExecution
{
$execution = $this->createMock(CronJobExecution::class);
$execution
->expects(static::any())
->method('isExecutable')
->willReturn($executable);

return $execution;
return new CronJobExecution(
(new CronJob())->setActive($active),
new \DateTimeImmutable(),
false
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

namespace Draw\Bundle\SonataIntegrationBundle\CronJob\Admin;

use Draw\Component\CronJob\Entity\CronJobExecution;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\Type\ModelAutocompleteType;
use Sonata\AdminBundle\Route\RouteCollectionInterface;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\DoctrineORMAdminBundle\Filter\ChoiceFilter;
use Sonata\DoctrineORMAdminBundle\Filter\ModelFilter;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;

class CronJobExecutionAdmin extends AbstractAdmin
{
Expand All @@ -29,6 +32,21 @@ protected function configureDatagridFilters(DatagridMapper $filter): void
]
)
->add('requestedAt')
->add(
'state',
ChoiceFilter::class,
[
'field_type' => ChoiceType::class,
'field_options' => [
'multiple' => true,
'choices' => array_combine(
CronJobExecution::STATES,
CronJobExecution::STATES
),
],
'show_filter' => true,
]
)
->add('force')
->add('executionStartedAt')
->add('executionEndedAt')
Expand All @@ -53,6 +71,7 @@ protected function configureListFields(ListMapper $list): void
]
)
->add('requestedAt')
->add('state')
->add('force')
->add('executionStartedAt')
->add('executionEndedAt')
Expand All @@ -74,6 +93,7 @@ protected function configureShowFields(ShowMapper $show): void
{
$show
->add('requestedAt')
->add('state')
->add('force')
->add('executionStartedAt')
->add('executionEndedAt')
Expand All @@ -84,7 +104,8 @@ protected function configureShowFields(ShowMapper $show): void

protected function configureRoutes(RouteCollectionInterface $collection): void
{
$collection->clearExcept(['list', 'show', 'delete']);
$collection->remove('create');
$collection->remove('edit');
}

public function configureGridFields(array $fields): array
Expand All @@ -93,6 +114,7 @@ public function configureGridFields(array $fields): array
$fields,
[
'requestedAt' => [],
'state' => [],
'force' => [],
'executionStartedAt' => [],
'executionEndedAt' => [],
Expand Down

0 comments on commit a5a6c8e

Please sign in to comment.