Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Latest
* [#84](https://github.com/cleverage/ui-process-bundle/issues/84) Add `LogRecord::hasContextInfo()`, deprecate the misnamed `LogRecord::contextIsEmpty()` (it returns `true` when the context is not empty). Add tests.
* [#85](https://github.com/cleverage/ui-process-bundle/issues/85) `ProcessHandler`: default report increment level aligned on the bundle configuration (`Warning`); declare the `symfony/ux-twig-component` dependency. Add tests.
* [#48](https://github.com/cleverage/ui-process-bundle/issues/48) Executions list: the duration is rendered by the `@CleverAgeUiProcess/admin/field/duration.html.twig` template, so that its format can be changed by overriding the template instead of `ProcessExecutionCrudController` (the `$translator` argument of its constructor is no longer used: deprecated, will be removed in 4.0). Add tests.
* [#62](https://github.com/cleverage/ui-process-bundle/issues/62) Notify the end of the process executions with Symfony Notifier (optional `symfony/notifier` dependency): `notification` bundle configuration (`enabled`, default `false`; `statuses` among `failed`, `finish_with_report` (finished with log levels counted in its report), `finish`, default `[failed, finish_with_report]`; `channels`, default the notifier channel policy; `recipients`, default the notifier admin recipients), overridden by the `notification` option of each process. Add the `ProcessExecutionEndedEvent`, dispatched when a top-level process execution has ended and has been saved (new optional `$eventDispatcher` argument of `ProcessEventSubscriber`). Add documentation and a cookbook. Add tests.
* [#129](https://github.com/cleverage/ui-process-bundle/issues/129) CI: `migrations` job running the migrations on MySQL, MariaDB and PostgreSQL, with DBAL 3 and 4 (`migrate`, `doctrine:schema:validate`, `migrate first`, `migrate` again); console of the test application (`tests/App/bin/console`), database URL overridable with `DATABASE_URL` (SQLite by default).

## Fixes
Expand Down
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,13 @@
"symfony/css-selector": "^6.4 || ^7.4 || ^8",
"symfony/debug-bundle": "^6.4 || ^7.4 || ^8",
"symfony/maker-bundle": "^1.31",
"symfony/notifier": "^6.4 || ^7.4 || ^8",
"symfony/web-profiler-bundle": "^6.4 || ^7.4 || ^8",
"vincentlanglet/twig-cs-fixer": "^3.11"
},
"suggest": {
"symfony/notifier": "To notify the end of the process executions"
},
"conflict": {
"symfony/twig-bridge": "7.2.0",
"twig/twig": "v3.15.0|v3.16.0"
Expand Down
1 change: 1 addition & 0 deletions config/services/event_subscriber.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ services:
- '@cleverage_ui_process.monolog_handler.process'
- '@cleverage_ui_process.monolog_handler.doctrine_process'
- '@cleverage_ui_process.manager.process_execution'
- '@event_dispatcher'
13 changes: 13 additions & 0 deletions config/services/notifier.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
services:
# Removed by the extension when symfony/notifier is not installed
cleverage_ui_process.notifier.process_execution:
class: CleverAge\UiProcessBundle\Notifier\ProcessExecutionNotifier
public: false
tags:
- { name: 'kernel.event_subscriber' }
- { name: 'monolog.logger', channel: 'cleverage_ui_process' }
arguments:
$processConfigurationsManager: '@cleverage_ui_process.manager.process_configuration'
$defaultOptions: [] # set by the extension
$notifier: '@?notifier'
$logger: '@?logger'
71 changes: 71 additions & 0 deletions docs/cookbooks/notify_process_failures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
Notify the failures of the processes on Slack and by email
=========================================================

This recipe sends a Slack message when a process fails or ends with warnings, and an email to the sales team at the
end of every run of their daily import.

Install the notifier and the Slack bridge:

```bash
composer require symfony/notifier symfony/slack-notifier
```

Configure the notifier: the failures (`high` importance) go to Slack and to the admin recipients by email, the
warnings (`medium` importance) to Slack only:

```yaml
# config/packages/notifier.yaml
framework:
notifier:
chatter_transports:
slack: '%env(SLACK_DSN)%' # e.g. slack://TOKEN@default?channel=CHANNEL
channel_policy:
high: ['chat/slack', 'email']
medium: ['chat/slack']
low: ['chat/slack']
admin_recipients:
- { email: 'ops@example.com' }
```

The `email` channel requires `symfony/mailer` (and its `MAILER_DSN`).

Enable the notifications for every process, with the default statuses (`failed`, `finish_with_report`):

```yaml
# config/packages/clever_age_ui_process.yaml
clever_age_ui_process:
notification:
enabled: true
```

Then override it for the processes needing it:

```yaml
# config/packages/process/app.daily_import.yaml
clever_age_process:
configurations:
app.daily_import:
options:
notification:
statuses: [failed, finish_with_report, finish] # every run
channels: ['email']
recipients:
- { email: 'sales@example.com' }
tasks:
# ...
app.cache_warmup:
options:
notification:
enabled: false
tasks:
# ...
```

- A failed process sends a Slack message and an email to `ops@example.com`, e.g. `Process "app.purge_authors"
failed`, with the error, the duration and the log file of the execution.
- A process ending with warnings (`Warning` or higher logs, see `logs.report_increment_level`) sends a Slack message,
e.g. `Process "app.purge_authors" finished with reported logs`, with its report (`Warning: 3, Error: 1`).
- Every run of `app.daily_import` sends an email to `sales@example.com`, `app.cache_warmup` is never notified.

See [notifications](../reference/09-notifications.md) for the whole configuration and the
`ProcessExecutionEndedEvent` to send your own notifications.
5 changes: 4 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Launch a CSV import from a form with file upload](cookbooks/form_file_upload.md)
- [Schedule a recurring process](cookbooks/scheduled_process.md)
- [Launch a process through the HTTP API](cookbooks/http_api_launch.md)
- [Notify the failures of the processes on Slack and by email](cookbooks/notify_process_failures.md)
- Reference
- [Bundle configuration](reference/01-bundle_configuration.md)
- [Process UI options](reference/02-process_ui_options.md)
Expand All @@ -16,6 +17,7 @@
- [HTTP API](reference/06-http_api.md)
- [Console commands](reference/07-console_commands.md)
- [Messenger & asynchronous execution](reference/08-messenger.md)
- [Notifications](reference/09-notifications.md)
- [Troubleshooting](troubleshooting.md)
- [CleverAge/ProcessBundle documentation](https://github.com/cleverage/process-bundle/blob/main/docs/index.md)

Expand All @@ -31,7 +33,8 @@ on top of the process bundle. It does not provide any process task. Its features
status, duration, report and logs (stored in database and in a log file),
- a scheduler to run processes periodically (cron or periodical expressions),
- user management (login form, roles, API tokens),
- an HTTP endpoint to launch a process from another application.
- an HTTP endpoint to launch a process from another application,
- notifications of the end of the process executions (failures, warnings...), with Symfony Notifier.

It relies on Doctrine ORM (users, executions, logs, schedules), Symfony Messenger (asynchronous execution), Symfony
Scheduler and Monolog.
Expand Down
17 changes: 17 additions & 0 deletions docs/reference/01-bundle_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ clever_age_ui_process:
report_increment_level: Warning
design:
logo_path: 'bundles/cleverageuiprocess/logo.jpg'
notification:
enabled: false
statuses: [failed, finish_with_report]
channels: []
recipients: []
```

Options
Expand Down Expand Up @@ -49,6 +54,18 @@ Levels are Monolog level names (case-insensitive): `Debug`, `Info`, `Notice`, `W
|-------------|----------|-----------------------------------------|------------------------------------------------------------------------------------------------------------|
| `logo_path` | `string` | `bundles/cleverageuiprocess/logo.jpg` | Path, relative to the public directory, of the logo displayed in the UI navigation. The default logo requires `bin/console assets:install`. |

### notification

Notification of the end of the process executions, with [symfony/notifier](https://symfony.com/doc/current/notifier.html)
(optional dependency). Each key can be overridden by process. See [notifications](09-notifications.md).

| Key | Type | Default | Description |
|--------------|------------|----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| `enabled` | `bool` | `false` | Notify the end of the process executions. `true` requires `symfony/notifier`. |
| `statuses` | `string[]` | `[failed, finish_with_report]` | Ends of process executions to notify: `failed`, `finish_with_report` (finished with log levels counted in its report), `finish` (finished without them). |
| `channels` | `string[]` | `[]` | Notifier channels, e.g. `chat/slack`, `email`. Empty: the `channel_policy` of the notifier, by importance. |
| `recipients` | `array[]` | `[]` | Recipients, with an `email` and/or a `phone`. Empty: the `admin_recipients` of the notifier. |

Container parameters
--------------------

Expand Down
3 changes: 3 additions & 0 deletions docs/reference/02-process_ui_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ clever_age_process:
Every key is optional: a process without `options.ui` is displayed and launched with a confirmation modal. Unknown
keys, or invalid values, raise an options resolver error when the process list is displayed.

The `notification` key, next to `ui`, configures the [notification](09-notifications.md) of the end of the process
executions.

Options
-------

Expand Down
137 changes: 137 additions & 0 deletions docs/reference/09-notifications.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
Notifications
=============

The end of a process execution can be notified (Slack, Teams, email, SMS...) with
[symfony/notifier](https://symfony.com/doc/current/notifier.html), whatever the way the process was launched (UI,
console, HTTP API, scheduler). Only the top-level process is notified, not its sub-processes.

Installation
------------

`symfony/notifier` is an optional dependency of the bundle:

```bash
composer require symfony/notifier
```

Then configure the notifier itself (transports, channel policy, admin recipients), see the
[Symfony documentation](https://symfony.com/doc/current/notifier.html), e.g.:

```yaml
# config/packages/notifier.yaml
framework:
notifier:
chatter_transports:
slack: '%env(SLACK_DSN)%'
channel_policy:
high: ['chat/slack', 'email']
medium: ['chat/slack']
low: ['chat/slack']
admin_recipients:
- { email: 'ops@example.com' }
```

Enabling `clever_age_ui_process.notification.enabled` without `symfony/notifier` installed raises an error when
the container is built. If `symfony/notifier` is installed but the notifier is not enabled (`framework.notifier`),
nothing is sent and a warning is logged.

Configuration
-------------

The notifications are disabled by default. They are configured for every process in the
[bundle configuration](01-bundle_configuration.md#notification), and each key can be overridden by process, under the
`notification` key of its options (next to the [`ui` options](02-process_ui_options.md)):

```yaml
# config/packages/clever_age_ui_process.yaml
clever_age_ui_process:
notification:
enabled: true
statuses: [failed, finish_with_report] # default

# config/packages/process/app.daily_import.yaml
clever_age_process:
configurations:
app.daily_import:
options:
notification:
statuses: [failed, finish_with_report, finish] # also notify the successful imports
channels: ['email']
recipients:
- { email: 'sales@example.com' }
tasks:
# ...
app.cache_warmup:
options:
notification:
enabled: false # never notified
tasks:
# ...
```

| Key | Type | Description |
|--------------|------------|------------------------------------------------------------------------------------------------------------------------------|
| `enabled` | `bool` | Notify the end of the executions of this process. |
| `statuses` | `string[]` | Ends of process executions to notify, see [statuses](#statuses). |
| `channels` | `string[]` | Notifier channels, e.g. `chat/slack`, `email`. Empty: the `channel_policy` of the notifier, by importance. |
| `recipients` | `array[]` | Recipients, with an `email` and/or a `phone` (required by the `email` and `sms` channels). Empty: the `admin_recipients` of the notifier. |

A key missing (or `null`) in the process options is inherited from the bundle configuration. Invalid process options
raise an options resolver error.

Statuses
--------

| Status | End of the process execution | Importance |
|----------------------|------------------------------------------------------------------------------------------------------------------------------------|------------|
| `failed` | Failed (status `failed`). | `high` |
| `finish_with_report` | Finished (status `finish`) with log levels counted in its [report](04-process_executions_and_logs.md#report), e.g. `{"Warning": 3}`. The counted levels depend on `logs.report_increment_level` (default `Warning`). | `medium` |
| `finish` | Finished without log levels counted in its report. | `low` |

The importance selects the channels of the notifier `channel_policy` when no `channels` are configured.

Notification
------------

The notification (`CleverAge\UiProcessBundle\Notifier\ProcessExecutionNotification`) contains:
- the subject, e.g. `Process "app.daily_import" failed`,
- the status, the start date and the duration of the execution, the log levels counted in its report, the error
message (failed execution), the log file and the id of the process execution,
- the exception of a failed execution (its trace is added to the emails).

What is displayed depends on the channel and on the transport: the emails contain the subject, the content and the
exception, the chat messages contain the subject, plus the content and the exception for some transports (e.g.
Slack), the SMS contain the subject.

An error while sending the notification (unknown channel, transport failure...) is logged (`cleverage_ui_process`
Monolog channel) and does not change the result of the process.

Custom notifications
--------------------

The bundle dispatches a `CleverAge\UiProcessBundle\Event\ProcessExecutionEndedEvent` when a top-level process
execution has ended and has been saved, with the `ProcessExecution` entity (status, dates, report, context...) and
the error of a failed execution. The notification is sent by a listener of this event. To send your own
notifications (or anything else), keep `notification.enabled` to `false` and listen to this event:

```php
use CleverAge\UiProcessBundle\Entity\Enum\ProcessExecutionStatus;
use CleverAge\UiProcessBundle\Event\ProcessExecutionEndedEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
final class ProcessExecutionEndedListener
{
public function __invoke(ProcessExecutionEndedEvent $event): void
{
if (ProcessExecutionStatus::Failed === $event->processExecution->status) {
// ...
}
}
}
```

This event does not require `symfony/notifier`. The events of the process bundle (`cleverage_process.end`,
`cleverage_process.fail`, see the
[process bundle documentation](https://github.com/cleverage/process-bundle/blob/main/docs/04-advanced_workflow.md#events))
are dispatched for every process, sub-processes included, before the process execution is saved.
12 changes: 12 additions & 0 deletions src/DependencyInjection/CleverAgeUiProcessExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
use CleverAge\UiProcessBundle\Message\ProcessExecuteMessage;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Notifier\NotifierInterface;

final class CleverAgeUiProcessExtension extends Extension implements PrependExtensionInterface
{
Expand Down Expand Up @@ -51,6 +53,16 @@ public function load(array $configs, ContainerBuilder $container): void

$container->getDefinition(ProcessDashboardController::class)
->setArgument('$logoPath', $config['design']['logo_path']);

if (interface_exists(NotifierInterface::class)) {
$container->getDefinition('cleverage_ui_process.notifier.process_execution')
->setArgument('$defaultOptions', $config['notification']);
} else {
if ($config['notification']['enabled']) {
throw new LogicException('The notification of the process executions requires symfony/notifier: run "composer require symfony/notifier".');
}
$container->removeDefinition('cleverage_ui_process.notifier.process_execution');
}
}

/**
Expand Down
Loading
Loading