# Native Observability — ECA Bridge

This module exposes Native Observability trace signals as **ECA (Event-Condition-Action)** events, enabling site builders to create no-code automations and alerts based on HTTP observability data without writing PHP.

---

## Requirements

| Dependency | Version |
|---|---|
| `native_observability` | (same release) |
| `native_observability_execution` | (same release) |
| `native_observability_spans` | (same release) |
| `drupal/eca` | ^3.1 |
| `drupal/modeler_api` | ^1.1 |
| `drupal/bpmn_io` | ^2.0 *(visual editor — strongly recommended)* |

The bridge follows the default self-route exclusion behaviour from Native Observability. Routes whose machine name starts with `native_observability` are skipped by the base trace subscriber and therefore do not generate ECA events unless you explicitly disable the exclusion for debugging.

---

## How it works

The bridge registers two ECA event plugins that wrap existing Symfony events
dispatched by Native Observability:

| ECA trigger | Symfony event | Fires when… |
|---|---|---|
| **Trace recorded** | `native_observability.trace_recorded` | An HTTP request trace is persisted to the database |
| **Traces deleted** | `native_observability.traces_deleted` | All traces are bulk-deleted via the admin UI or Drush |

ECA's own plugin manager subscribes to these events automatically — the bridge
adds no additional event subscribers.

The first release keeps the surface area intentionally small: trace recorded and traces deleted are enough to start wiring observability into ECA workflows without overcomplicating the event model.

> **Slow request detection**
> ECA enforces that each Symfony event name maps to exactly one ECA plugin.
> A separate "Slow request detected" derivative would share the same underlying
> Symfony event and is therefore rejected by ECA at boot. Use the
> *Trace recorded* trigger with a **Scalar comparison** condition on
> `[event:duration_ms]` instead — see the step-by-step example below.

### Route exclusion

When **Capture › Exclude own routes** is enabled (the default), Native
Observability skips tracing its own admin pages before storage runs.
`TraceRecordedEvent` is never dispatched for those routes, so ECA rules are
never triggered for them. No extra configuration is required here.

---

## Available tokens

All tokens are accessible under the `[event:*]` prefix in every ECA action
and condition field.

### Trace recorded

| Token | Type | Description |
|---|---|---|
| `[event:route]` | string | Drupal route machine name (e.g. `node.view`) |
| `[event:method]` | string | HTTP method in upper case (`GET`, `POST`, …) |
| `[event:status_code]` | string | HTTP response status code (`200`, `404`, `500`, …) |
| `[event:duration_ms]` | integer (as string) | Request duration in milliseconds |
| `[event:is_ajax]` | `1` / `0` | Whether the request was an AJAX call |

### Traces deleted

| Token | Type | Description |
|---|---|---|
| `[event:deleted_count]` | integer (as string) | Number of deleted trace records |

---

## Step-by-step: first working setup

> **Why `bpmn_io` is mandatory**
>
> `modeler_api` (the routing layer behind ECA's admin UI) only registers the
> "Add model" route and the edit canvas when at least one real modeler plugin
> is present. With only the built-in `fallback` plugin, the route
> `/admin/config/workflow/eca/add` does not exist and no "Add" button is
> rendered. Installing `bpmn_io` registers the `bpmn_io` modeler plugin,
> which unlocks the full UI.

### Step 1 — Install `bpmn_io`

```bash
composer require drupal/bpmn_io
drush pm:install bpmn_io
```

After this the "Add model" button appears at `/admin/config/workflow/eca`.

### Step 2 — Enable the bridge module

```bash
drush pm:install native_observability_eca_bridge
```

This also enables `eca`, `eca_ui`, `eca_base`, and `eca_log` automatically
because they are declared as dependencies.

Verify the two Native Observability event plugins are registered:

```bash
drush ev "
\$defs = \Drupal::service('plugin.manager.eca.event')->getDefinitions();
foreach (\$defs as \$id => \$d) {
  if (str_starts_with(\$id, 'native_observability')) {
    echo \$id . ': ' . (string)\$d['label'] . PHP_EOL;
  }
}"
# Expected output:
# native_observability:trace_recorded: Trace recorded
# native_observability:traces_deleted: Traces deleted
```

### Step 3 — Optional: install ready-made demo ECA models

The optional `native_observability_eca_bridge_demo` module installs five ECA
configurations from its `config/install/` directory. They appear in the ECA UI
and can be used to verify the bridge without building a model manually:

| Demo config | Behaviour |
|---|---|
| `no_demo_log_all_traces` | Logs every recorded trace |
| `no_demo_500_errors` | Logs HTTP 500 traces |
| `no_demo_slow_requests` | Logs traces with `[event:duration_ms] >= 3000` |
| `no_demo_ajax_requests` | Logs traces with `[event:is_ajax] = 1` |
| `no_demo_traces_deleted` | Logs bulk trace deletions |

Install it with:

```bash
drush pm:install native_observability_eca_bridge_demo
```

Then browse to:

```text
/admin/config/workflow/eca
```

or verify from Drush:

```bash
drush config:get eca.eca.no_demo_log_all_traces id
drush config:get eca.eca.no_demo_500_errors id
drush config:get eca.eca.no_demo_slow_requests id
drush config:get eca.eca.no_demo_ajax_requests id
drush config:get eca.eca.no_demo_traces_deleted id
drush watchdog:show --type=native_observability --count=20
```

See `modules/native_observability_eca_bridge_demo/README.md` for the full demo
workflow.

### Step 4 — Open the ECA admin

Navigate to **Configuration › Workflow › ECA**:

```
/admin/config/workflow/eca
```

### Step 5 — Create a new ECA model

1. Click **+ Add model**.
2. Choose the **BPMN.io** modeler.
3. Enter a machine name and label, e.g. `native_observability_alerts`.
4. Click **Save** — the visual BPMN canvas opens.

### Step 6 — Add a trigger (start event)

In the BPMN canvas:

1. Drag a **Start event** (circle) from the left palette onto the canvas.
2. Click the circle to open its properties panel on the right.
3. Under **Plugin** select **Native Observability** → **Trace recorded**.
4. The properties panel lists all available `[event:*]` tokens.

### Step 7 — Add an action

1. Drag a **Task** (rounded rectangle) onto the canvas.
2. Draw a **sequence flow** arrow from the start event to the task.
3. Click the task → **Plugin** → **ECA Log** → **Write log message**.
4. Fill in:
   - **Type** (channel): `native_observability`
   - **Severity**: `Notice`
   - **Message**: `[event:route] [event:method] [event:status_code] [event:duration_ms]ms`
5. Click **Save** (top-right).

### Step 8 — Add a condition (optional: filter by duration)

To fire only when a request takes ≥ 3 seconds:

1. Click the sequence flow arrow between the start event and the task.
2. In the properties panel set **Condition** → **Plugin** →
   **ECA Base** → **Scalar comparison**.
3. Fill in:
   - **First value**: `[event:duration_ms]`
   - **Operator**: `>=`
   - **Second value**: `3000`
4. Click **Save**.

### Step 9 — Test the rule

Browse any page on the site (a cache-warm page hit works). Then open the
Drupal log at `/admin/reports/dblog` and filter by **Type** =
`native_observability`. One log entry per traced request should appear.

Or via Drush:

```bash
drush watchdog:show --type=native_observability --count=10
```

### Step 10 — Verify with the Traces dashboard

Open **Reports › Native Observability** to confirm that traces are being
captured. If the log is empty, check that **Capture › Enable trace capture**
is enabled at `/admin/config/development/native-observability`.

---

## Deploying rules via configuration YAML

For CI/CD deployments or when the visual editor is not available, place a
config file in any custom module at
`config/install/eca.eca.{machine_name}.yml`:

```yaml
langcode: en
status: true
dependencies:
  module:
    - eca_base
    - eca_log
    - native_observability_eca_bridge
id: native_observability_log_traces
modeller: bpmn_io
label: 'Log every trace to watchdog'
version: v1
weight: null
events:
  Event_trace:
    plugin: 'native_observability:trace_recorded'
    label: 'Trace recorded'
    configuration: {}
    successors:
      - id: Action_log
        condition: ''
conditions: {}
gateways: {}
actions:
  Action_log:
    plugin: eca_write_log_message
    label: 'Log trace'
    configuration:
      channel: native_observability
      severity: 5
      message: '[event:route] [event:method] [event:status_code] [event:duration_ms]ms'
    successors: {}
```

Example with slow-request condition (>= 3 000 ms):

```yaml
langcode: en
status: true
dependencies:
  module:
    - eca_base
    - eca_log
    - native_observability_eca_bridge
id: native_observability_slow_requests
modeller: bpmn_io
label: 'Log slow requests (>= 3 s)'
version: v1
weight: null
events:
  Event_trace:
    plugin: 'native_observability:trace_recorded'
    label: 'Trace recorded'
    configuration: {}
    successors:
      - id: Action_log
        condition: Condition_slow
conditions:
  Condition_slow:
    plugin: eca_scalar
    label: 'duration >= 3000'
    configuration:
      left: '[event:duration_ms]'
      right: '3000'
      operator: '>='
      negate: false
gateways: {}
actions:
  Action_log:
    plugin: eca_write_log_message
    label: 'Log slow trace'
    configuration:
      channel: native_observability
      severity: 4
      message: 'Slow request: [event:route] took [event:duration_ms]ms'
    successors: {}
```

Import the file by placing it in `config/install/` of an enabled module and
running:

```bash
ddev exec drush config:import --partial --source=path/to/config/install
```

---

## Running tests

```bash
ddev exec vendor/bin/phpunit \
  --configuration web/core/phpunit.xml.dist \
  web/modules/custom/native_observability/modules/native_observability_eca_bridge/tests/ \
  --testdox
```
