feat(scheduler): dispatch event on job failure, notify admins via notifications module
Add ?ModuleEventDispatcher to SchedulerRunService (fail-open, nullable). Dispatch scheduler.job_failed event when a job fails with payload: job_id, job_key, label, error_code, error_message, trigger_type. New SchedulerJobFailedNotificationListener in notifications module creates in-app admin notifications for users with JOBS_MANAGE permission, scoped per tenant, with 30-min dedup per job_key. Also fixes audit module manifest: system_audit_purge job_key was mismatched (audit_system_purge vs system_audit_purge in DB). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ use MintyPHP\Domain\Taxonomy\SchedulerRuntimeResult;
|
||||
use MintyPHP\Repository\Scheduler\ScheduledJobRepositoryInterface;
|
||||
use MintyPHP\Repository\Scheduler\ScheduledJobRunRepositoryInterface;
|
||||
use MintyPHP\Repository\Scheduler\SchedulerRuntimeRepositoryInterface;
|
||||
use MintyPHP\App\Module\ModuleEventDispatcher;
|
||||
use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
use MintyPHP\Service\Audit\AuditRecorderInterface;
|
||||
@@ -32,7 +33,8 @@ class SchedulerRunService
|
||||
private readonly ScheduledJobRegistry $scheduledJobRegistry,
|
||||
private readonly ScheduleCalculator $scheduleCalculator,
|
||||
private readonly DatabaseSessionRepository $databaseSessionRepository,
|
||||
private readonly AuditRecorderInterface $systemAuditService
|
||||
private readonly AuditRecorderInterface $systemAuditService,
|
||||
private readonly ?ModuleEventDispatcher $eventDispatcher = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -265,6 +267,10 @@ class SchedulerRunService
|
||||
$run = ['status' => $status, 'error_code' => $errorCode, 'error_message' => $errorMessage];
|
||||
$this->recordSchedulerJobEvent($jobId, (string) ($job['job_key'] ?? ''), $triggerType, $actorUserId, $run, $durationMs);
|
||||
|
||||
if ($status === ScheduledJobRunStatus::Failed->value) {
|
||||
$this->dispatchJobFailedEvent($job, $errorCode, $errorMessage, $triggerType);
|
||||
}
|
||||
|
||||
return $run;
|
||||
}
|
||||
|
||||
@@ -410,6 +416,26 @@ class SchedulerRunService
|
||||
);
|
||||
}
|
||||
|
||||
private function dispatchJobFailedEvent(array $job, ?string $errorCode, ?string $errorMessage, string $triggerType): void
|
||||
{
|
||||
if (!$this->eventDispatcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->eventDispatcher->dispatch('scheduler.job_failed', [
|
||||
'job_id' => (int) ($job['id'] ?? 0),
|
||||
'job_key' => (string) ($job['job_key'] ?? ''),
|
||||
'label' => (string) ($job['label'] ?? ''),
|
||||
'error_code' => $errorCode,
|
||||
'error_message' => $errorMessage,
|
||||
'trigger_type' => $triggerType,
|
||||
]);
|
||||
} catch (\Throwable) {
|
||||
// fail-open: notification failure must not affect scheduler
|
||||
}
|
||||
}
|
||||
|
||||
private function recordAudit(string $eventType, string $outcome, array $context): void
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace MintyPHP\Service\Scheduler;
|
||||
|
||||
use MintyPHP\App\AppContainer;
|
||||
use MintyPHP\App\Module\ModuleEventDispatcher;
|
||||
use MintyPHP\Repository\Scheduler\ScheduledJobRepositoryInterface;
|
||||
use MintyPHP\Repository\Scheduler\ScheduledJobRunRepositoryInterface;
|
||||
use MintyPHP\Repository\Scheduler\SchedulerRuntimeRepositoryInterface;
|
||||
@@ -47,7 +48,8 @@ class SchedulerServicesFactory
|
||||
$this->getScheduledJobRegistry(),
|
||||
$this->getScheduleCalculator(),
|
||||
$this->getDatabaseSessionRepository(),
|
||||
$this->getAuditRecorder()
|
||||
$this->getAuditRecorder(),
|
||||
$this->getEventDispatcher()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,4 +101,13 @@ class SchedulerServicesFactory
|
||||
return $this->auditRecorder;
|
||||
}
|
||||
|
||||
private function getEventDispatcher(): ?ModuleEventDispatcher
|
||||
{
|
||||
if ($this->appContainer->has(ModuleEventDispatcher::class)) {
|
||||
$dispatcher = $this->appContainer->get(ModuleEventDispatcher::class);
|
||||
return $dispatcher instanceof ModuleEventDispatcher ? $dispatcher : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user